-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcoordinate_picker.py
More file actions
238 lines (189 loc) · 8.75 KB
/
Copy pathcoordinate_picker.py
File metadata and controls
238 lines (189 loc) · 8.75 KB
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# -*- coding:utf-8 -*-
"""
@File : coordinate_picker.py
@Time : 2025/11/19
@Author : mr_guo
@Function : 坐标拾取核心功能类
"""
import cv2
import os
import threading
class CoordinatePicker:
"""坐标拾取器核心类"""
def __init__(self, image_path):
self.image_path = image_path
self.is_running = False
self.thread = None
# 回调函数
self.coordinate_callback = None
self.finished_callback = None
# 图像相关变量
self.marked_img = None
self.original_height = 0
self.original_width = 0
self.display_scale = 1.0
def set_coordinate_callback(self, callback):
"""设置坐标回调函数"""
self.coordinate_callback = callback
def set_finished_callback(self, callback):
"""设置结束回调函数"""
self.finished_callback = callback
def get_image_info(self):
"""获取图像信息"""
if not os.path.exists(self.image_path):
raise FileNotFoundError(f"图像文件不存在: {self.image_path}")
img = cv2.imread(self.image_path)
if img is None:
raise ValueError(f"无法读取图像文件: {self.image_path}")
height, width = img.shape[:2]
scale = self.calculate_display_scale(width, height)
return {
'filename': os.path.basename(self.image_path),
'filepath': self.image_path,
'width': width,
'height': height,
'scale': scale
}
@staticmethod
def calculate_display_scale(img_width, img_height):
"""计算显示缩放因子"""
diagonal = (img_width ** 2 + img_height ** 2) ** 0.5
if diagonal < 300:
return 0.3
elif diagonal < 600:
return 0.5
elif diagonal < 1200:
return 0.8
elif diagonal < 2000:
return 1.2
else:
return 2.0
def mouse_callback(self, event, x, y, _flags, _param):
"""鼠标移动事件回调"""
if event == cv2.EVENT_MOUSEMOVE and self.is_running:
# 调用坐标回调
if self.coordinate_callback:
self.coordinate_callback(x, y)
# 创建临时图像副本
display_img = self.marked_img.copy()
# 动态计算线条粗细和字体参数
line_thickness = max(1, int(2 * self.display_scale))
font_scale = max(0.3, 0.7 * self.display_scale)
font_thickness = max(1, int(2 * self.display_scale))
# 在鼠标位置绘制动态十字线
cv2.line(display_img, (x, 0), (x, display_img.shape[0]), (0, 0, 255), line_thickness)
cv2.line(display_img, (0, y), (display_img.shape[1], y), (0, 0, 255), line_thickness)
# 准备坐标文本
coord_text = f'({x}, {y})'
# 计算文本尺寸和位置
text_size = cv2.getTextSize(coord_text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, font_thickness)[0]
text_width, text_height = text_size
# 计算文本背景区域
padding = int(5 * self.display_scale)
display_width, display_height = display_img.shape[1], display_img.shape[0]
# 初始文本位置
bg_x1 = x + 10
bg_y1 = y - text_height - padding * 2
# 智能调整文本显示位置
if bg_x1 + text_width + padding * 2 > display_width:
bg_x1 = x - text_width - padding * 2 -10
if bg_y1 < 0:
bg_y1 = y + 10
# 确保坐标在图像范围内
bg_x1 = max(0, min(bg_x1, display_width - text_width - padding * 2))
bg_y1 = max(0, min(bg_y1, display_height - text_height - padding * 2))
# 计算最终背景区域
bg_x2 = bg_x1 + text_width + padding * 2
bg_y2 = bg_y1 + text_height + padding * 2
# 绘制半透明背景
overlay = display_img.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
cv2.addWeighted(overlay, 0.6, display_img, 0.4, 0, display_img)
# 绘制坐标文本
text_x = bg_x1 + padding
text_y = bg_y2 - padding
cv2.putText(display_img, coord_text, (text_x, text_y),
cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), font_thickness)
# 更新显示
cv2.imshow("Coordinate Picker", display_img)
def run_picker(self):
"""运行坐标拾取器"""
try:
# 读取图像
original_img = cv2.imread(self.image_path)
if original_img is None:
raise ValueError(f"无法读取图像: {self.image_path}")
# 获取图像尺寸
self.original_height, self.original_width = original_img.shape[:2]
self.display_scale = self.calculate_display_scale(self.original_width, self.original_height)
# 创建带标记的图像副本
self.marked_img = original_img.copy()
# 创建窗口
window_title = "Coordinate Picker"
cv2.namedWindow(window_title, cv2.WINDOW_NORMAL)
# 设置窗口大小(基于缩放因子)
initial_width = int(self.original_width * min(1.5, 1 + (self.display_scale - 1) * 0.5))
initial_height = int(self.original_height * min(1.5, 1 + (self.display_scale - 1) * 0.5))
cv2.resizeWindow(window_title, initial_width, initial_height)
# 修复:手动保持宽高比
aspect_ratio = self.original_width / self.original_height
# 设置鼠标回调
cv2.setMouseCallback(window_title, self.mouse_callback)
# 初始显示
cv2.imshow(window_title, self.marked_img)
print("坐标拾取器已启动 - 在图像上移动鼠标查看坐标,按ESC退出")
print(f"图像原始尺寸: {self.original_width}x{self.original_height}, 宽高比: {aspect_ratio:.2f}")
# 主循环
self.is_running = True
while self.is_running:
key = cv2.waitKey(10) & 0xFF
if key == 27: # ESC键退出
break
# 检查窗口是否关闭
if cv2.getWindowProperty(window_title, cv2.WND_PROP_VISIBLE) < 1:
break
# 修复:手动维护宽高比
try:
# 获取当前窗口尺寸
current_size = cv2.getWindowImageRect(window_title)
if current_size is not None and current_size[2] > 0 and current_size[3] > 0:
current_width, current_height = current_size[2], current_size[3]
# 计算当前宽高比
current_ratio = current_width / current_height
# 如果宽高比偏差超过5%,则强制调整
if abs(current_ratio - aspect_ratio) > 0.05:
# 基于宽度计算正确高度
correct_height = int(current_width / aspect_ratio)
# 基于高度计算正确宽度
correct_width = int(current_height * aspect_ratio)
# 选择变化较小的调整方式
width_diff = abs(current_width - correct_width)
height_diff = abs(current_height - correct_height)
if width_diff < height_diff:
cv2.resizeWindow(window_title, correct_width, current_height)
else:
cv2.resizeWindow(window_title, current_width, correct_height)
except (cv2.error, TypeError, ValueError) as e:
# 忽略调整大小时的异常,但记录其他异常
if not isinstance(e, cv2.error) or "window does not exist" not in str(e):
# 如果不是窗口不存在的错误,记录日志
print(f"调整窗口大小时出现异常: {e}")
pass
except Exception as e:
print(f"坐标拾取器错误: {e}")
import traceback
traceback.print_exc()
finally:
self.is_running = False
cv2.destroyAllWindows()
if self.finished_callback:
self.finished_callback()
def start(self):
"""启动坐标拾取器"""
if not self.is_running:
self.thread = threading.Thread(target=self.run_picker)
self.thread.daemon = True
self.thread.start()
def stop(self):
"""停止坐标拾取器"""
self.is_running = False