OpenCV之目标追踪

1.目标追踪

目标追踪(Object Tracking)是指在视频序列中持续追踪目标的位置,这是计算机视觉中的核心难点,其核心挑战包括遮挡、光照变化和快速运动。在视频分析、自动驾驶、智能监控、人机交互等领域中应用广泛。

1.按任务类型分类

  1. 单目标追踪(SOT, Single Object Tracking):只追踪一个初始帧中给定的目标,典型方法如:
    1. Siamese-based Tracking(孪生网络)
    2. Transformer-based Tracking
  2. 多目标追踪(MOT, Multi-Object Tracking):同时追踪多个目标,通常包括目标检测与数据关联两个阶段,典型方法如:
    1. 基于检测的跟踪(Tracking-by-Detection)
    2. 端到端方法
  3. 多摄像头追踪(MTMC, Multi-Camera Multi-Object Tracking):跨视角数据关联,更复杂的特征匹配,常结合ReID、时空建模、图神经网络等

2.传统目标追踪算法

除了基于深度学习的追踪算法,还有依赖传统的机器学习的目标追踪算法,它们适用于某些轻量级、实时或资源受限的场景(如嵌入式设备)。

OpenCV中常用的目标追踪算法:

  1. Boosting Tracker
    原理:使用在线Boosting分类器,从背景中区分出目标
    优点:适应性强,适合光照变化的情况
    缺点:对遮挡敏感,易漂移
  2. MIL (Multiple Instance Learning) Tracker
    原理:目标不是单个样本表示,而是多个候选区域的集合;只要集合中包含目标就算正例
    优点:缓解标签不确定问题,增强鲁棒性
    缺点:对遮挡还是不太稳,精度一般
  3. KCF (Kernelized Correlation Filter)
    原理:使用核技巧加速的相关滤波器,训练和测试都在频域完成
    优点:非常快,速度极高(可达上百帧/秒),适合实时追踪
    缺点:对尺度变化、遮挡、快速运动敏感
  4. TLD (Tracking-Learning-Detection)
    原理:将追踪、检测、学习分成三个模块协同工作。追踪失败时,检测器会重新识别目标
    优点:鲁棒性强,能处理遮挡后的恢复
    缺点:复杂度高,速度慢,容易累积错误
  5. MedianFlow
    原理:跟踪局部块(点),计算前后帧之间的点的光流;中位数流用于稳定估计
    优点:平滑、稳定,适合小运动
    缺点:对快速运动、遮挡很不鲁棒;会直接失败而不是漂移
  6. MOSSE (Minimum Output Sum of Squared Error Filter)
    原理:训练一个滤波器,使得卷积后的响应图和期望响应(高斯峰)最接近
    优点:非常快(轻量、实时);对照明变化有一定鲁棒性
    缺点:定位不够准,易受背景干扰
  7. CSRT (Discriminative Correlation Filter with Channel and Spatial Reliability)
    原理:在KCF基础上改进,引入通道可靠性和空间可靠性来增强鲁棒性
    优点:比KCF更准确,适合尺度变化、部分遮挡
    缺点:比KCF慢,实时性稍差

3.OpenCV目标追踪

下面是一个可以在视频中实时添加跟踪目标的代码示例:

1.支持算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def get_tracker(name):  
"""根据OpenCV的版本自动获取跟踪器的构造函数"""
if hasattr(cv2.legacy, name + "_create"):
return getattr(cv2.legacy, name + "_create")
elif hasattr(cv2, name + "_create"):
return getattr(cv2, name + "_create")
return None

# opencv支持的跟踪算法
opencv_object_trackers = {
"boosting": get_tracker("TrackerBoosting"), # 基于AdaBoost的传统算法,性能较差
"mil": get_tracker("TrackerMIL"), # 比Boosting鲁棒,但仍有局限性
"kcf": get_tracker("TrackerKCF"), # 速度快,但对遮挡敏感
"tld": get_tracker("TrackerTLD"), # 处理目标遮挡和消失,但易漂移
"medianflow": get_tracker("TrackerMedianFlow"), # 适用于匀速运动的小目标,失败时会自我检测
"mosse": get_tracker("TrackerMOSSE"), # 极快,适合实时应用,但精度较低
"csrt": get_tracker("TrackerCSRT") # 高精度,但速度较慢
}

2.主函数

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
def tracking(video_path, tracker_type="kcf"):

# 创建一个多目标追踪器对象(MultiTracker),可以同时追踪多个对象
trackers = cv2.legacy.MultiTracker_create()
vs = cv2.VideoCapture(video_path) # 读取视频流

while True:
# 取当前帧
frame = vs.read()
frame = frame[1]
if frame is None:
break

# 读取视频帧并缩放(这里统一宽度为 600 像素)
(h, w) = frame.shape[:2]
width = 600
r = width / float(w)
dim = (width, int(h * r))
frame = cv2.resize(frame, dim, interpolation=cv2.INTER_AREA)

# 更新所有跟踪目标
(success, boxes) = trackers.update(frame)

# 绘制区域
for box in boxes:
(x, y, w, h) = [int(v) for v in box]
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

# 显示
cv2.imshow("Frame", frame)
key = cv2.waitKey(100) & 0xFF

if key == ord("s"):
# 选择一个区域,按s
box = cv2.selectROI("Frame", frame, fromCenter=False,
showCrosshair=True)

# 创建一个新的追踪器
tracker = opencv_object_trackers[tracker_type]()
trackers.add(tracker, frame, box)

# 退出
elif key == 27:
break
vs.release()
cv2.destroyAllWindows()

3.调用

1
multi_object_tracking.tracking("videos/soccer_01.mp4", "medianflow")


s键,框出需要追踪的区域,再按任意键继续

4.DNN+Dlib的实时视频追踪

