摘要:本文是为计算机视觉初学者及进阶者准备的一篇详尽指南。我们将首先稳固掌握OpenCV中视频处理的基础------如何高效地读取、显示和保存视频文件。随后,我们将深入探讨两种经典且强大的目标追踪算法:MeanShift和CamShift,从原理到应用场景,为您揭示其背后的智慧与局限性。
学习目标
- 基础篇:熟练掌握使用OpenCV进行视频文件的读取、属性查询与视频流的保存。
- 进阶篇:深刻理解MeanShift算法的迭代原理,并掌握其在目标追踪中的应用流程。
- 高级篇:了解CamShift作为MeanShift的改进算法,如何实现对目标大小和角度的自适应追踪。
第一部分:视频处理之基石------读、写、显
在进行任何高级视频分析之前,我们必须先学会如何与视频数据打交道。OpenCV为此提供了简洁高效的接口。
1. 从文件中读取与播放视频
核心在于创建一个 VideoCapture 对象,它如同一个播放器,能从文件或摄像头中逐帧提取图像。
核心API与流程
-
创建读取对象
- API :
cap = cv.VideoCapture(filepath) - 参数 :
filepath- 视频文件的路径。也可以是数字0,代表默认的摄像头。
- API :
-
判断是否成功打开
- API :
is_opened = cap.isOpened() - 说明 : 在读取前检查此项,确保视频源有效。返回
True则表示成功。
- API :
-
获取视频属性
- API :
retval = cap.get(propId) - 说明 :
propId是代表不同属性的常量(如cv.CAP_PROP_FRAME_WIDTH代表帧宽度)。这对于后续处理(如创建写入对象)至关重要。
- API :
-
设置视频属性
- API :
cap.set(propId, value) - 说明: 可以尝试修改某些属性,例如从摄像头捕获时的分辨率。并非所有属性都可修改。
- API :
-
逐帧读取图像
- API :
ret, frame = cap.read() - 返回值 :
ret: 布尔值,True表示成功读取一帧,False表示视频已结束或读取失败。frame: 读取到的单帧图像。
- API :
-
显示与控制
- 使用
cv.imshow()显示每一帧图像。 - 配合
cv.waitKey(delay)来控制播放速度。delay单位为毫秒,一个典型值是25,模拟约40fps的播放效果。
- 使用
-
释放资源
- API :
cap.release() - 说明: 视频处理结束后,务必调用此方法来关闭视频文件或摄像头,释放系统资源。
- API :
代码示例
python
import numpy as np
import cv2 as cv
# 1.获取视频对象
cap = cv.VideoCapture('DOG.wmv')
# 2.判断是否读取成功
while(cap.isOpened()):
# 3.获取每一帧图像
ret, frame = cap.read()
# 4. 获取成功显示图像
if ret == True:
cv.imshow('frame',frame)
# 5.每一帧间隔为25ms
if cv.waitKey(25) & 0xFF == ord('q'):
break
# 6.释放视频对象
cap.release()
cv.destoryAllwindows()
2. 保存视频
当处理完视频帧(例如添加了追踪框或特效)后,我们需要使用 VideoWriter 对象将这些帧序列重新编码成一个新的视频文件。
核心API与流程
-
创建写入对象
- API :
out = cv.VideoWriter(filename, fourcc, fps, frameSize) - 参数 :
filename: 输出视频文件的名称,如'output.avi'。fourcc: 视频编码器。fps: 帧率。frameSize: 帧的尺寸,格式为(宽度, 高度)的元组。
- API :
-
设置视频编码器 (FourCC)
- API :
retval = cv.VideoWriter_fourcc(c1, c2, c3, c4) - 说明: FourCC是一个4字节代码,用于指定视频压缩格式。
- 常用组合 :
- 在Windows上:
DIVX(生成.avi) - 跨平台/Web:
MJPG(生成.mp4或.avi) ,X264(生成.mkv)
- 在Windows上:
- API :
-
逐帧写入
- API :
out.write(frame) - 说明 : 在循环中,将处理好的每一帧图像写入文件。注意 :写入的
frame尺寸必须与创建VideoWriter时指定的frameSize完全一致。
- API :
-
释放资源
- API :
out.release() - 说明 : 所有帧写入完毕后,调用此方法来完成文件编码和保存。这是至关重要的一步,否则视频文件可能损坏或为空。
- API :
代码示例
python
import cv2 as cv
import numpy as np
# 1. 读取视频
cap = cv.VideoCapture("DOG.wmv")
# 2. 获取图像的属性(宽和高,),并将其转换为整数
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
# 3. 创建保存视频的对象,设置编码格式,帧率,图像的宽高等
out = cv.VideoWriter('outpy.avi',cv.VideoWriter_fourcc('M','J','P','G'), 10, (frame_width,frame_height))
while(True):
# 4.获取视频中的每一帧图像
ret, frame = cap.read()
if ret == True:
# 5.将每一帧图像写入到输出文件中
out.write(frame)
else:
break
# 6.释放资源
cap.release()
out.release()
cv.destroyAllWindows()
第二部分:动态世界之眼------目标追踪
1. MeanShift算法:寻找密度的高峰
MeanShift是一种强大的聚类算法,其核心思想是通过迭代,将搜索窗口移动到数据点最密集的区域。在视频追踪中,"数据点"就是符合目标特征的像素。
1.1 原理剖析
想象一下,你在一个漆黑的房间里寻找一群发光萤火虫最密集的地方。
- 初始窗口: 你随机打开一个手电筒光圈(初始搜索窗口)。
- 计算质心: 你观察光圈内所有萤火虫的位置,并计算出它们的"质心"(视觉上的中心点)。
- 迭代移动: 如果手电筒的中心和萤火虫的质心不重合,你就将手电筒移动到刚才计算出的质心位置。
- 收敛: 你不断重复第2步和第3步,直到手电筒的中心和质心几乎重合。此时,你的手电筒就照亮了萤火虫最密集的核心区域。
在图像追踪中,我们用直方图反向投影来创建这个"萤火虫密度图"。我们首先计算目标的颜色直方图(比如,一只黄色的狗),然后用这个直方图去扫描整个视频帧,生成一张概率图。在这张图上,颜色与目标越相似的像素,其亮度值就越高。MeanShift要做的,就是在这张概率图上找到亮度(密度)的最高峰。
1.2 OpenCV中的实现流程
- 定义目标: 在视频的第一帧手动或自动选定一个感兴趣区域 (ROI)。
- 建立特征模型: 计算该ROI在HSV颜色空间的颜色直方图,并进行归一化。这个直方图就是我们追踪的"模板"。
- 目标追踪循环 :
- 对每一新帧,计算其相对于"模板直方图"的反向投影图。
- 调用API :
cv.meanShift(probImage, window, criteria)probImage: 直方图反向投影得到的概率图。window: 当前的搜索窗口位置。criteria: 搜索的终止条件(如迭代次数或偏移量阈值)。
- API会返回更新后的最佳窗口位置
track_window。 - 在原图上根据
track_window绘制矩形框,显示追踪结果。
代码示例
python
import numpy as np
import cv2 as cv
# 1.获取图像
cap = cv.VideoCapture('DOG.wmv')
# 2.获取第一帧图像,并指定目标位置
ret,frame = cap.read()
# 2.1 目标位置(行,高,列,宽)
r,h,c,w = 197,141,0,208
track_window = (c,r,w,h)
# 2.2 指定目标的感兴趣区域
roi = frame[r:r+h, c:c+w]
# 3. 计算直方图
# 3.1 转换色彩空间(HSV)
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
# 3.2 去除低亮度的值
# mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
# 3.3 计算直方图
roi_hist = cv.calcHist([hsv_roi],[0],None,[180],[0,180])
# 3.4 归一化
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
# 4. 目标追踪
# 4.1 设置窗口搜索终止条件:最大迭代次数,窗口中心漂移最小值
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(True):
# 4.2 获取每一帧图像
ret ,frame = cap.read()
if ret == True:
# 4.3 计算直方图的反向投影
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# 4.4 进行meanshift追踪
ret, track_window = cv.meanShift(dst, track_window, term_crit)
# 4.5 将追踪的位置绘制在视频上,并进行显示
x,y,w,h = track_window
img2 = cv.rectangle(frame, (x,y), (x+w,y+h), 255,2)
cv.imshow('frame',img2)
if cv.waitKey(60) & 0xFF == ord('q'):
break
else:
break
# 5. 资源释放
cap.release()
cv.destroyAllWindows()
2. CamShift算法:自适应的追踪之眼
MeanShift有一个明显的缺点:搜索窗口的大小是固定的。如果目标在视频中变大(靠近镜头)或变小(远离镜头),固定的窗口就会变得不准确。CamShift(Continuously Adaptive Mean-Shift)正是为了解决这个问题而生。
2.1 原理剖析
CamShift在MeanShift的基础上增加了一个"自适应"步骤:
- 运行MeanShift: 首先,它和MeanShift一样,先运行一次迭代,找到目标概率分布的中心。
- 调整窗口 : 一旦MeanShift收敛,CamShift会分析收敛区域的概率分布特征(比如二阶矩),从而计算出该分布的最佳拟合椭圆的大小和方向。
- 更新窗口: 在下一帧视频中,CamShift将使用这个经过调整(可能更大、更小或有旋转角度)的新窗口作为MeanShift的初始搜索窗口。
通过这种方式,CamShift的追踪框能够随着目标的尺寸和朝向变化而动态调整,提供了更鲁棒的追踪效果。
2.2 OpenCV中的实现
实现流程与MeanShift几乎完全相同,只需替换核心API即可:
- 调用API :
ret, track_window = cv.CamShift(dst, track_window, term_crit) - 结果绘制 : CamShift返回的是一个旋转矩形 (
RotatedRect),包含了中心点、尺寸和旋转角度。通常使用cv.boxPoints()将其转换为四个顶点,再用cv.polylines()绘制出来,这样可以直观地看到追踪框的角度变化。
第三部分:算法总结与对比
| 特性 | MeanShift | CamShift |
|---|---|---|
| 核心思想 | 迭代寻找概率密度最大区域 | 在MeanShift基础上,自适应调整搜索窗口 |
| 窗口大小 | 固定不变 | 可根据目标大小和方向动态变化 |
| 适用场景 | 目标大小基本不变的简单追踪 | 目标大小、方向会发生变化的复杂追踪 |
| 主要优点 | 算法简单,计算速度快 | 鲁棒性更强,能适应目标形变 |
| 主要缺点 | 无法适应目标尺寸变化,易跟丢 | 背景与目标颜色相似时,窗口可能错误扩张,导致跟踪失败 |
总结 :
无论是MeanShift还是CamShift,它们都是基于颜色直方图的经典追踪算法,计算效率高,在许多场景下表现出色。理解它们的工作原理,是深入学习更现代的目标追踪技术(如基于深度学习的Siam系列、SORT等)的重要基石。
第四部分:参考资料
黑马程序员人工智能教程_10小时学会图像处理OpenCV入门教程
感谢阅读!如果这篇文章对你有帮助,欢迎点赞、收藏并关注我,我们下期再见!