import cv2 from sort import * import math import n...
創建於:2025年3月25日
使用 O3 Mini High 回答 Chat01
創建於:2025年3月25日
使用 O3 Mini High 回答 Chat01
import cv2
from sort import *
import math
import numpy as np
from ultralytics import YOLO
import cvzone
from time import sleep
讀取影片與模型
cap = cv2.VideoCapture('Video0.avi')
model = YOLO(r'D:\venv\OTMS1\best.pt')
讀取類別名稱
classnames = []
with open('classes.txt', 'r') as f:
classnames = f.read().splitlines()
初始化 SORT 追蹤器
tracker = Sort(max_age=20)
定義計數用的直線 (左上與右下座標)
line = [120, 1050, 920, 1050]
wafer_count = 0
counted_ids = [] # 用來記錄已經計數過的物件 ID
while cap.isOpened():
sleep(0.1) # 可依需求調整每幀間的延遲時間
ret, frame = cap.read()
if not ret:
cap = cv2.VideoCapture('Video0.avi')
#continue
result = model(frame, stream=True)
detections = [] # 存放每一幀的偵測結果 [x1, y1, x2, y2, conf]
for info in result:
boxes = info.boxes
for box in boxes:
# 取得邊界框座標與信心分數
x1, y1, x2, y2 = box.xyxy[0]
conf = box.conf[0]
classindex = int(box.cls[0])
textx1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) conf = float(conf) # 將每個偵測結果加入列表 (注意:這邊皆以 wafer 為例,若要僅追蹤特定類別可加入條件篩選) detections.append([x1, y1, x2, y2, conf]) # 繪製原始偵測框與標籤 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cvzone.putTextRect(frame, f'{classnames[classindex]} {math.ceil(conf * 100)}%', [x1 + 8, y1 - 12], thickness=2, scale=3) # 計算並標示偵測物件中心點(紅點) cx = int((x1 + x2) / 2) cy = int((y1 + y2) / 2) cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
if len(detections) > 0:
dets = np.array(detections)
else:
dets = np.empty((0, 5))
tracks = tracker.update(dets)
cv2.line(frame, (line[0], line[1]), (line[2], line[3]), (255, 0, 0), thickness=10)
for track in tracks:
x1, y1, x2, y2, track_id = track
x1, y1, x2, y2, track_id = int(x1), int(y1), int(x2), int(y2), int(track_id)
text# 繪製追蹤框與 ID cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 2) cvzone.putTextRect(frame, str(track_id), [x1, y1], scale=2, thickness=2) # 計算物件中心點 (此處雖然已在偵測時標示,但依需求可再次標示) cx = int((x1 + x2) / 2) cy = int((y1 + y2) / 2) cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1) # 判斷若中心點位於計數線附近,且尚未計數,則累計 wafer 數量 if (line[0] <= cx <= line[2]) and abs(cy - line[1]) < 10: if track_id not in counted_ids: wafer_count += 1 counted_ids.append(track_id)
cvzone.putTextRect(frame, f'Count: {wafer_count}', [50, 50], scale=3, thickness=2)
cv2.namedWindow('Wafer Detection', cv2.WINDOW_NORMAL)
cv2.resizeWindow('Wafer Detection', 500, 700)
cv2.imshow('Wafer Detection', frame)
cv2.waitKey(1)
cap.release()
cv2.destroyAllWindows()
上面程式沒有正確數出正確的wafer 的數量出來,請修改一下
看起來你的程式主要是透過 YOLO 偵測物件並用 SORT (Simple Online and Realtime Tracking) 來追蹤,然後在物件穿越一條計數線時來進行計數。從程式上看,有些部分可能會影響正確計數的結果。
我建議你檢查並修改以下幾個地方:
counted_ids
檢查邏輯:目前 counted_ids
用來防止重複計數,但你的條件只檢查了物件中心點是否穿過線,這樣可能會出現重複計數或計數錯誤。可以加入一個「方向」檢查,確保只有當物件從左側穿越到右側時才進行計數。
要避免計數重複,可以加入一個邏輯,確保物件的方向只有當它由左到右通過計數線時才增加。
目前是基於物件的中心點是否接近計數線,這樣會有些不精確。可以稍微調整條件,讓判斷更精確。
以下是修改後的程式碼:
pythonimport cv2 from sort import * import math import numpy as np from ultralytics import YOLO import cvzone from time import sleep # 讀取影片與模型 cap = cv2.VideoCapture('Video0.avi') model = YOLO(r'D:\venv\OTMS1\best.pt') # 讀取類別名稱 classnames = [] with open('classes.txt', 'r') as f: classnames = f.read().splitlines() # 初始化 SORT 追蹤器 tracker = Sort(max_age=20) # 定義計數用的直線 (左上與右下座標) line = [120, 1050, 920, 1050] wafer_count = 0 counted_ids = [] # 用來記錄已經計數過的物件 ID object_direction = {} # 用來記錄每個物件的方向 (左 -> 右) while cap.isOpened(): sleep(0.1) # 可依需求調整每幀間的延遲時間 ret, frame = cap.read() if not ret: cap = cv2.VideoCapture('Video0.avi') continue # 使用 YOLO 模型進行偵測 (stream 模式逐張處理) result = model(frame, stream=True) detections = [] # 存放每一幀的偵測結果 [x1, y1, x2, y2, conf] for info in result: boxes = info.boxes for box in boxes: # 取得邊界框座標與信心分數 x1, y1, x2, y2 = box.xyxy[0] conf = box.conf[0] classindex = int(box.cls[0]) x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) conf = float(conf) # 將每個偵測結果加入列表 (注意:這邊皆以 wafer 為例,若要僅追蹤特定類別可加入條件篩選) detections.append([x1, y1, x2, y2, conf]) # 繪製原始偵測框與標籤 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cvzone.putTextRect(frame, f'{classnames[classindex]} {math.ceil(conf * 100)}%', [x1 + 8, y1 - 12], thickness=2, scale=3) # 計算並標示偵測物件中心點(紅點) cx = int((x1 + x2) / 2) cy = int((y1 + y2) / 2) cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1) # 若有偵測結果則轉換為 numpy 陣列,否則傳入空陣列 if len(detections) > 0: dets = np.array(detections) else: dets = np.empty((0, 5)) # 更新追蹤器,取得追蹤物件 (每列 [x1, y1, x2, y2, track_id]) tracks = tracker.update(dets) # 在影像上劃出計數用的直線 cv2.line(frame, (line[0], line[1]), (line[2], line[3]), (255, 0, 0), thickness=10) # 遍歷每一個追蹤物件 for track in tracks: x1, y1, x2, y2, track_id = track x1, y1, x2, y2, track_id = int(x1), int(y1), int(x2), int(y2), int(track_id) # 繪製追蹤框與 ID cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 2) cvzone.putTextRect(frame, str(track_id), [x1, y1], scale=2, thickness=2) # 計算物件中心點 (此處雖然已在偵測時標示,但依需求可再次標示) cx = int((x1 + x2) / 2) cy = int((y1 + y2) / 2) cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1) # 判斷若中心點位於計數線附近,並且物件尚未計數,則累計 wafer 數量 if (line[0] <= cx <= line[2]) and abs(cy - line[1]) < 10: if track_id not in counted_ids: # 記錄物件的穿越方向 if track_id not in object_direction: object_direction[track_id] = 'left' # 檢查物件的方向 (從左到右) if object_direction[track_id] == 'left' and cx > (line[2] + line[0]) // 2: wafer_count += 1 counted_ids.append(track_id) object_direction[track_id] = 'right' # 物件已經穿越過線,改變方向 # 在影像上顯示計數數量 cvzone.putTextRect(frame, f'Count: {wafer_count}', [50, 50], scale=3, thickness=2) cv2.namedWindow('Wafer Detection', cv2.WINDOW_NORMAL) cv2.resizeWindow('Wafer Detection', 500, 700) cv2.imshow('Wafer Detection', frame) cv2.waitKey(1) cap.release() cv2.destroyAllWindows()
object_direction
字典:用來記錄每個物件的「方向」,確保物件從左到右穿越計數線才被計數。object_direction
中紀錄物件的狀態。import cv2
#from MBTS import * # 假設 MBTS.py 在同目錄或已安裝
try:
# 嘗試導入,如果 MBTS.py 不存在,可以先註解掉或提供 dummy class
from MBTS import MBTS
except ImportError:
print("警告: 無法導入 MBTS 模組。將使用 dummy tracker。")
# 提供一個簡單的 dummy tracker
class MBTS:
def init(self, max_age=25):
self.tracks = []
self.next_id = 0
print("使用 Dummy MBTS Tracker")
def update(self, dets):
# 這裡只是一個非常簡化的示例,實際追蹤邏輯複雜得多
# 這裡只是為了讓程式能運行,不會真的追蹤
output = []
for i, det in enumerate(dets):
# 返回格式 [x1, y1, x2, y2, track_id]
output.append([det[0], det[1], det[2], det[3], self.next_id + i])
self.next_id += len(dets) # 增加 ID
# 注意:這個 dummy tracker 沒有處理消失的物件、max_age 等
return np.array(output) if len(output) > 0 else np.empty((0, 5))
from MBTS import *
import math
import numpy as np
from ultralytics import YOLO
import cvzone
from time import sleep, time
import os
import subprocess
import sys
import ast # 新增:用於安全地解析字串列表
VIDEO_SOURCE = 'case1-1.mp4'
YOLO_PIN_MODEL_PATH = r'D:\venv\OTMS\best_LED.pt' # 主程式用的模型
CLASSES_FILE = 'classes.txt'
SAVE_DIR_TRACKED = "RED_circuit"
SAVE_DIR_GREEN = "Green_square"
PREDICTIONS_FILE = 'predictions.txt' # 副程式輸出的檔案名稱
if not os.path.exists(SAVE_DIR_TRACKED):
os.makedirs(SAVE_DIR_TRACKED)
if not os.path.exists(SAVE_DIR_GREEN):
os.makedirs(SAVE_DIR_GREEN)
try:
cap = cv2.VideoCapture(VIDEO_SOURCE)
if not cap.isOpened():
print(f"錯誤: 無法開啟影片 '{VIDEO_SOURCE}'")
sys.exit()
except Exception as e:
print(f"開啟影片時發生錯誤: {e}")
sys.exit()
try:
model = YOLO(YOLO_PIN_MODEL_PATH)
except Exception as e:
print(f"載入 YOLO 模型 '{YOLO_PIN_MODEL_PATH}' 失敗: {e}")
cap.release()
sys.exit()
try:
with open(CLASSES_FILE, 'r') as f:
classnames = f.read().splitlines()
except FileNotFoundError:
print(f"錯誤: 找不到類別檔案 '{CLASSES_FILE}'")
classnames = {0: 'object'} # 提供預設值以防萬一
print("使用預設類別名稱: {0: 'object'}")
except Exception as e:
print(f"讀取類別檔案時發生錯誤: {e}")
classnames = {0: 'object'}
print("使用預設類別名稱: {0: 'object'}")
tracker = MBTS(max_age=25)
line = [1200, 600, 1400, 600] # 計數線座標
wafer_count = 0
counted_ids = set() # 使用 set 提高查找效率
save_counter_green = 1
tracked_counter = 0
last_saved_tracked_frame_path = None # 記錄最後儲存的追蹤幀路徑
print("開始處理影片...")
try:
while cap.isOpened():
sleep(0) # 可選的延遲
ret, frame = cap.read()
if not ret:
print("影片處理完成或讀取影格失敗。")
break
textheight, width, _ = frame.shape rect_w, rect_h = 800, 100 shift_x, shift_y = 250, 60 center_x, center_y = width // 2, height // 2 top_left = (center_x - rect_w // 2 - shift_x, center_y - rect_h // 2 - shift_y + 80 ) bottom_right = (center_x + rect_w // 2 - shift_x, center_y + rect_h // 2 - shift_y + 80 ) # 繪製綠色 ROI 框 cv2.rectangle(frame, top_left, bottom_right, (0, 255, 0), thickness=2) # YOLO 偵測 try: results = model(frame, stream=True, verbose=False) # verbose=False 減少控制台輸出 detections = [] detected_class_index = 0 # 預設值 highest_conf = 0.0 # 預設值 for info in results: boxes = info.boxes for box in boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) conf = float(box.conf[0]) classindex = int(box.cls[0]) detections.append([x1, y1, x2, y2, conf]) # 記錄信心度最高的類別索引,用於顯示文字 if conf > highest_conf: highest_conf = conf detected_class_index = classindex except Exception as e: print(f"YOLO 偵測時發生錯誤: {e}") detections = [] highest_conf = 0.0 detected_class_index = 0 # 格式化偵測結果給追蹤器 if detections: dets = np.array(detections) else: dets = np.empty((0, 5)) # 更新追蹤器 try: tracks = tracker.update(dets) except Exception as e: print(f"追蹤器更新時發生錯誤: {e}") tracks = np.empty((0, 5)) # 繪製計數線 cv2.line(frame, (line[0], line[1]), (line[2], line[3]), (0, 255, 0), thickness=8) # 處理並繪製追蹤物件 frame_has_tracks = False # 標記當前幀是否有追蹤物件 if tracks.size > 0: frame_has_tracks = True for track in tracks: x1, y1, x2, y2, track_id = map(int, track) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 255), 2) # 追蹤框 (黃色) # 顯示類別和信賴度 (使用偵測到的最高信賴度物件的類別) # 注意:這裡假設追蹤器跟蹤的是主要物件,顯示的是偵測階段的類別資訊 display_classname = classnames[detected_class_index] if detected_class_index < len(classnames) else f"Class_{detected_class_index}" cvzone.putTextRect(frame, f'{display_classname} {math.ceil(highest_conf * 100)}%', [x1 + 8, y1 - 12], thickness=3, scale=2, colorT=(0, 0, 0), colorR=(255, 127, 0)) # 橘色背景 # 顯示 Track ID cvzone.putTextRect(frame, str(track_id), [x1, y1], scale=2, thickness=2, colorR=(0, 255, 255), colorT=(255, 0, 0)) # 黃色背景, 藍色文字 # 繪製中心點 cx = (x1 + x2) // 2 cy = (y1 + y2) // 2 cv2.circle(frame, (cx, cy), 10, (0, 255, 255), -1) # 黃色實心圓 # --- 計數邏輯 --- # 檢查中心點是否跨越線 (假設從下往上跨越) 且 ID 未被計數 if cy < line[1] and track_id not in counted_ids: counted_ids.add(track_id) # 添加到集合 wafer_count += 1 print(f"物件 ID {track_id} 已計數,總數: {wafer_count}") # 擷取綠色方框影像並儲存 try: # 確保座標在範圍內 roi_y1 = max(0, top_left[1]) roi_y2 = min(height, bottom_right[1]) roi_x1 = max(0, top_left[0]) roi_x2 = min(width, bottom_right[0]) if roi_y1 < roi_y2 and roi_x1 < roi_x2: # 確保 ROI 有效 green_crop = frame[roi_y1:roi_y2, roi_x1:roi_x2] green_save_path = os.path.join(SAVE_DIR_GREEN, f"{save_counter_green:02d}.jpg") cv2.imwrite(green_save_path, green_crop) print(f"已儲存綠色方框影像: {green_save_path}") save_counter_green += 1 else: print(f"警告: 計算出的綠色 ROI 無效 ({top_left}, {bottom_right})") except Exception as e: print(f"儲存綠色方框影像時發生錯誤: {e}") # 顯示總計數 count_text_pos = (800, 50) cvzone.putTextRect(frame, f'Count: {wafer_count}', count_text_pos, scale=2, thickness=3, colorT=(0, 0, 0), colorR=(255, 127, 0)) # 橘色背景 # 儲存包含追蹤結果的畫面 (只有當畫面中有追蹤物件時才存) if frame_has_tracks: try: tracked_save_path = os.path.join(SAVE_DIR_TRACKED, f"tracked_{tracked_counter:04d}.jpg") # 使用4位數補零 cv2.imwrite(tracked_save_path, frame) last_saved_tracked_frame_path = tracked_save_path # 更新最後儲存的路徑 tracked_counter += 1 except Exception as e: print(f"儲存追蹤畫面時發生錯誤: {e}") # 顯示即時畫面 try: # 調整視窗大小 cv2.namedWindow('Wafer Detection', cv2.WINDOW_NORMAL) cv2.resizeWindow('Wafer Detection', 1200, 500) # 可以取消註解如果需要固定大小 cv2.imshow('Wafer Detection', frame) except Exception as e: print(f"顯示畫面時發生錯誤: {e}") # 按 'q' 退出 if cv2.waitKey(1) & 0xFF == ord('q'): print("使用者按下 'q',停止處理。") break
finally:
# --- 清理資源 ---
cap.release()
cv2.destroyAllWindows()
print("影像處理迴圈結束,資源已釋放。")
'''
# --- 執行副程式 ---
print("\n開始執行 wafer_seg.py...")
all_predictions = None # 初始化預測結果變數
try:
python_path = sys.executable
wafer_seg_path = os.path.join(os.path.dirname(file), 'wafer_seg.py')
textif not os.path.exists(wafer_seg_path): raise FileNotFoundError(f"找不到副程式: {wafer_seg_path}") # 執行副程式,等待它完成 subprocess.run([python_path, wafer_seg_path], check=True, capture_output=True, text=True) # capture_output 捕捉輸出, text=True 使輸出為文字 print("wafer_seg.py 執行完畢。") # --- 讀取副程式產生的預測結果 --- print(f"嘗試讀取預測結果檔案: {PREDICTIONS_FILE}") if os.path.exists(PREDICTIONS_FILE): try: with open(PREDICTIONS_FILE, 'r') as f: content = f.read() # 使用 ast.literal_eval 安全地解析字串為 Python 列表 all_predictions = ast.literal_eval(content) print(f"成功讀取並解析預測結果: {all_predictions}") except FileNotFoundError: print(f"錯誤: 找不到預測檔案 '{PREDICTIONS_FILE}'。") except SyntaxError as e: print(f"錯誤: 解析預測檔案 '{PREDICTIONS_FILE}' 失敗,格式可能不正確: {e}") print(f"檔案內容:\n{content}") # 打印內容以供調試 except Exception as e: print(f"讀取或解析預測檔案時發生未預期錯誤: {e}") else: print(f"警告: 副程式執行後未找到預測檔案 '{PREDICTIONS_FILE}'。") except FileNotFoundError as e: print(f"錯誤: {e}") except subprocess.CalledProcessError as e: print(f"執行 wafer_seg.py 時發生錯誤:") print(f"返回碼: {e.returncode}") print(f"標準輸出:\n{e.stdout}") print(f"標準錯誤:\n{e.stderr}") except Exception as e: print(f"執行副程式或讀取結果時發生未預期的錯誤: {e}") # --- 顯示最終結果 (在最後一張追蹤圖上) --- if last_saved_tracked_frame_path and os.path.exists(last_saved_tracked_frame_path): print(f"\n載入最後儲存的追蹤畫面: {last_saved_tracked_frame_path}") final_frame = cv2.imread(last_saved_tracked_frame_path) if final_frame is not None: # 獲取原計數文字的位置和大小信息 (近似值或重新計算) count_text = f'Count: {wafer_count}' #(text_width, text_height), baseline = cv2.getTextSize(count_text, cvzone.FONT_HERSHEY_PLAIN, 4, 5) # 獲取文字大小 (text_width, text_height), baseline = cv2.getTextSize(count_text, cv2.FONT_HERSHEY_PLAIN, 4, 5) # 獲取文字大小 # count_text_pos_final = (1100, 50) # 和之前的位置相同 count_text_rect_height = text_height + baseline + 10 # 近似計算putTextRect的高度 (需要微調) # 設定預測結果文字的位置 (計數文字下方) prediction_text_y = count_text_pos[1] + count_text_rect_height + 20 # 在計數下方留點間隔 # 準備要顯示的預測字串 if all_predictions is not None: # 將列表轉換為字串,方便顯示 prediction_str = f"Result:{str(all_predictions)}" # 使用 putTextRect 繪製,自動處理背景 # 注意:如果字串太長,可能需要換行或縮小字體 cvzone.putTextRect(final_frame, prediction_str, #(count_text_pos[0], prediction_text_y), # X 座標與計數對齊,Y 座標在下方 (10, prediction_text_y), # <--- 將 X 座標改為 50 scale=3, thickness=3, colorT=(255, 0, 0), colorR=(200, 200, 200)) # 紅字灰底 else: # 如果沒有讀取到預測結果 cvzone.putTextRect(final_frame, "Result: N/A", #(count_text_pos[0], prediction_text_y), (10, prediction_text_y), # <--- 將 X 座標改為 50 scale=3, thickness=3, colorT=(255, 0, 0), colorR=(200, 200, 200)) # 顯示帶有最終預測結果的畫面 cv2.namedWindow('Final Result with Predictions', cv2.WINDOW_NORMAL) cv2.resizeWindow('Final Result with Predictions', 1200, 500) cv2.imshow('Final Result with Predictions', final_frame) print("\n顯示最終結果畫面。按任意鍵關閉。") cv2.waitKey(0) # 等待按鍵後關閉 cv2.destroyAllWindows() else: print(f"錯誤: 無法讀取最後儲存的追蹤畫面 '{last_saved_tracked_frame_path}'") elif all_predictions is not None: # 如果沒有儲存的追蹤畫面,但有預測結果,只打印結果 print("\n未找到最後的追蹤畫面,僅打印預測結果:") print(all_predictions) else: print("\n沒有儲存追蹤畫面,也未能讀取預測結果。") print("\n主程式執行完畢。")
'''
請改寫上面的程式,讓物件追蹤的判斷方式更為精確
下面提供一個示範,說明如何在原程式基礎上,儲存「每個追蹤 ID 先前的中心點位置」,並透過 「由下往上」或「由上往下」的跨線判斷,讓計數更加精確。程式中主要改動如下:
prev_centers
字典:用來儲存每個 track_id
上一幀的中心點座標。cy
是否跨越計數線 line[1]
,由 (大於 → 小於) 或 (小於 → 大於) 來決定是否計數。例如:
cy > line[1]
且本幀 cy < line[1]
,才增加計數。cy < line[1]
且本幀 cy > line[1]
,才增加計數。track_id
已經被計數過,就不再重複計數。請根據實際需求(下往上或上往下)選擇判斷條件。如果要用「左往右」或「右往左」同理,只要改成比較 cx
與直線的 X 座標即可。
pythonimport cv2 # from MBTS import * # 假設 MBTS.py 在同目錄或已安裝 try: from MBTS import MBTS except ImportError: print("警告: 無法導入 MBTS 模組。將使用 dummy tracker。") class MBTS: def __init__(self, max_age=25): self.tracks = [] self.next_id = 0 print("使用 Dummy MBTS Tracker") def update(self, dets): output = [] for i, det in enumerate(dets): # [x1, y1, x2, y2, track_id] output.append([det[0], det[1], det[2], det[3], self.next_id + i]) self.next_id += len(dets) return np.array(output) if len(output) > 0 else np.empty((0, 5)) import math import numpy as np from ultralytics import YOLO import cvzone from time import sleep, time import os import subprocess import sys import ast # --- 設定 --- VIDEO_SOURCE = 'case1-1.mp4' YOLO_PIN_MODEL_PATH = r'D:\venv\OTMS\best_LED.pt' # 主程式用的模型 CLASSES_FILE = 'classes.txt' SAVE_DIR_TRACKED = "RED_circuit" SAVE_DIR_GREEN = "Green_square" PREDICTIONS_FILE = 'predictions.txt' # 副程式輸出的檔案名稱 # --- 建立資料夾 --- if not os.path.exists(SAVE_DIR_TRACKED): os.makedirs(SAVE_DIR_TRACKED) if not os.path.exists(SAVE_DIR_GREEN): os.makedirs(SAVE_DIR_GREEN) # --- 載入資源 --- try: cap = cv2.VideoCapture(VIDEO_SOURCE) if not cap.isOpened(): print(f"錯誤: 無法開啟影片 '{VIDEO_SOURCE}'") sys.exit() except Exception as e: print(f"開啟影片時發生錯誤: {e}") sys.exit() try: model = YOLO(YOLO_PIN_MODEL_PATH) except Exception as e: print(f"載入 YOLO 模型 '{YOLO_PIN_MODEL_PATH}' 失敗: {e}") cap.release() sys.exit() try: with open(CLASSES_FILE, 'r') as f: classnames = f.read().splitlines() except FileNotFoundError: print(f"錯誤: 找不到類別檔案 '{CLASSES_FILE}'") classnames = {0: 'object'} # 提供預設值以防萬一 print("使用預設類別名稱: {0: 'object'}") except Exception as e: print(f"讀取類別檔案時發生錯誤: {e}") classnames = {0: 'object'} print("使用預設類別名稱: {0: 'object'}") # --- 初始化 --- tracker = MBTS(max_age=25) line = [1200, 600, 1400, 600] # 計數線座標 (示範:水平線) wafer_count = 0 counted_ids = set() # 已經計數過的追蹤 ID prev_centers = {} # 記錄每個物件上一幀中心點,格式: {track_id: (prev_cx, prev_cy)} save_counter_green = 1 tracked_counter = 0 last_saved_tracked_frame_path = None # 記錄最後儲存的追蹤幀路徑 print("開始處理影片...") try: while cap.isOpened(): sleep(0) # 可選的延遲 ret, frame = cap.read() if not ret: print("影片處理完成或讀取影格失敗。") break height, width, _ = frame.shape rect_w, rect_h = 800, 100 shift_x, shift_y = 250, 60 center_x, center_y = width // 2, height // 2 top_left = (center_x - rect_w // 2 - shift_x, center_y - rect_h // 2 - shift_y + 80 ) bottom_right = (center_x + rect_w // 2 - shift_x, center_y + rect_h // 2 - shift_y + 80 ) # 繪製綠色 ROI 框 cv2.rectangle(frame, top_left, bottom_right, (0, 255, 0), thickness=2) # --- YOLO 偵測 --- try: results = model(frame, stream=True, verbose=False) detections = [] detected_class_index = 0 highest_conf = 0.0 for info in results: boxes = info.boxes for box in boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) conf = float(box.conf[0]) classindex = int(box.cls[0]) detections.append([x1, y1, x2, y2, conf]) # 記錄信心度最高的類別索引,用於顯示文字 if conf > highest_conf: highest_conf = conf detected_class_index = classindex except Exception as e: print(f"YOLO 偵測時發生錯誤: {e}") detections = [] highest_conf = 0.0 detected_class_index = 0 # --- 格式化偵測結果給追蹤器 --- if detections: dets = np.array(detections) else: dets = np.empty((0, 5)) # --- 更新追蹤器 --- try: tracks = tracker.update(dets) except Exception as e: print(f"追蹤器更新時發生錯誤: {e}") tracks = np.empty((0, 5)) # --- 繪製計數線 --- cv2.line(frame, (line[0], line[1]), (line[2], line[3]), (0, 255, 0), thickness=8) # 這裡示範「由下往上」跨線計數: # 前一幀:cy_prev > line[1] # 本 幀:cy_now < line[1] frame_has_tracks = False if tracks.size > 0: frame_has_tracks = True for track in tracks: x1, y1, x2, y2, track_id = map(int, track) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 255), 2) # 追蹤框 (黃色) # 顯示類別和信賴度 (使用偵測到的最高信賴度物件的類別) display_classname = classnames[detected_class_index] if detected_class_index < len(classnames) else f"Class_{detected_class_index}" cvzone.putTextRect(frame, f'{display_classname} {math.ceil(highest_conf * 100)}%', [x1 + 8, y1 - 12], thickness=3, scale=2, colorT=(0, 0, 0), colorR=(255, 127, 0)) # 顯示 Track ID cvzone.putTextRect(frame, str(track_id), [x1, y1], scale=2, thickness=2, colorR=(0, 255, 255), colorT=(255, 0, 0)) # 計算中心點 cx = (x1 + x2) // 2 cy = (y1 + y2) // 2 cv2.circle(frame, (cx, cy), 10, (0, 255, 255), -1) # 黃色實心圓 # ====== 物件「跨線」精確判斷邏輯 ====== if track_id not in prev_centers: # 如果第一次看到這個追蹤ID,先記錄它當前位置即可 prev_centers[track_id] = (cx, cy) else: # 取得上一幀的中心點 prev_cx, prev_cy = prev_centers[track_id] prev_centers[track_id] = (cx, cy) # 更新最新中心點 # 判斷是否已計數 if track_id not in counted_ids: # 以「下 → 上」為例 (若要「上 → 下」,反過來即可) crossed_down_to_up = (prev_cy > line[1]) and (cy < line[1]) if crossed_down_to_up: # 執行計數 counted_ids.add(track_id) wafer_count += 1 print(f"物件 ID {track_id} 已計數,總數: {wafer_count}") # 擷取 ROI 與儲存 try: roi_y1 = max(0, top_left[1]) roi_y2 = min(height, bottom_right[1]) roi_x1 = max(0, top_left[0]) roi_x2 = min(width, bottom_right[0]) if roi_y1 < roi_y2 and roi_x1 < roi_x2: green_crop = frame[roi_y1:roi_y2, roi_x1:roi_x2] green_save_path = os.path.join(SAVE_DIR_GREEN, f"{save_counter_green:02d}.jpg") cv2.imwrite(green_save_path, green_crop) print(f"已儲存綠色方框影像: {green_save_path}") save_counter_green += 1 else: print(f"警告: 計算出的綠色 ROI 無效 ({top_left}, {bottom_right})") except Exception as e: print(f"儲存綠色方框影像時發生錯誤: {e}") # 顯示總計數 count_text_pos = (800, 50) cvzone.putTextRect(frame, f'Count: {wafer_count}', count_text_pos, scale=2, thickness=3, colorT=(0, 0, 0), colorR=(255, 127, 0)) # 如果該幀有出現追蹤物件,就存檔 if frame_has_tracks: try: tracked_save_path = os.path.join(SAVE_DIR_TRACKED, f"tracked_{tracked_counter:04d}.jpg") cv2.imwrite(tracked_save_path, frame) last_saved_tracked_frame_path = tracked_save_path tracked_counter += 1 except Exception as e: print(f"儲存追蹤畫面時發生錯誤: {e}") # 顯示即時畫面 try: cv2.namedWindow('Wafer Detection', cv2.WINDOW_NORMAL) cv2.resizeWindow('Wafer Detection', 1200, 500) cv2.imshow('Wafer Detection', frame) except Exception as e: print(f"顯示畫面時發生錯誤: {e}") # 按 'q' 退出 if cv2.waitKey(1) & 0xFF == ord('q'): print("使用者按下 'q',停止處理。") break finally: cap.release() cv2.destroyAllWindows() print("影像處理迴圈結束,資源已釋放。") # 如果要接著執行 wafer_seg.py 等其他流程,可保留你原先的副程式執行邏輯 # ...
prev_centers
字典
track_id
,先記錄它的中心點。track_id
,就可以拿到它的「上一幀中心點」,再和當前幀做比較。「跨線」的判斷方式
若要偵測「上往下」,只要把判斷式換成:pythoncrossed_down_to_up = (prev_cy > line[1]) and (cy < line[1])
或同理改用pythoncrossed_up_to_down = (prev_cy < line[1]) and (cy > line[1])
cx
做左右的判斷。避免重複計數
track_id
計數過,就把它丟進 counted_ids
;下次就不會再重複增加。利用上述方法,就能大幅減少因為雜訊或偵測框閃爍造成的誤判,提高計數的穩定度。若需更複雜的情境(例如同時要偵測上下雙線或左右方向不同的計數),可以在程式中做更多的「方向」或「當前狀態」判斷。