You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:20:02