如何用OpenCV为游动鱼类保持固定跟踪标签?
解决鱼类跟踪中标签不固定的问题
老兄,你的中心点匹配思路完全可行!这其实是目标跟踪领域里「最近邻匹配」的简化思路,非常适合你这种鱼缸里的低速运动场景——鱼的游动速度不会太快,相邻帧里同一条鱼的中心点距离肯定是最近的,用这个逻辑来关联跨帧目标完全没问题。
先说说你现有代码的核心问题
- 没有跨帧状态记忆:当前代码每帧都从1开始给轮廓编号,完全不关联上一帧的目标,这就导致每一帧的编号都是独立的,必然乱跳
- 轮廓过滤太粗糙:只靠
w>50和h>50过滤,很容易把鱼缸里的气泡、光影变化误判成目标,干扰后续的跟踪匹配 - 背景处理冗余:同时用了移动平均和MOG2背景减法,两者都是用来提取前景运动目标的,功能重叠反而会引入额外噪声,建议二选一(MOG2的鲁棒性更强,更适合动态背景)
基于你的中心点思路的改进实现
我们可以在循环外初始化一个列表,用来存储上一帧每个目标的「ID+中心点坐标」,然后每帧计算当前目标的中心点,通过计算距离找到上一帧中最近的匹配目标,给它分配相同的ID;如果是新出现的目标,就分配新ID。
下面是修改后的完整代码:
import cv2 import numpy as np device = cv2.VideoCapture(0) flag, frame = device.read() background = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=40, detectShadows=False) font = cv2.FONT_HERSHEY_SIMPLEX kernelOpen = np.ones((5,5)) kernelClose = np.ones((20,20)) # 初始化存储上一帧目标的列表:每个元素是 (目标ID, 中心点x, 中心点y) prev_targets = [] next_id = 1 # 新目标的起始ID while True: flag, frame = device.read() if not flag: break # 背景减法提取前景 gaussion = background.apply(frame) # 形态学操作去噪 gaussion = cv2.morphologyEx(gaussion, cv2.MORPH_OPEN, kernelOpen) gaussion = cv2.morphologyEx(gaussion, cv2.MORPH_CLOSE, kernelClose) # 查找轮廓 _, conts, _ = cv2.findContours(gaussion.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) current_targets = [] if len(conts) == 0: cv2.putText(frame,"No moving objects found!",(50,200), font, 1,(255,255,255),2,cv2.LINE_AA) prev_targets = [] # 没有目标时清空上一帧记录 else: # 处理当前帧的每个轮廓 for cnt in conts: x,y,w,h = cv2.boundingRect(cnt) if (w > 50) and (h > 50): # 计算中心点 M = cv2.moments(cnt) if M['m00'] != 0: cx = int(M['m10']/M['m00']) cy = int(M['m01']/M['m00']) current_targets.append( (cx, cy, x, y, w, h) ) # 匹配当前帧和上一帧的目标 matched_ids = [] for curr in current_targets: cx_curr, cy_curr, x, y, w, h = curr min_dist = float('inf') matched_id = None # 找距离最近的上一帧目标 for prev in prev_targets: prev_id, cx_prev, cy_prev = prev dist = np.sqrt( (cx_curr - cx_prev)**2 + (cy_curr - cy_prev)**2 ) if dist < min_dist and prev_id not in matched_ids: min_dist = dist matched_id = prev_id # 如果找到匹配的ID,否则分配新ID if matched_id is not None and min_dist < 100: # 100是距离阈值,可根据鱼缸大小调整 target_id = matched_id matched_ids.append(target_id) else: target_id = next_id next_id += 1 # 绘制标注 cv2.rectangle(frame,(x,y),(x+w,y+h),(0,0,255), 2) cv2.circle(frame,(cx_curr,cy_curr), 2, (0,255,0), -1) cv2.putText(frame,f"{target_id} object",(x,y+h), font, 1,(255,255,255),2,cv2.LINE_AA) # 更新上一帧目标记录 prev_targets = [] for idx, curr in enumerate(current_targets): cx, cy, _, _, _, _ = curr if idx < len(matched_ids): prev_targets.append( (matched_ids[idx], cx, cy) ) else: prev_targets.append( (next_id - len(current_targets) + idx + 1, cx, cy) ) cv2.imshow("Gaussian",gaussion) cv2.imshow("Track",frame) if cv2.waitKey(1) == 27: break device.release() cv2.destroyAllWindows()
更稳健的进阶方案:用OpenCV自带的多目标跟踪器
如果你的鱼缸里鱼的数量较多,或者出现鱼交叉、短暂遮挡的情况,手动写的匹配逻辑可能会失效。这时候可以用OpenCV自带的MultiTracker,它集成了成熟的跟踪算法(比如CSRT、KCF),稳定性更强。
示例代码如下:
import cv2 import numpy as np device = cv2.VideoCapture(0) flag, frame = device.read() background = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=40, detectShadows=False) font = cv2.FONT_HERSHEY_SIMPLEX kernelOpen = np.ones((5,5)) kernelClose = np.ones((20,20)) # 初始化多目标跟踪器 multi_tracker = cv2.MultiTracker_create() tracker_type = "CSRT" # CSRT适合高精度,KCF适合高速 next_id = 1 target_ids = [] def create_tracker(tracker_type): if tracker_type == 'CSRT': return cv2.TrackerCSRT_create() elif tracker_type == 'KCF': return cv2.TrackerKCF_create() else: raise ValueError("Invalid tracker type") while True: flag, frame = device.read() if not flag: break # 更新跟踪器 success, bboxes = multi_tracker.update(frame) # 绘制跟踪结果 for i, bbox in enumerate(bboxes): x, y, w, h = [int(v) for v in bbox] cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 2) cv2.putText(frame,f"{target_ids[i]} object",(x,y+h), font, 1,(255,255,255),2,cv2.LINE_AA) # 每隔30帧重新检测目标(处理新出现的鱼或跟踪丢失的情况) if cv2.getTickCount() % 30 == 0: gaussion = background.apply(frame) gaussion = cv2.morphologyEx(gaussion, cv2.MORPH_OPEN, kernelOpen) gaussion = cv2.morphologyEx(gaussion, cv2.MORPH_CLOSE, kernelClose) _, conts, _ = cv2.findContours(gaussion.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 过滤已有跟踪的目标 existing_bboxes = [bbox for bbox in bboxes] for cnt in conts: x,y,w,h = cv2.boundingRect(cnt) if (w > 50) and (h > 50): # 检查这个轮廓是否已经被跟踪 is_new = True for bbox in existing_bboxes: x_ex, y_ex, w_ex, h_ex = [int(v) for v in bbox] # 计算IOU判断是否是同一个目标 inter_x1 = max(x, x_ex) inter_y1 = max(y, y_ex) inter_x2 = min(x+w, x_ex+w_ex) inter_y2 = min(y+h, y_ex+h_ex) inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1) union_area = w*h + w_ex*h_ex - inter_area iou = inter_area / union_area if union_area !=0 else 0 if iou > 0.5: is_new = False break if is_new: # 添加新跟踪器 tracker = create_tracker(tracker_type) multi_tracker.add(tracker, frame, (x,y,w,h)) target_ids.append(next_id) next_id += 1 cv2.imshow("Track",frame) if cv2.waitKey(1) == 27: break device.release() cv2.destroyAllWindows()
内容的提问来源于stack exchange,提问作者iuhettiarachchi
相关产品推荐
相关产品推荐