下面是一个先检测再追踪的代码示例:

1.单个目标追踪

dlib.correlation_trackerDlib 库中基于相关滤波(correlation filter)算法的目标追踪器,可以用来在视频中持续追踪一个物体。

它属于 单目标追踪器(Single Object Tracker),不是检测器,不会识别物体类型,只能追踪一开始指定的区域。

实现原理:
利用初始帧中的目标图像作为“模板”,在后续每一帧中,通过相关匹配来寻找与这个模板最相似的区域,更新模板以适应外观的小变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def start_tracker(box, label, rgb, inputQueue, outputQueue):  

# 初始化一个dlib的追踪器,并设置初始位置(start_track)
t = dlib.correlation_tracker()
rect = dlib.rectangle(int(box[0]), int(box[1]), int(box[2]), int(box[3]))
t.start_track(rgb, rect)

while True:
# 持续从 inputQueue 中获取下一帧图像
rgb = inputQueue.get()

# 非空就开始处理
if rgb is not None:
# 每来一帧就更新追踪器的位置
t.update(rgb)
pos = t.get_position()

startX = int(pos.left())
startY = int(pos.top())
endX = int(pos.right())
endY = int(pos.bottom())

# 将新的位置通过outputQueue发回主进程
outputQueue.put((label, (startX, startY, endX, endY)))

多进程的好处是:多个对象可以并行追踪,不会互相影响,CPU 利用率高

2.主函数

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
# 类别定义 用于检测输出中的label索引对应类别名
classes = ["background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow",
"diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

def tracking(video_path, model, proto_txt, output, confidence_value):
inputQueues = []
outputQueues = []

# 加载Caffe格式的深度学习模型
net = cv2.dnn.readNetFromCaffe(proto_txt, model)
print("[INFO] starting video stream...")
vs = cv2.VideoCapture(video_path) # 打开视频流
writer = None
fps = FPS().start() # 启动FPS计数器

while True:
# 读取并预处理每一帧
(grabbed, frame) = vs.read()
if frame is None:
break
(h, w) = frame.shape[:2]
width = 600
r = width / float(w)
dim = (width, int(h * r))
frame = cv2.resize(frame, dim, interpolation=cv2.INTER_AREA)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

# 初始化视频保存器
if output is not None and writer is None:
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = cv2.VideoWriter(output, fourcc, 30,
(frame.shape[1], frame.shape[0]), True)

# 检测并启动追踪器(只在第一帧执行)
if len(inputQueues) == 0:
(h, w) = frame.shape[:2]
# 使用OpenCV DNN执行一次前向推理
blob = cv2.dnn.blobFromImage(frame, 0.007843, (w, h), 127.5)
net.setInput(blob)
detections = net.forward()
for i in np.arange(0, detections.shape[2]):
confidence = detections[0, 0, i, 2]
# 过滤置信度大于confidence_value 且类别为 "person" 的结果
if confidence > confidence_value:
idx = int(detections[0, 0, i, 1])
label = classes[idx]
if classes[idx] != "person":
continue
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype("int")
# 得到目标框
bb = (startX, startY, endX, endY)

# 创建输入q和输出q
iq = multiprocessing.Queue()
oq = multiprocessing.Queue()
inputQueues.append(iq)
outputQueues.append(oq)

# 创建一个 multiprocessing.Queue 输入输出队列
p = multiprocessing.Process(target=start_tracker, args=(bb, label, rgb, iq, oq))
p.daemon = True
p.start()

cv2.rectangle(frame, (startX, startY), (endX, endY),
(0, 255, 0), 2)
cv2.putText(frame, label, (startX, startY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 255, 0), 2)

else:
# 更新追踪器
for iq in inputQueues:
iq.put(rgb) # 所有追踪器共享相同帧

for oq in outputQueues:
# 得到更新结果
(label, (startX, startY, endX, endY)) = oq.get()

# 绘图
cv2.rectangle(frame, (startX, startY), (endX, endY),
(0, 255, 0), 2)
cv2.putText(frame, label, (startX, startY - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 255, 0), 2)

if writer is not None:
writer.write(frame)

cv2.imshow("Frame", frame)

# FPS更新+按键控制
key = cv2.waitKey(1) & 0xFF
if key == 27:
break
fps.update()

print("[INFO] elapsed time: {:.2f}".format(fps.elapsed()))
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))

# 清理资源
fps.stop()
if writer is not None:
writer.release()
cv2.destroyAllWindows()
vs.release()

3.调用

1
2
3
4
5
multi_object_tracking_fast.tracking("videos/race.mp4",  
"mobilenet_ssd/MobileNetSSD_deploy.caffemodel",
"mobilenet_ssd/MobileNetSSD_deploy.prototxt",
"output/race_fast.mp4",
0.2)

主要功能点:

  1. 用 OpenCV 的 DNN 模块加载 Caffe 模型检测“person”
  2. 每个目标分配一个子进程进行独立追踪
  3. 利用 multiprocessing 提升处理性能
  4. 使用 FPS 类打印处理速度

5.总结

方法类型 优势 局限 适用场景
传统算法 轻量、高速,适合嵌入式设备 对复杂变化(遮挡、尺度)敏感 实时监控、资源受限环境
深度学习+Dlib 高精度,支持多目标并行 计算资源要求较高 复杂场景(如人流密集、遮挡频繁)
端到端模型 检测与追踪联合优化,简化流程 需大量标注数据,训练成本高 高精度需求(如自动驾驶)

6.备注

环境

  • mac: 15.2
  • python: 3.12.4
  • numpy: 1.26.4
  • opencv-python: 4.11.0.86
  • dlib: 19.24.6

资源和代码

https://github.com/keychankc/dl_code_for_blog/tree/main/009_opencv_tracking