OpenCV计算机视觉实战(24)——目标追踪算法
0. 前言
在视频分析与智能监控中,目标追踪 (Object Tracking
) 是识别出目标后,实时在后续帧中持续定位其位置的核心技术。本文将从经典的 MeanShift
算法原理入手,介绍其在 OpenCV
中的实现;接着讲解 CamShift
(Continuously Adaptive Mean Shift
) 的改进策略;最后讨论多目标追踪时面临的挑战与常见解决思路。
1. MeanShift 目标追踪
MeanShift
是一种基于统计密度的迭代搜索算法,用于在目标色彩直方图模型下,不断调整搜索窗口使其收敛到概率密度最大的区域,实现对目标位置的跟踪。
1.1 实现过程
ROI
选择:通过cv2.selectROI
在首帧指定目标窗口,获取(x,y,w,h)
- 直方图建模:将
ROI
转为HSV
空间,提取H
通道并使用掩码剔除低饱和/低亮度像素,然后用calcHist
计算目标色彩分布并归一化 - 反向投影:对每帧图像计算其
HSV
图像在目标直方图下的概率分布,用以指导搜索方向 MeanShift
更新:cv2.meanShift
根据反向投影图,在当前窗口附近迭代移动,使窗口中心逐步趋向密度最大处- 可视化:每次迭代后在原图上绘制新窗口
import cv2
import numpy as np
# 基于 MeanShift 算法,在视频中跟踪指定 ROI 内的对象
cap = cv2.VideoCapture('r2.mp4')
ret, frame = cap.read()
# 1. 初始化追踪窗口(x,y,w,h)
x, y, w, h = cv2.selectROI('Select ROI', frame, False, False)
track_window = (x, y, w, h)
cv2.destroyWindow('Select ROI')
# 2. 计算 ROI 的 HSV 直方图并归一化
roi = frame[y:y+h, x:x+w]
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, np.array((0,60,32)), np.array((180,255,255)))
roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0,180])
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
# 3. 设置 MeanShift 终止条件:迭代 10 次或窗口中心移动少于 1 像素
term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
while True:
ret, frame = cap.read()
if not ret:
break
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 4. 反向投影:得到每像素属于目标直方图的概率
back_proj = cv2.calcBackProject([hsv], [0], roi_hist, [0,180], 1)
# 5. 执行 MeanShift 更新窗口
ret, track_window = cv2.meanShift(back_proj, track_window, term_crit)
x, y, w, h = track_window
# 6. 绘制结果
result = cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 2)
cv2.imshow('MeanShift Tracking', result)
if cv2.waitKey(30) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
关键函数解析:
cv2.calcHist(images, channels, mask, histSize, ranges)
:计算单通道直方图cv2.calcBackProject(images, channels, hist, ranges, scale)
:反向投影,输出概率图cv2.meanShift(probImage, window, criteria)
:MeanShift
算法核心,返回新的窗口位置cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT
:迭代终止条件,结合迭代次数和精度
1.2 改进思路
- 多通道直方图融合:除了
H
通道,还可将S
通道或V
通道一并用于反向投影,提升对比度弱目标的追踪稳定性 - 动态模型更新:在每若干帧后,用新的
ROI
对象区域更新直方图,以适应光照或外观变化,避免模型漂移 - 自适应窗口初始大小:根据目标在前几帧的速度变化,自适应调整搜索窗口大小与迭代次数
import cv2
import numpy as np
# 多通道 MeanShift,动态更新直方图模型
cap = cv2.VideoCapture('r2.mp4')
ret, frame = cap.read()
# 1. 选择 ROI
x, y, w, h = cv2.selectROI('Select ROI', frame, False, False)
track_window = (x, y, w, h)
cv2.destroyWindow('Select ROI')
# 2. 计算初始直方图(H+S 通道)
roi = frame[y:y+h, x:x+w]
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, (0,30,32), (180,255,255))
roi_hist = cv2.calcHist([hsv_roi], [0,1], mask, [180,256], [0,180,0,256])
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
frame_count = 0
while True:
ret, frame = cap.read()
if not ret: break
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 3. 反向投影(H+S)
back_proj = cv2.calcBackProject([hsv], [0,1], roi_hist, [0,180,0,256], 1)
# 4. MeanShift 更新
ret, track_window = cv2.meanShift(back_proj, track_window, term_crit)
x,y,w,h = track_window
# 5. 绘制跟踪窗口
img2 = cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 2)
cv2.imshow('MeanShift Multi-Channel', img2)
# 6. 每 30 帧动态更新模型
frame_count += 1
if frame_count % 30 == 0:
new_roi = frame[y:y+h, x:x+w]
hsv_new = cv2.cvtColor(new_roi, cv2.COLOR_BGR2HSV)
mask_new = cv2.inRange(hsv_new, (0,30,32), (180,255,255))
roi_hist = cv2.calcHist([hsv_new], [0,1], mask_new, [180,256], [0,180,0,256])
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
if cv2.waitKey(30) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
关键函数解析:
cv2.calcBackProject([hsv], [0,1], roi_hist, ranges, scale)
:支持多通道反向投影,将H
、S
联合概率映射到灰度图,增强区分度- 动态更新:每
N
帧用当前窗口区域重新计算直方图,防止光照/目标外观改变导致跟丢
2. CamShift 目标追踪
CamShift
(Continuously Adaptive Mean Shift
) 在 MeanShift
的基础上,会根据目标大小变化自动调整窗口尺寸,并输出目标的旋转矩形,实现更灵活的跟踪。
2.1 实现过程
ROI
与直方图初始化:与MeanShift
相同,将目标模型化为HSV
直方图CamShift
调用:cv2.CamShift
在MeanShift
基础上,每次不仅更新窗口中心,还根据反向投影分布自动计算新的窗口大小和方向(即K-L
椭圆)- 旋转矩形绘制:用
boxPoints
获得输出的旋转矩形四个顶点,并通过polylines
画在图像上
import cv2
import numpy as np
# 基于 CamShift 算法,自适应调整窗口大小与角度
cap = cv2.VideoCapture('r2.mp4')
ret, frame = cap.read()
# 1. 初始化 ROI 与直方图(同 MeanShift)
x, y, w, h = cv2.selectROI('Select ROI', frame, False, False)
track_window = (x, y, w, h)
cv2.destroyWindow('Select ROI')
roi = frame[y:y+h, x:x+w]
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, np.array((0,60,32)), np.array((180,255,255)))
roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0,180])
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
while True:
ret, frame = cap.read()
if not ret: break
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
back_proj = cv2.calcBackProject([hsv], [0], roi_hist, [0,180], 1)
# 2. 执行 CamShift,返回旋转矩形
ret, track_window = cv2.CamShift(back_proj, track_window, term_crit)
pts = cv2.boxPoints(ret) # 4 顶点
pts = np.int0(pts)
# 3. 绘制旋转矩形
result = frame.copy()
cv2.polylines(result, [pts], True, (0,255,0), 2)
cv2.imshow('CamShift Tracking', result)
if cv2.waitKey(30) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
关键函数解析
cv2.CamShift(probImage, window, criteria)
:改进版MeanShift
,返回旋转矩形(center, (w,h), angle)
与新窗口cv2.boxPoints(rotRect)
:将旋转矩形转换为4
个顶点坐标cv2.polylines(img, pts, isClosed, color, thickness)
:绘制多边形,填充或描边追踪轮廓
2.2 改进思路
- 椭圆可视化:除了多边形,还可绘制
cv2.ellipse
,直观展示目标方向与长短轴长度 - 自适应迭代:对颜色分布复杂的场景,可根据窗口面积动态调整
term_crit
中的最大迭代次数 - 背景补偿:在反向投影前对背景图像进行高斯模糊或直方图均衡,增强目标对比
import cv2
import numpy as np
# CamShift + 椭圆可视化 + 背景直方图均衡
cap = cv2.VideoCapture('r2.mp4')
ret, frame = cap.read()
x,y,w,h = cv2.selectROI('ROI', frame, False, False)
track_window = (x,y,w,h)
cv2.destroyWindow('ROI')
roi = frame[y:y+h, x:x+w]
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, (0,30,32), (180,255,255))
roi_hist = cv2.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv2.normalize(roi_hist, roi_hist, 0,255,cv2.NORM_MINMAX)
term_crit = (cv2.TERM_CRITERIA_EPS|cv2.TERM_CRITERIA_COUNT, 15, 1)
while True:
ret, frame = cap.read()
if not ret: break
# 背景均衡
blur = cv2.GaussianBlur(frame, (5,5), 0)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
back_proj = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# CamShift
ret, track_window = cv2.CamShift(back_proj, track_window, term_crit)
pts = cv2.boxPoints(ret)
pts = np.int0(pts)
# 画椭圆
center, (w_e,h_e), angle = ret
result = frame.copy()
cv2.ellipse(result, (tuple(map(int,center)), (int(w_e),int(h_e)), angle), (0,255,0), 2)
cv2.polylines(result, [pts], True, (255,0,0), 2)
cv2.imshow('CamShift Enhanced', result)
if cv2.waitKey(30) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
关键函数解析:
cv2.ellipse(img, (center, axes, angle), color, thickness)
:直接绘制旋转椭圆,反映目标方向与尺度cv2.GaussianBlur(frame, (5,5), 0)
:先做背景均衡模糊,提高反向投影鲁棒性
3. 多目标追踪
在复杂场景中,通常需要同时跟踪多个目标。最简单的思路是为每个目标分别初始化多个 CamShift
跟踪器,但要解决的挑战包括目标遮挡、相互干扰与误跟踪漂移。
3.1 代码实现
import cv2
import numpy as np
# 多目标 CamShift 跟踪
cap = cv2.VideoCapture('r2.mp4')
ret, frame = cap.read()
# 1. 手动选择多个 ROI
bboxes = []
while True:
bbox = cv2.selectROI('Select ROI, Enter为空结束', frame, False, False)
if sum(bbox) == 0: break
bboxes.append(bbox)
cv2.destroyAllWindows()
# 2. 初始化每个 ROI 的直方图与窗口
trackers = []
for (x,y,w,h) in bboxes:
hsv_roi = cv2.cvtColor(frame[y:y+h, x:x+w], cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, np.array((0,60,32)), np.array((180,255,255)))
hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0,180])
cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX)
trackers.append({'hist':hist, 'window':(x,y,w,h)})
term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
while True:
ret, frame = cap.read()
if not ret: break
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
result = frame.copy()
# 3. 对每个目标执行 CamShift
for tr in trackers:
back_proj = cv2.calcBackProject([hsv], [0], tr['hist'], [0,180], 1)
ret, tr['window'] = cv2.CamShift(back_proj, tr['window'], term_crit)
pts = cv2.boxPoints(ret).astype(int)
cv2.polylines(result, [pts], True, (0,255,0), 2)
cv2.imshow('Multi-Object Tracking', result)
if cv2.waitKey(30) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
3.2 优化思路
- 唯一颜色标识:为每个目标随机分配追踪框颜色,便于视觉区分
- 丢失与重新检测:若某个目标超过连续
N
帧未被追踪到,调用检测重新初始化ROI
# 在初始化 trackers 时为每个分配随机 color
trackers = [{'hist':..., 'window':..., 'color':tuple(np.random.randint(0,255,3))} for ... in bboxes]
while True:
ret, frame = cap.read()
if not ret: break
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
for tr in trackers:
back = cv2.calcBackProject([hsv],[0],tr['hist'],[0,180],1)
ret, tr['window'] = cv2.CamShift(back, tr['window'], term_crit)
if ret[1][0]*ret[1][1] < 100: # 窗口面积过小,视为丢失
tr['lost'] += 1
else:
tr['lost'] = 0
if tr['lost'] > 10:
tr['window'] = detect_new_roi(frame) # 调用检测模块重定位
pts = cv2.boxPoints(ret).astype(int)
cv2.polylines(frame, [pts], True, tr['color'], 2)
cv2.imshow('Multi-Object Advanced', frame)
if cv2.waitKey(30)&0xFF==27: break
小结
在本文中,我们从 MeanShift
的色彩直方图密度搜索出发,深入剖析了其多通道建模与动态模型更新技巧,又在 CamShift
中加入了背景均衡、椭圆可视化和自适应迭代策略,显著提升了跟踪的精度与鲁棒性。最后,探讨了多目标追踪,通过这些实践,了解了从单目标到多目标、从传统算法到深度检测器融合的完整追踪管线,能够在复杂视频场景中稳定地识别、定位并持续追踪各类移动目标。
系列链接
OpenCV计算机视觉实战(1)——计算机视觉简介
OpenCV计算机视觉实战(2)——环境搭建与OpenCV简介
OpenCV计算机视觉实战(3)——计算机图像处理基础
OpenCV计算机视觉实战(4)——计算机视觉核心技术全解析
OpenCV计算机视觉实战(5)——图像基础操作全解析
OpenCV计算机视觉实战(6)——经典计算机视觉算法
OpenCV计算机视觉实战(7)——色彩空间详解
OpenCV计算机视觉实战(8)——图像滤波详解
OpenCV计算机视觉实战(9)——阈值化技术详解
OpenCV计算机视觉实战(10)——形态学操作详解
OpenCV计算机视觉实战(11)——边缘检测详解
OpenCV计算机视觉实战(12)——图像金字塔与特征缩放
OpenCV计算机视觉实战(13)——轮廓检测详解
OpenCV计算机视觉实战(14)——直方图均衡化
OpenCV计算机视觉实战(15)——霍夫变换详解
OpenCV计算机视觉实战(16)——图像分割技术
OpenCV计算机视觉实战(17)——特征点检测详解
OpenCV计算机视觉实战(18)——视频处理详解
OpenCV计算机视觉实战(19)——特征描述符详解
OpenCV计算机视觉实战(20)——光流法运动分析
OpenCV计算机视觉实战(21)——模板匹配详解
OpenCV计算机视觉实战(22)——图像拼接详解
OpenCV计算机视觉实战(23)——目标检测详解