1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
!pip install opencv-python
import cv2
import numpy as np
from scipy.interpolate import interp1d
# 全域變數
random_colors = None
random_interp = None
ml_colors = None
ml_interp = None
img_rgb = None # 原始圖片的 RGB 格式
img_raw = None # 原始圖片的 BGR 格式 (用於顯示和臨時繪圖)
img_with_rois = None # 帶有永久繪製的 ROI 框的圖片
# 全域變數用於 ROI 選擇
roi_points = [] # 儲存點擊的 (x, y) 座標
# ROI 選擇階段的狀態機:
# 0: 準備選擇 '隨機分案' 色標的起點 (等待第一次點擊)
# 1: 選擇 '隨機分案' 色標的終點 (第一次點擊已發生,等待第二次點擊)
# 2: '隨機分案' 色標已選定,準備選擇 '機器學習' 色標的起點 (等待第三次點擊)
# 3: 選擇 '機器學習' 色標的終點 (第三次點擊已發生,等待第四次點擊)
# 4: 兩個色標均已選定,進入年份識別階段
selecting_roi_state = 0
# 建立色標 → 年份的插值函數
# 參數:
# roi: 圖片中色標的區域 (numpy 陣列,包含色標的像素數據)
# year_min: 色標代表的最小年份 (例如: 2014)
# year_max: 色標代表的最大年份 (例如: 2020)
# debug_name: 用於調試輸出的色標名稱
# 返回值:
# center_col: 色標中心列的 RGB 顏色序列 (N x 3 numpy 陣列)
# interp_func: 從像素在色標中的垂直位置 (索引) 到年份的插值函數
def build_color_axis(roi, year_min=2014, year_max=2020, debug_name="色標"):
h = roi.shape[0] # 色標區域的高度
# 從 ROI 中選取中心列的像素顏色。我們假設色標是垂直的。
center_col = roi[:, roi.shape[1] // 2, :]
# 確保年份從上到下(從 year_max 到 year_min)對應像素索引 (0 到 h-1)
# 這樣色條頂部 (索引 0) 對應 year_max (2020),底部 (索引 h-1) 對應 year_min (2014)
years = np.linspace(year_max, year_min, h)
print(f"\n--- 調試信息: {debug_name} ---")
print(f"ROI 高度: {h} 像素")
print(f"色標頂部顏色 (索引 0): {center_col[0]}, 預期年份: {years[0]:.2f}")
print(f"色標底部顏色 (索引 {h-1}): {center_col[h-1]}, 預期年份: {years[h-1]:.2f}")
print(f"插值映射: 索引 0 -> {year_max}, 索引 {h-1} -> {year_min}")
print(f"示例年份 (頂部, 中間, 底部): {years[0]:.2f}, {years[h//2]:.2f}, {years[h-1]:.2f}")
return center_col, interp1d(np.arange(h), years, bounds_error=False, fill_value="extrapolate")
# 匹配單個顏色到最近的色標顏色,並返回對應年份
# 參數:
# sample_color: 要匹配的單個像素顏色 (1x3 numpy 陣列,RGB 值)
# color_axis: 預先建立的色標顏色序列 (N x 3 numpy 陣列)
# interp_func: 對應色標的插值函數
# 返回值:
# 年份 (浮點數)
def match_color_to_year(sample_color, color_axis, interp_func):
# 計算樣本顏色與色標顏色序列中所有顏色的歐幾里得距離
dists = np.linalg.norm(color_axis - sample_color, axis=1)
idx = np.argmin(dists) # 找到距離最近的顏色在色標顏色序列中的索引
# 使用插值函數將找到的索引轉換為對應的年份
return float(interp_func(idx))
# 鼠標點擊事件處理函數 (年份識別階段)
# 當用戶點擊圖片時,此函數會被呼叫
def click_event(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN: # 檢查是否為鼠標左鍵點擊事件
global img_rgb, random_colors, random_interp, ml_colors, ml_interp
# 確保必要的全局變數已經被初始化
if img_rgb is None or random_colors is None or ml_colors is None:
print("錯誤:圖片或色標數據未載入。請確保已成功框選色標。")
return
print(f"\n點擊位置: ({x}, {y})")
# 獲取點擊位置的像素顏色 (RGB 值)
clicked_color = img_rgb[y, x, :]
print(f"點擊像素顏色 (RGB): {clicked_color}")
# 計算點擊顏色與兩個色標的最小距離
random_dists = np.linalg.norm(random_colors - clicked_color, axis=1)
ml_dists = np.linalg.norm(ml_colors - clicked_color, axis=1)
random_dist = np.min(random_dists)
ml_dist = np.min(ml_dists)
print(f"與 '隨機分案' 色標的最小距離: {random_dist:.2f}")
print(f"與 '機器學習' 色標的最小距離: {ml_dist:.2f}")
# 根據哪個色標的距離更小,決定點擊的顏色屬於哪種改革類型
if random_dist < ml_dist:
reform_type = "隨機分案"
# 使用 '隨機分案' 的插值函數來獲取年份
reform_time = match_color_to_year(clicked_color, random_colors, random_interp)
else:
reform_type = "機器學習"
# 使用 '機器學習' 的插值函數來獲取年份
reform_time = match_color_to_year(clicked_color, ml_colors, ml_interp)
print(f"結果: {reform_type}, 年份: {reform_time:.2f}")
# 鼠標事件處理函數 (ROI 選擇階段)
# 此函數只負責記錄點擊點和繪製臨時框,狀態轉換由主循環控制
def select_roi_event(event, x, y, flags, param):
global roi_points, selecting_roi_state, img_raw, img_with_rois
if event == cv2.EVENT_LBUTTONDOWN:
roi_points.append((x, y))
# 狀態轉換邏輯在 main 函數中根據 roi_points 的長度進行
elif event == cv2.EVENT_MOUSEMOVE:
# 在選擇第一個 ROI 的第二個點時繪製臨時框 (綠色)
if selecting_roi_state == 0 and len(roi_points) == 1:
temp_img = img_raw.copy() # 從原始圖片複製,避免覆蓋永久框
cv2.rectangle(temp_img, roi_points[0], (x, y), (0, 255, 0), 1) # 綠色臨時框
cv2.imshow('點擊獲取年份 (請框選色標)', temp_img)
# 在選擇第二個 ROI 的第二個點時繪製臨時框 (綠色)
elif selecting_roi_state == 2 and len(roi_points) == 3:
temp_img = img_with_rois.copy() # 從已繪製第一個 ROI 的圖片複製
cv2.rectangle(temp_img, roi_points[2], (x, y), (0, 255, 0), 1) # 綠色臨時框
cv2.imshow('點擊獲取年份 (請框選色標)', temp_img)
# 主函數
def main():
global img_rgb, random_colors, random_interp, ml_colors, ml_interp, img_raw, img_with_rois, selecting_roi_state, roi_points
# 圖片路徑
img_path = 'D:/PyTorch_practice/map/x.png'
img_raw = cv2.imread(img_path) # 載入圖片 (以 BGR 格式)
if img_raw is None:
print(f"錯誤:圖片載入失敗,請檢查路徑: {img_path}")
return
img_rgb = cv2.cvtColor(img_raw, cv2.COLOR_BGR2RGB) # 將圖片轉換為 RGB 格式
img_with_rois = img_raw.copy() # 創建一個副本用於顯示和繪製永久 ROI 框
cv2.namedWindow('點擊獲取年份 (請框選色標)')
# 初始階段設定鼠標回調函數為 ROI 選擇模式
cv2.setMouseCallback('點擊獲取年份 (請框選色標)', select_roi_event)
print("--- 互動式色標框選 ---")
print("步驟 1/2: 請框選 '隨機分案' 色標 (左側藍色條)。")
print(" 點擊色標的左上角,然後拖曳鼠標到右下角並釋放。")
# ROI 選擇循環
while selecting_roi_state < 4: # 當 selecting_roi_state 達到 4 時表示兩個 ROI 都已選定
cv2.imshow('點擊獲取年份 (請框選色標)', img_with_rois) # 持續顯示帶有永久 ROI 框的圖片
key = cv2.waitKey(1) & 0xFF
if key == ord('q'): # 按 'q' 鍵退出
cv2.destroyAllWindows()
return
# 根據 roi_points 的長度來管理狀態轉換
if selecting_roi_state == 0 and len(roi_points) == 1: # 隨機色標起點已點擊
selecting_roi_state = 1 # 進入等待隨機色標終點的狀態
print("請框選 '隨機分案' 色標的終點 (右下角)。")
elif selecting_roi_state == 1 and len(roi_points) == 2: # 隨機色標終點已點擊
# 繪製 '隨機分案' 色標的永久矩形框 (藍色)
cv2.rectangle(img_with_rois, roi_points[0], roi_points[1], (255, 0, 0), 2)
cv2.imshow('點擊獲取年份 (請框選色標)', img_with_rois) # 更新顯示
selecting_roi_state = 2 # 進入準備選擇 '機器學習' 色標起點的狀態
print("\n步驟 2/2: 請框選 '機器學習' 色標 (右側紅色條)。")
print(" 點擊色標的左上角,然後拖曳鼠標到右下角並釋放。")
elif selecting_roi_state == 2 and len(roi_points) == 3: # 機器學習色標起點已點擊
selecting_roi_state = 3 # 進入等待機器學習色標終點的狀態
print("請框選 '機器學習' 色標的終點 (右下角)。")
elif selecting_roi_state == 3 and len(roi_points) == 4: # 機器學習色標終點已點擊
# 繪製 '機器學習' 色標的永久矩形框 (紅色)
cv2.rectangle(img_with_rois, roi_points[2], roi_points[3], (0, 0, 255), 2)
cv2.imshow('點擊獲取年份 (請框選色標)', img_with_rois) # 更新顯示
selecting_roi_state = 4 # 兩個 ROI 都已選定,退出循環
# 色標選擇完成後,提取座標並建立顏色軸
# 確保座標是正確的 (x_start, y_start, x_end, y_end) 格式,並且 start <= end
random_roi_coords = (min(roi_points[0][0], roi_points[1][0]), min(roi_points[0][1], roi_points[1][1]),
max(roi_points[0][0], roi_points[1][0]), max(roi_points[0][1], roi_points[1][1]))
ml_roi_coords = (min(roi_points[2][0], roi_points[3][0]), min(roi_points[2][1], roi_points[3][1]),
max(roi_points[2][0], roi_points[3][0]), max(roi_points[2][1], roi_points[3][1]))
# 從原始 RGB 圖片中提取 ROI 圖片數據
random_roi = img_rgb[random_roi_coords[1]:random_roi_coords[3], random_roi_coords[0]:random_roi_coords[2]]
ml_roi = img_rgb[ml_roi_coords[1]:ml_roi_coords[3], ml_roi_coords[0]:ml_roi_coords[2]]
# 建立兩個色標的顏色軸和插值函數
random_colors, random_interp = build_color_axis(random_roi, debug_name="隨機分案色標")
ml_colors, ml_interp = build_color_axis(ml_roi, debug_name="機器學習色標")
# 轉換到年份識別階段
cv2.setMouseCallback('點擊獲取年份 (請框選色標)', click_event) # 將鼠標回調函數改為年份識別模式
cv2.setWindowTitle('點擊獲取年份 (請框選色標)', '點擊獲取年份') # 更新視窗標題
print("\n色標框選完成。現在請點擊地圖上的省份以獲取年份。按 'q' 鍵退出。")
# 年份識別循環
while True:
cv2.imshow('點擊獲取年份', img_with_rois) # 繼續顯示帶有永久 ROI 框的圖片
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
|