import cv2 from sort import * import math import n...

創建於: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

使用 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])

text
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)

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 的數量出來,請修改一下

思考

I'm trying to glean the best method to respond to the user's request, which includes importing various libraries such as cv2, sort, math, numpy, ultralytics, and cvz.

Assessing the request

OK, let me see. I’m mapping out the request to import cv2, sort, math, numpy, ultralytics, and cvz. It seems organized, and I’m ready to discuss the next steps.

Charting the course

I’m evaluating how to address the request for imports from cv2, sort, math, numpy, ultralytics, and cvz, considering their utility and integration.

Assessing library needs

Evaluating the user's imports: cv2, sort, math, numpy, ultralytics, and cvz. These tools are necessary for practical coding, aligning well with the context.

Determining the prerequisites

I’m mapping out the necessary imports: cv2, sort, math, numpy, ultralytics, and cvz, to ensure comprehensive coverage for practical coding and integration.

Pulling together

I'm working through the imports of cv2, sort, math, numpy, ultralytics, and cvz, each serving a distinct function in the coding process, ensuring comprehensive coverage.

Evaluating import needs

I’m assessing cv2, sort, math, numpy, ultralytics, and cvz imports. Each brings unique functionalities, crucial for advancing the coding process and ensuring comprehensive coverage.

Acknowledging the request

I’m noting the user's request to include imports for cv2, sort, math, numpy, ultralytics, and cvz, each serving a distinct function to enhance coding functionality and integration.

Reasoned for 8 seconds

回答

看起來你的程式主要是透過 YOLO 偵測物件並用 SORT (Simple Online and Realtime Tracking) 來追蹤,然後在物件穿越一條計數線時來進行計數。從程式上看,有些部分可能會影響正確計數的結果。

我建議你檢查並修改以下幾個地方:

1. 修正 counted_ids 檢查邏輯:

目前 counted_ids 用來防止重複計數,但你的條件只檢查了物件中心點是否穿過線,這樣可能會出現重複計數或計數錯誤。可以加入一個「方向」檢查,確保只有當物件從左側穿越到右側時才進行計數。

2. 修改計數邏輯:

要避免計數重複,可以加入一個邏輯,確保物件的方向只有當它由左到右通過計數線時才增加。

3. 更細緻的條件判斷:

目前是基於物件的中心點是否接近計數線,這樣會有些不精確。可以稍微調整條件,讓判斷更精確。

以下是修改後的程式碼:

python
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 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()

主要變更:

  1. 新增 object_direction 字典:用來記錄每個物件的「方向」,確保物件從左到右穿越計數線才被計數。
  2. 改進了計數邏輯:現在只有當物件穿越計數線並且方向正確時才會被計數。
  3. 處理重複計數:已經計數過的物件會被排除,並且在 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

text
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) # 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')

text
if 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主程式執行完畢。")

'''
請改寫上面的程式,讓物件追蹤的判斷方式更為精確

思考

Mapping object movements

I’m piecing together strategies to refine object tracking by considering historical positions, ensuring accurate crossing detections for precise up/down or left/right movements.

Reasoned about object tracking precision for 7 seconds

回答

下面提供一個示範,說明如何在原程式基礎上,儲存「每個追蹤 ID 先前的中心點位置」,並透過 「由下往上」或「由上往下」的跨線判斷,讓計數更加精確。程式中主要改動如下:

  1. 新增 prev_centers 字典:用來儲存每個 track_id 上一幀的中心點座標。
  2. 過線判斷:判斷前後兩幀的 cy 是否跨越計數線 line[1],由 (大於 → 小於) 或 (小於 → 大於) 來決定是否計數。例如:
    • 如果想由「下往上」計數:上一幀 cy > line[1] 且本幀 cy < line[1],才增加計數。
    • 如果想由「上往下」計數:上一幀 cy < line[1] 且本幀 cy > line[1],才增加計數。
  3. 避免重複計數:當同一個 track_id 已經被計數過,就不再重複計數。

請根據實際需求(下往上或上往下)選擇判斷條件。如果要用「左往右」或「右往左」同理,只要改成比較 cx 與直線的 X 座標即可。


示範改寫程式

python
import 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: # 以「下 &rarr; 上」為例 (若要「上 &rarr; 下」,反過來即可) 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 等其他流程,可保留你原先的副程式執行邏輯 # ...

核心修改重點

  1. prev_centers 字典

    • 在每次進行追蹤更新後,對於出現的新 track_id,先記錄它的中心點。
    • 如果是已出現過的 track_id,就可以拿到它的「上一幀中心點」,再和當前幀做比較。
  2. 「跨線」的判斷方式

    • 示範程式中使用「下往上」:
      python
      crossed_down_to_up = (prev_cy > line[1]) and (cy < line[1])
      若要偵測「上往下」,只要把判斷式換成:
      python
      crossed_up_to_down = (prev_cy < line[1]) and (cy > line[1])
      或同理改用 cx 做左右的判斷。
  3. 避免重複計數

    • 一旦 track_id 計數過,就把它丟進 counted_ids;下次就不會再重複增加。

利用上述方法,就能大幅減少因為雜訊或偵測框閃爍造成的誤判,提高計數的穩定度。若需更複雜的情境(例如同時要偵測上下雙線或左右方向不同的計數),可以在程式中做更多的「方向」或「當前狀態」判斷。

分享這個問答