目标跟踪算法
1.MeanShift目标跟踪算法
前言
在计算机视觉领域,目标跟踪是一项极具挑战性的任务,广泛应用于视频监控、人机交互、智能交通等领域。
Mean Shift算法的原理很简单。假设你有一堆点集,还有一个小的窗口,这个窗口可能是圆形的,现在你可能要移动这个窗口到点集密度最大的区域当中。如下图:👇

最开始的窗口是蓝色圆环的区域,命名为C1。蓝色圆环的圆心用一个蓝色的矩形标注,命名为C1_o。
而窗口中所有点的点集构成的质心在蓝色圆形点C1_r处,显然圆环的形心和质心并不重合。
所以,移动蓝色的窗口,使得形心与之前得到的质心重合。在新移动后的圆环的区域当中再次寻找圆环当中所包围点集的质心,然后再次移动,通常情况下,形心和质心是不重合的。
不断执行上面的移动过程,直到形心和质心大致重合结束。 这样,最后圆形的窗口会落到像素分布最大的地方,也就是图中的绿色圈,命名为C2。
Mean Shift算法可以用于目标跟踪,如人脸追踪的核心思想是:将人脸区域建模为颜色(或纹理)的概率分布(直方图),通过在视频序列中迭代搜索与目标直方图最相似的区域,实现目标的持续定位。
如何在一个视频中使用Mean Shift算法来追踪一个运动的物体呢?大致流程如下: 👇
- 首先在图像上选定一个目标区域,这个就是ROI(Region of Interest);
- 计算选定区域的直方图分布,一般是HSV色彩空间的直方图;
- 对下一帧图像b同样计算直方图分布;
- 计算图像b中与选定区域直方图分布最为相似的区域,使用Mean Shift算法将选定区域沿着最为相似的部分进行移动,直到找到最相似的区域,便完成了在图像b中的目标追踪;
- 重复3到4的过程,就完成整个视频目标追踪。
通常情况下我们使用直方图反向投影得到的图像和第一帧目标对象的起始位置——当目标对象的移动会反映到直方图反向投影图中时,Mean Shift算法就会把我们的窗口移动到反向投影图像中灰度密度最大的区域了。
案例如下:👇
import cv2
import numpy as np
def main():
# 方式1
# cap = cv.VideoCapture('DOG.wmv') # 打开视频文件
# 方式2
cap = cv2.VideoCapture(0) # 打开摄像头
ret, frame = cap.read() # 读取一帧图像
if not ret:
print("无法读取视频流")
return
# 选择跟踪目标区域
roi = cv2.selectROI("Select ROI and press SPACE or ENTER", frame) # 弹出窗口选择ROI
cv2.destroyWindow("Select ROI and press SPACE or ENTER") # 关闭选择窗口
x, y, w, h = roi
track_window = (x, y, w, h) # MeanShift需要的窗口格式 (x, y, w, h)
# 计算目标颜色直方图
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.))) # 过滤低亮度和低饱和度
roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0, 180]) # 计算H通道直方图
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX) # 归一化
# 设置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)
dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
# MeanShift追踪
ret, track_window = cv2.meanShift(dst, track_window, term_crit)
# 绘制矩形框 (x, y, w, h)
x, y, w, h = track_window
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('MeanShift Tracking', frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # 按q退出
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
2. CamShift目标跟踪算法
前言
CamShift(连续自适应的 MeanShift 算法) 算法作为一种高效的目标跟踪算法,因其能够 自适应调整跟踪窗口大小和位置 ,受到了广泛关注。
CamShift算法原理 👇
CamShift算法的全称是“连续自适应的Mean-SHIFT算法”。其核心思想是对视频序列中的每一帧图像进行Mean-Shift运算,并将上一帧的结果(即搜索窗口的中心位置和窗口大小)作为下一帧Mean-Shift算法的初始值,从而实现对目标的连续跟踪。
算法步骤 👇
- 初始化目标区域:
- 用户手动选择初始目标区域(ROI),并计算该区域的颜色直方图。
- 颜色直方图反向投影:
- 将当前帧图像转换到HSV颜色空间,计算目标区域的色调(H)分量的直方图。
- 利用直方图反向投影技术,得到目标像素的概率分布图。
- Mean-Shift迭代:
- 在概率分布图上应用Mean-Shift算法,找到目标的新位置和大小。
- 更新搜索窗口:
- 将新位置和大小作为下一帧的初始搜索窗口,继续迭代。
一句话总结: CamShift是一种基于颜色直方图的实时目标跟踪方法,通过迭代寻找目标概率密度函数的峰值并自适应调整搜索窗口大小和方向,实现对运动目标的鲁棒跟踪。
Python实现步骤 👇
import cv2 # 导入OpenCV库
import numpy as np # 导入NumPy库
def main():
cap = cv2.VideoCapture(0) # 打开摄像头
ret, frame = cap.read() # 读取一帧图像
if not ret:
print("无法读取视频流")
return
# 选择跟踪目标区域
roi = cv2.selectROI(frame, False) # ROI表示目标区域,
x, y, w, h = roi
# 计算目标颜色直方图
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.))) # 提取绿色区域
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) # 终止条件: 10次迭代或1次epsilon变化
while True:
ret, frame = cap.read() # 读取一帧图像
if not ret:
break
# 计算反向投影并跟踪
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 转换为HSV颜色空间
dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1) # 计算反向投影
ret, track_window = cv2.CamShift(dst, (x, y, w, h), term_crit) # CamShift算法
# 绘制跟踪框
pts = cv2.boxPoints(ret) # 获取矩形框的四个顶点
pts = np.int32(pts) # 转换为整数类型
cv2.polylines(frame, [pts], True, (0, 255, 0), 2) # 绿色矩形框
cv2.imshow('Tracking', frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # 按q键退出
break
cap.release() # 释放摄像头
cv2.destroyAllWindows() # 关闭所有窗口
if __name__ == "__main__":
main()效果图:
输入q退出

如果你把手掌放在脸前面,然后移动手掌,你会发现摄像头会跟随你的手掌移动。 思考一下这是为什么?
3.人脸识别案例
前言
通过前面的学习,我们已经掌握了像CamShift这样的经典目标跟踪算法。它的核心思想是利用目标的色彩分布和运动连续性,通过迭代的方式在视频序列中‘锁定’运动的物体。这种方法非常直观且高效,尤其适合背景相对简单、目标与背景颜色对比强烈的场景。

然而,CamShift也存在其局限性 。它严重依赖于颜色信息,一旦目标被遮挡、颜色与背景相似或发生剧烈形变,跟踪就很容易失败。因为它本质上是一个‘追踪器’,而非‘识别器’,它假设目标始终在视野中运动,并且没有一种机制去主动‘辨认’或‘验证’当前锁定的对象是否真的是我们最初设定的那个目标。
那么,如何让计算机具备像人眼一样,仅凭目标的‘长相’(即其固有的静态外观特征)就能在一帧图像中快速定位到它呢? 这就需要引入更强大的特征描述子和分类器 。
接下来,我们将学习一种革命性的技术——Haar特征与AdaBoost级联分类器。如果说CamShift是‘动态追踪’,那么Haar特征则是‘静态侦察’。它不关心目标如何运动,而是通过提取图像中类似‘明暗条纹’的Haar-like特征,训练出一个高效的分类器,从而能够在一幅复杂的静态图像中,以极高的速度完成目标的精准定位,为我们打开目标检测的大门。
AdaBoost(阿达布斯特,自适应提升算法)。它是一种集成学习方法,通过组合多个弱分类器(例如决策树)来构建一个强分类器
下图中的 Haar 特征(哈尔特征 Haar-like Features)会被使用,就像我们的卷积核,每一个特征是一个值,这个值等于黑色矩形中的像素值总和减去白色矩形中的像素值总和。
Haar特征基于图像中相邻矩形区域内像素亮度的差值(即明暗变化)来描述物体的结构与纹理信息。

Haar特征值反映了图像的灰度变化情况。例如:脸部的一些特征能由矩形特征简单地描述,眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深。
Haar特征可用于图像任意位置,大小也可以任意改变,所以矩形特征值是矩形模板类别、矩形位置和矩形大小这三个因素的函数。由于类别、大小和位置的变化,使得很小的检测窗口含有非常多的矩形特征.

得到图像的特征后,训练一个决策树构建的adaboost级联决策器来识别是否为人脸 👇

实现:👇
OpenCV中自带已训练好的检测器,包括面部,眼睛,猫脸等,都保存在XML文件中,我们可以通过以下程序找到他们:
import cv2
import os
# 打印OpenCV数据目录路径
print("OpenCV数据目录:", cv2.data.haarcascades)
# 列出该目录下的所有XML文件
xml_files = [f for f in os.listdir(cv2.data.haarcascades) if f.endswith('.xml')]
print("可用的XML检测器文件:")
for file in xml_files:
print(f" - {file}")OpenCV数据目录: C:\Users\huyan\AppData\Roaming\Python\Python313\site-packages\cv2\data\
可用的XML检测器文件:
👤 人脸检测系列
haarcascade_frontalface_default.xml - 标准正面人脸检测器(最常用)
haarcascade_frontalface_alt.xml - 正面人脸检测器版本1(更准确但较慢)
haarcascade_frontalface_alt2.xml - 正面人脸检测器版本2(速度和精度平衡)
haarcascade_frontalface_alt_tree.xml - 正面人脸检测器版本3(高精度,很慢)
haarcascade_profileface.xml - 侧面人脸检测器
👁️ 眼睛检测系列
haarcascade_eye.xml - 通用眼睛检测器
haarcascade_eye_tree_eyeglasses.xml - 能检测戴眼镜眼睛的专用检测器
haarcascade_lefteye_2splits.xml - 左眼专用检测器
haarcascade_righteye_2splits.xml - 右眼专用检测器
😊 其他面部特征
haarcascade_smile.xml - 微笑检测器
🐱 动物检测
haarcascade_frontalcatface.xml - 猫脸正面检测器
haarcascade_frontalcatface_extended.xml - 猫脸正面检测器(扩展版)
🚗 车辆相关
haarcascade_license_plate_rus_16stages.xml - 俄罗斯车牌检测器
haarcascade_russian_plate_number.xml - 俄罗斯车牌号码检测器
👥 人体部位检测
haarcascade_fullbody.xml - 全身检测器
haarcascade_lowerbody.xml - 下半身检测器
haarcascade_upperbody.xml - 上半身检测器
我们利用这些文件来识别人脸、眼睛等。检测流程如下:
- 读取图片,并转换成灰度图
- 脸和眼睛检测的分类器对象
- 和眼睛的检测
- 果绘制出来即可
人脸案例
import cv2 as cv
import matplotlib.pyplot as plt
import os
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负
# 设置分类器路径
cascade_dir = r"C:\Users\huyan\AppData\Roaming\Python\Python313\site-packages\cv2\data" # 分类器目录
face_cascade = os.path.join(cascade_dir, "haarcascade_frontalface_default.xml") # 人脸检测器
eye_cascade = os.path.join(cascade_dir, "haarcascade_eye.xml") # 眼睛检测器
# 读取图像
img = cv.imread("./img/hezhao.png")
if img is None:
print("无法加载图像文件")
exit()
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 转换为灰度图
# 加载分类器
face_cas = cv.CascadeClassifier(face_cascade) # 人脸检测器
eyes_cas = cv.CascadeClassifier(eye_cascade)# 眼睛检测器
# 人脸检测
faceRects = face_cas.detectMultiScale(gray, 1.1, 4, minSize=(50, 50)) # 检测人脸, 1.1为缩放比例, 4为邻近窗口, 最小人脸大小为50x50
# 绘制检测结果
for i, (x, y, w, h) in enumerate(faceRects):
# 画人脸框
cv.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 3)
cv.putText(img, f'Face {i+1}', (x, y-10), cv.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
# 检测眼睛
if not eyes_cas.empty():
roi_gray = gray[y:y+h, x:x+w]
eyes = eyes_cas.detectMultiScale(roi_gray, 1.1, 3, minSize=(20, 20)) # 检测眼睛 1.1为缩放比例, 3为邻近窗口, 最小眼睛大小为20x20
for j, (ex, ey, ew, eh) in enumerate(eyes):
cv.rectangle(img[y:y+h, x:x+w], (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)
# 显示结果
plt.figure(figsize=(12, 8))
plt.imshow(img[:, :, ::-1])
plt.title(f'检测到 {len(faceRects)} 张人脸')
plt.axis('off')
plt.show()
print(f"完成!发现 {len(faceRects)} 张人脸")
如果将检测器转成猫脸,只需要将face_cascade换成haarcascade_frontalcatface.xml即可。然后上传cat.png 就可以看到效果了。

如果将图像转成视频,我们可以使用摄像头进行实时检测,如下: 👇 👇
4.视频跟踪案例
前言
通过前面的学习,我们已经构建了一个强大的‘目标侦察系统’——基于Haar特征的分类器。它就像一位经验丰富的侦探,仅凭目标的‘五官面貌’(Haar特征)就能在静态的单张照片中迅速将其识别出来。
我们已经掌握了如何‘发现’和‘定位’目标的技能。
但是,我们的视觉世界并非静止的画卷,而是由连绵不绝的动态影像构成的。一个真正的智能视觉系统,不仅要在静帧中明察秋毫,更要能驾驭时间的河流,理解并分析连续的运动过程。那么,如何将我们这位出色的‘静态侦探’升级为一位能够‘观看电影’的分析师呢?
答案的关键就在于:将独立的图片串联起来,构建连续的视觉流。这正是我们接下来要实践的核心环节——‘图片缓存视频’。
这个技术可以理解为为我们的程序创建了一个‘记忆缓冲区’或‘临时放映厅’,它能够将一系列按时间顺序捕获的图片帧缓存起来,并以指定的帧率连续播放,从而模拟出视频流的连续效果。
这一步至关重要,它是连接‘单帧图像识别’与‘视频流实时分析’的桥梁。只有将Haar特征检测器部署在这个动态的‘舞台’上,我们才能见证它如何从一张张孤立的快照中,实时地、连续地捕捉到目标的踪迹,最终实现一个完整的目标检测与跟踪演示。
现在,就让我们动手搭建这个‘视觉舞台’吧!
import cv2
import numpy as np
def main():
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
if not ret:
print("无法读取视频流")
return
# 选择跟踪目标区域
roi = cv2.selectROI(frame, False) # 选择跟踪目标区域
x, y, w, h = roi
# 计算目标颜色直方图
hsv_roi = cv2.cvtColor(frame[y:y+h, x:x+w], cv2.COLOR_BGR2HSV) # 转换为HSV颜色空间
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) # 终止条件 :当聚类中心移动小于1.0或迭代达到10次时停止
while True:
ret, frame = cap.read()
if not ret:
break
# 计算反向投影并跟踪
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 转换为HSV颜色空间
dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1) # 计算反向投影
ret, track_window = cv2.CamShift(dst, (x, y, w, h), term_crit) # 跟踪 dst, 跟踪窗口, 终止条件
# 绘制跟踪框
pts = cv2.boxPoints(ret) # 获取跟踪框的四个顶点
pts = np.int32(pts)
cv2.polylines(frame, [pts], True, (0, 255, 0), 2) # 绘制跟踪框
cv2.imshow('Tracking', frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # 按q键退出
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
用手掌在摄像头前挥动,看是否会识别为脸部
