Repository navigation
Expand file tree
/
Copy pathmain.py
More file actions
329 lines (277 loc) · 13.9 KB
/
Copy pathmain.py
File metadata and controls
329 lines (277 loc) · 13.9 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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# # main.py
# # ----------------------------------------
# # 檔案:main.py
# # 功能:整合視窗定位、限定 ROI 串流、運動偵測、物件偵測與點擊策略
# # 作者:BO RUEI HUANG
# # 建立日期:2025-05-07
# # 更新日期:2025-05-09
# # ----------------------------------------
# from config import CONFIG
# from window_locator.locator import WindowLocator
# from clicker.clicker import click_center
# from window_streamer.streamer import WindowStreamer
# from motion_detector.motion_detector import MotionDetector
# from object_detector.detector import ObjectDetector
# import cv2 # type: ignore
# import mss # type: ignore
# import numpy as np
# import time
# import sys
# import threading
# import queue
# # 初始化模組
# motion = MotionDetector()
# detector = ObjectDetector()
# def make_processor(full_bounds):
# consec = 0 # 連續幀計數
# last_click = 0.0 # 上次點擊時間戳
# def process_frame(frame: np.ndarray) -> np.ndarray:
# nonlocal consec, last_click
# h, w = frame.shape[:2]
# t0 = time.time()
# # 運動偵測:先判斷是否有移動
# # if motion.has_motion(frame):
# # # 在畫面上顯示提示文字
# # cv2.putText(frame, 'Motion Detected', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
# # else:
# # # 無運動時顯示並跳過後續偵測
# # cv2.putText(frame, 'No Motion', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (200, 200, 200), 2)
# # consec = 0
# # return frame
# t1 = time.time(); print("Motion Detector need times:", (t1-t0)*1000, "ms") # 偵測時間約25ms
# # 畫對準方框
# bw = int(w * CONFIG['align_box_width_ratio'])
# bh = int(h * CONFIG['align_box_height_ratio'])
# ov = int(h * CONFIG['align_box_vertical_offset_ratio'])
# oh = int(w * CONFIG['align_box_horizontal_offset_ratio'])
# cx, cy = w // 2 + oh, h // 2 + ov
# ax1, ay1 = cx - bw // 2, cy - bh // 2
# ax2, ay2 = cx + bw // 2, cy + bh // 2
# cv2.rectangle(frame, (ax1, ay1), (ax2, ay2), tuple(CONFIG['align_box_color']), 2)
# detections = detector.detect(frame)
# for d in detections:
# x1, y1, x2, y2 = d['box']
# conf = d['conf']
# name = d['name']
# # 畫出偵測框
# cv2.rectangle(frame, (x1, y1), (x2, y2), (0,255,0), 2)
# # 先畫一個底色框(可選,讓黑字更清晰)
# (tw, th), _ = cv2.getTextSize(f"{name} {conf:.2f}", cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
# cv2.rectangle(frame,
# (x1, y1 - th - 4),
# (x1 + tw + 4, y1),
# (255,255,255), # 白底
# cv2.FILLED)
# # 再畫黑字
# cv2.putText(frame,
# f"{name} {conf:.2f}",
# (x1 + 2, y1 - 2),
# cv2.FONT_HERSHEY_SIMPLEX,
# 0.5,
# (0,0,0), # 黑色文字
# 1)
# t2 = time.time(); print("Object Detector need times:", (t2-t1)*1000, "ms")
# dx, dy = (x1 + x2) // 2, (y1 + y2) // 2
# # 去抖動:中心在對準方框內才累計
# if ax1 <= dx <= ax2 and ay1 <= dy <= ay2:
# consec += 1
# else:
# consec = 0
# # 冷卻 + 觸發點擊
# if consec >= CONFIG['debounce_frames'] and (time.time() - last_click) >= CONFIG['click_cooldown_seconds']:
# click_center(full_bounds,
# clicks=min(CONFIG['clicks'], CONFIG['max_clicks']),
# interval=CONFIG['interval'])
# last_click = time.time()
# consec = 0
# break
# return frame
# return process_frame
# if __name__ == '__main__':
# # 1. 取得整個投影視窗的螢幕座標
# info = WindowLocator(CONFIG).get_bounds()
# if not info:
# print('找不到指定視窗')
# sys.exit(1)
# # 2. 計算要串流的 ROI 範圍
# h = info['Height']
# top_offset = int(h * CONFIG['roi_top_ratio'])
# height_range = int(h * (CONFIG['roi_bottom_ratio'] - CONFIG['roi_top_ratio']))
# roi_bounds = {
# 'X': info['X'],
# 'Y': info['Y'] + top_offset,
# 'Width': info['Width'],
# 'Height': height_range
# }
# # 3. 建立處理器
# processor = make_processor(info)
# # 4. 啟動串流,只顯示 ROI
# if CONFIG['enable_stream']:
# print(f"啟動 ROI 串流:{roi_bounds}")
# WindowStreamer(roi_bounds, CONFIG).start(frame_callback=processor)
# # 5. 結束後截圖
# if CONFIG.get('screenshot_path'):
# from window_streamer.screencap import capture_window_screenshot
# capture_window_screenshot(info, save_path=CONFIG['screenshot_path'])
# # 6. 最後點擊一次視窗中心
# click_center(info, clicks=min(CONFIG['clicks'], CONFIG['max_clicks']), interval=CONFIG['interval'])
# main.py
# ----------------------------------------
# 檔案:main.py
# 功能:整合視窗定位、限定 ROI 串流、物件偵測+追蹤(Tracker)與點擊策略
# 作者:BO RUEI HUANG
# 建立日期:2025-05-07
# 更新日期:2025-05-11 新增Tracker偵測與追蹤流程
# ----------------------------------------
import sys
import time
import cv2 # type: ignore
from mss import mss as MSS # 修正 mss import
import numpy as np
from config import CONFIG
from window_locator.locator import WindowLocator
from clicker.clicker import click_center
from object_detector.detector import ObjectDetector
def main():
# 1. 定位原始視窗
info = WindowLocator(CONFIG).get_bounds()
if not info:
print('找不到指定視窗')
sys.exit(1)
# 2. 計算串流 ROI
h_full = info['Height']
top_offset = int(h_full * CONFIG['roi_top_ratio'])
roi = {
'left': info['X'],
'top': info['Y'] + top_offset,
'width': info['Width'],
'height': int(h_full * (CONFIG['roi_bottom_ratio'] - CONFIG['roi_top_ratio']))
}
# 3. 初始化
detector = ObjectDetector()
tracker = None
lost_frames = 0
frame_count = 0 # 新增幀計數器,用於定期重新偵測
MAX_LOST = CONFIG.get('tracker_max_lost_frames', 30)
DETECT_INTERVAL = CONFIG.get('detect_interval', 5) # 每隔多少幀重新偵測一次
last_click = 0.0
# 4. 開始擷取與顯示
with MSS() as sct:
cv2.namedWindow(CONFIG['stream_window'], cv2.WINDOW_NORMAL)
while True:
img = np.array(sct.grab(roi))
frame = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
h, w = frame.shape[:2]
# 繪製對準方框
bw = int(w * CONFIG['align_box_width_ratio'])
bh = int(h * CONFIG['align_box_height_ratio'])
ov = int(h * CONFIG['align_box_vertical_offset_ratio'])
oh = int(w * CONFIG['align_box_horizontal_offset_ratio'])
cx, cy = w // 2 + oh, h // 2 + ov
ax1, ay1 = cx - bw // 2, cy - bh // 2
ax2, ay2 = cx + bw // 2, cy + bh // 2
# 繪製對準方框(紅色)
cv2.rectangle(frame, (ax1, ay1), (ax2, ay2), tuple(CONFIG['align_box_color']), 2)
# 在對準方框上方顯示文字
cv2.putText(frame, "Target Zone", (ax1, ay1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, tuple(CONFIG['align_box_color']), 2)
# 增加幀計數
frame_count += 1
# 5. 如果無Tracker或需要定期重新偵測,執行偵測並初始化/更新Tracker
if tracker is None or frame_count % DETECT_INTERVAL == 0:
# 執行物件偵測
results = detector.model(frame)
dets = results.xyxy[0].cpu().numpy()
# 選擇最高信心的Capoo,並設置最低信心閾值
best = None
best_conf = 0
min_conf_threshold = 0.45 # 設置最低信心閾值,低於此值視為無效偵測
has_valid_detection = False # 預設為無有效偵測
for *box, conf, cls in dets:
if conf > best_conf:
best_conf = conf
best = box
# 只有當信心值超過閾值時才處理偵測結果
if best is not None and best_conf >= min_conf_threshold:
# 有有效偵測
has_valid_detection = True
x1, y1, x2, y2 = map(int, best)
# 在偵測結果上顯示信心值
cv2.putText(frame, f"Conf: {best_conf:.2f}", (x1, y1-30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 如果是新的追蹤器或重新偵測
if tracker is None:
# 初始化追蹤器 (OpenCV 4.11.0 版本)
# 只使用不需要額外模型文件的追蹤器
tracker_type = CONFIG.get('tracker_type', 'MIL')
if tracker_type == 'MIL':
tracker = cv2.TrackerMIL.create()
elif tracker_type == 'Nano':
tracker = cv2.TrackerNano.create()
else:
# 默認使用 MIL
print(f"警告:未知的追蹤器類型 {tracker_type},使用 MIL 替代")
tracker = cv2.TrackerMIL.create()
tracker.init(frame, (x1, y1, x2-x1, y2-y1))
print(f"初始化追蹤器:位置 ({x1},{y1},{x2},{y2}),信心值 {best_conf:.2f}")
else:
# 如果已有追蹤器但是定期重新偵測,則重新初始化
tracker.init(frame, (x1, y1, x2-x1, y2-y1))
print(f"更新追蹤器:位置 ({x1},{y1},{x2},{y2}),信心值 {best_conf:.2f}")
lost_frames = 0
# 6. 有Tracker時,更新位置
if tracker is not None:
ok, box = tracker.update(frame)
if ok:
x, y, w_t, h_t = map(int, box)
x1, y1, x2, y2 = x, y, x + w_t, y + h_t
# 繪製追蹤框(藍色)
cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 2)
# 在追蹤框上方顯示文字
cv2.putText(frame, "Capoo", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
# 檢查是否進入對準方框
if ax1 <= (x1+x2)//2 <= ax2 and ay1 <= (y1+y2)//2 <= ay2 and (time.time()-last_click) >= CONFIG['click_cooldown_seconds']:
click_center(info,
clicks=min(CONFIG['clicks'], CONFIG['max_clicks']),
interval=CONFIG['interval'])
last_click = time.time()
print(f"點擊:位置 ({(x1+x2)//2},{(y1+y2)//2}) 在目標區域內")
lost_frames = 0
else:
lost_frames += 1
# 若超過可承受失追幀數,重置Tracker
if lost_frames > MAX_LOST:
print(f"追蹤失敗超過 {MAX_LOST} 幀,重置追蹤器")
tracker = None
# 如果進行了偵測但沒有有效結果,且當前有追蹤器,則考慮重置追蹤器
elif tracker is not None and frame_count % DETECT_INTERVAL == 0 and not has_valid_detection:
# 如果進行了偵測但沒有找到有效的 Capoo,增加失敗計數
lost_frames += 2 # 加速失敗計數增長
print(f"無有效偵測,失敗計數增加至 {lost_frames}/{MAX_LOST}")
# 若超過可承受失追幀數,重置Tracker
if lost_frames > MAX_LOST:
print(f"無有效偵測且失敗計數超過 {MAX_LOST},重置追蹤器")
tracker = None
# 顯示追蹤器信息
tracker_info = f"Tracker: {CONFIG.get('tracker_type', 'MIL')}"
if tracker is None:
status = "Detecting..."
else:
status = "Tracking" if ok else f"Lost ({lost_frames}/{MAX_LOST})"
# 顯示最後一次偵測的信心值
conf_text = f"Last Conf: {best_conf:.2f}" if 'best_conf' in locals() and best_conf > 0 else "No Detection"
# 使用已經在偵測階段設置的 has_valid_detection 變數
# 顯示基本信息
cv2.putText(frame, tracker_info, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.putText(frame, f"Status: {status}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# 顯示信心值信息(根據是否有有效偵測顯示不同顏色)
conf_color = (0, 255, 0) if has_valid_detection else (0, 0, 255) # 綠色表示有效,紅色表示無效
cv2.putText(frame, conf_text, (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, conf_color, 2)
# 顯示閾值信息
cv2.putText(frame, f"Threshold: {min_conf_threshold}", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)
# 顯示畫面
cv2.imshow(CONFIG['stream_window'], frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
if __name__ == '__main__':
main()