原文地址:
Meanshift 和 Camshifthttps://apachecn.github.io/opencv-doc-zh/#/docs/4.0.0/6.1-tutorial_py_meanshift
一点小记录,稍微学点opencv(一点视频处理、利用反向投影-CSDN blink-领先的开发者技术社区
这篇文章是小白的笔记,仅做记录,不适合学习,误入
引入
meanshift无法自适应改变窗口,而camshift算法能够根据目标的大小和旋转来适应调整窗口大小
该解决方案又一次来自"OpenCV 实验室",这个算法被称为 CAMshift(连续自适应 Meanshift 算法),并由 Gary Bradsky 于 1998 年在他的论文"用于感知用户界面的计算机视觉面部跟踪"中发布。
这个算法首先利用了 meanshift。一旦 meanshift 收敛,他将自动将窗口的大小更新为
并且寻找出最佳拟合椭圆的方向。同样的,在缩放后的窗口和先前窗口都会应用 meanshift 算法。该过程将持续到精确度满足要求。
代码
import numpy as np
import cv2 as cv
cap = cv.VideoCapture('learnCV/nana.mp4') # 改成你自己的视频
if not cap.isOpened():
print("Error: Could not open video.")
exit()
# 获取视频的第一帧
ret,frame = cap.read()
# 设置窗口的初始位置
r,h,c,w = 250,90,400,125 # 简单地硬编码值
track_window = (c,r,w,h)
# 设置 ROI(图像范围)以进行跟踪
roi = frame[r:r+h, c:c+w]
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
# 设置结束标志,10 次迭代或至少 1 次移动
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# 运行 Camshift 用以获取新的位置
ret, track_window = cv.CamShift(dst, track_window, term_crit)
# 绘制到新图像中
pts = cv.boxPoints(ret)
pts = np.int0(pts)
img2 = cv.polylines(frame,[pts],True, 255,2)
cv.imshow('img2',img2)
k = cv.waitKey(60) & 0xff
if k == 27:
break
else:
cv.imwrite(chr(k)+".jpg",img2)
else:
break
cv.destroyAllWindows()
cap.release()
细节详解
cap.read()
ret,frame = cap.read()
在OpenCV中,cap.read()
是一个用于从视频捕获对象中读取帧的方法。它返回两个值:
-
ret
:一个布尔值,指示帧是否被正确读取。如果成功读取帧,则为True
;如果没有更多的帧可以读取,则为False
。 -
frame
:实际读取的帧图像,如果ret
为True
,则frame
包含当前帧的图像数据;如果ret
为False
,则frame
可能不包含有效的图像数据。
cv.inRange
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
`mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))` 这行代码用于创建一个掩码,它将用于在HSV颜色空间中选择一个特定的颜色范围。让我们详细解释一下这行代码的各个部分:
-
`hsv_roi`:这是我们之前从视频帧中提取的ROI(感兴趣区域)的HSV表示。HSV颜色空间由色调(Hue)、饱和度(Saturation)、亮度(Value)三个维度组成,通常用于颜色分割和跟踪,因为它比BGR颜色空间更符合人类对颜色的感知。
-
`np.array((0., 60., 32.))` 和 `np.array((180., 255., 255.))`:这两个NumPy数组定义了HSV颜色空间中的颜色范围。第一个数组定义了颜色范围的下限,第二个数组定义了上限。
-
对于色调(Hue),下限是0,上限是180。色调值的范围通常是0到179,代表颜色的类型(例如,红色、绿色、蓝色等)。
-
对于饱和度(Saturation),下限是60,上限是255。饱和度表示颜色的强度或纯度。
-
对于亮度(Value),下限是32,上限是255。亮度表示颜色的明暗程度。
- `cv.inRange(hsv_roi, lower_bound, upper_bound)`:这个OpenCV函数用于创建一个二值图像(掩码),其中只有满足给定颜色范围的像素点会被设置为白色(255),其余像素点会被设置为黑色(0)。这个掩码将用于直方图的计算,确保只考虑我们感兴趣的颜色范围内的像素。
-
`hsv_roi`:输入的HSV图像。
-
`lower_bound`:颜色范围的下限。
-
`upper_bound`:颜色范围的上限。
通过这种方式,我们可以选择一个特定的颜色范围,例如,选择一个绿色的范围。在HSV颜色空间中,色调值0到180可以代表一个完整的颜色圆环,其中大约0到120可以代表绿色到黄色的范围。
在这段代码中,色调的范围从0到180,这实际上选择了所有的颜色,因为我们没有限制色调。然而,饱和度和亮度的范围限制了我们只选择那些饱和度和亮度较高的颜色。这意味着我们选择的颜色将是非常鲜艳的,亮度较高的颜色。
最后,这个掩码将用于计算ROI的直方图,这个直方图将被CamShift算法用来跟踪视频中的特定颜色对象。
cv.calcHist
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
`roi_hist = cv.calcHist([hsv_roi], [0], mask, [180], [0,180])` 这行代码用于计算感兴趣区域(ROI)的直方图,这个直方图将用于后续的颜色跟踪。让我们详细解释一下这行代码的各个部分:
-
`hsv_roi`:这是我们之前从视频帧中提取的ROI的HSV表示。HSV颜色空间由色调(Hue)、饱和度(Saturation)、亮度(Value)三个维度组成。
-
`[0]`:这个参数指定了直方图计算的通道。在这里,`[0]`表示我们只计算色调(Hue)通道的直方图。
-
`mask`:这是之前通过`cv.inRange`函数创建的掩码,用于选择HSV颜色空间中特定颜色范围的像素。
-
`[180]`:这个参数指定了直方图的"维度"。在这里,我们计算180个可能的值,对应于色调(Hue)的范围。色调的范围通常是0到179,但OpenCV中色调的范围是从0到180。
-
`[0,180]`:这个参数指定了直方图计算的像素值范围。在这里,我们考虑从0到180的所有可能的色调值。
-
`cv.calcHist`:这是OpenCV中用于计算图像直方图的函数。它返回一个直方图,表示图像中每个像素值的频率。
将这些参数组合起来,`cv.calcHist([hsv_roi], [0], mask, [180], [0,180])` 计算了`hsv_roi`图像中,被`mask`掩码选择的像素的色调直方图。直方图的每个"bin"对应一个可能的色调值,并且包含了该色调值在掩码区域内出现的次数。
这个直方图非常有用,因为它提供了关于ROI中特定颜色分布的统计信息。在CamShift算法中,这个直方图将用于寻找和跟踪视频中的颜色对象。CamShift算法会尝试找到一个窗口,使得该窗口内的像素值与我们计算的直方图最匹配,从而实现跟踪。
简而言之,这行代码创建了一个直方图,它描述了ROI中我们感兴趣的颜色的分布情况,这个直方图将被用于颜色跟踪算法中。
cv.normalized
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
`cv.normalize(roi_hist, roi_hist, 0, 255, cv.NORM_MINMAX)` 这行代码用于归一化直方图数据。让我们详细解释一下这行代码的各个部分:
-
`roi_hist`:这是我们之前计算的感兴趣区域(ROI)的直方图。直方图是一个一维数组,其中每个元素代表特定像素值(在本例中为色调值)在图像中出现的频率。
-
`roi_hist`(第二个参数):这是归一化后的直方图的输出。由于我们使用相同的变量名作为输入和输出,归一化的结果将覆盖原始的直方图。
-
`0` 和 `255`:这些是归一化的最小值和最大值。归一化过程将调整直方图的值,使得最小值变为0,最大值变为255。
-
`cv.NORM_MINMAX`:这是归一化的类型。`cv.NORM_MINMAX` 表示最小-最大归一化,它将数组中的值缩放到指定的范围内(在本例中是0到255)。
归一化直方图的目的是确保直方图的值在特定的范围内,这在许多图像处理和计算机视觉任务中非常有用。在CamShift算法中,归一化直方图有助于提高颜色跟踪的准确性和鲁棒性,因为它确保了直方图的值与像素值的范围一致。
让我们通过一个具体的例子来理解 `cv.normalize` 函数的作用。假设我们有一个直方图 `roi_hist`,它包含了以下四个值:[10, 20, 30, 40]
这些值分别表示在图像的ROI中,对应色调值的像素数量。现在,我们想要将这个直方图归一化到0到255的范围。使用 `cv.NORM_MINMAX` 类型的归一化,我们可以按照以下步骤进行:
-
找到直方图中的最小值和最大值。在这个例子中,最小值是10,最大值是40。
-
应用归一化公式,将每个值缩放到0到255的范围。归一化后的值计算如下:[0, 64, 128, 255]
现在,我们可以使用Python和OpenCV来模拟这个过程:
import numpy as np
import cv2 as cv
# 假设的直方图数据
roi_hist = np.array([10, 20, 30, 40])
# 归一化直方图
cv.normalize(roi_hist, roi_hist, 0, 255, cv.NORM_MINMAX)
# 打印归一化后的直方图
print(roi_hist)
执行这段代码后,`roi_hist` 将包含归一化后的值,即 `[0, 64, 128, 255]`。这就是 `cv.normalize` 函数的作用:它将原始数据根据指定的最小值和最大值进行缩放,使其落在一个新的范围内。在这个例子中,直方图的值被缩放到了0到255的范围内。
term_crit
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
在OpenCV中,`cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1` 是用来指定CamShift算法终止条件的参数。让我们详细解释一下这个参数:
- **终止条件** (`cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT`):这是两个标志的组合,用来定义CamShift算法何时停止迭代。
-
`cv.TERM_CRITERIA_EPS`:这个标志表示算法将在指定的最大迭代次数内停止,如果连续迭代之间的椭圆变化小于一个很小的阈值(epsilon,ε)。换句话说,如果变化非常小,说明算法已经收敛,不需要进一步迭代。
-
`cv.TERM_CRITERIA_COUNT`:这个标志表示算法将在指定的最大迭代次数内停止。无论算法是否收敛,达到迭代次数限制后就会停止。
将这两个标志用逻辑或操作符(`|`)组合起来,意味着算法将在满足任一条件时停止。
-
**最大迭代次数** (`10`):这是算法在停止前允许的最大迭代次数。如果迭代次数达到这个值,即使变化还没有小于ε,算法也会停止。
-
**ε值** (`1`):这是一个很小的正数,用来与连续迭代之间的椭圆变化量比较。如果变化量小于这个值,就认为算法已经收敛。
在CamShift算法中,这个终止条件用来平衡算法的计算效率和精度。通过设置合适的ε值和迭代次数,可以控制算法在达到足够准确的结果和避免过度计算之间做出权衡。
总结来说,`term_crit` 参数定义了CamShift算法的终止条件,确保算法在一定数量的迭代后或者当进一步迭代不会产生显著改进时停止。这有助于提高算法的性能,防止无谓的计算。
cv.calcBackProject
dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
`dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)` 这行代码用于计算反向投影(back projection),这是CamShift算法中的一个重要步骤。反向投影是一种统计方法,用于图像中每个像素属于特定颜色分布(由直方图定义)的概率。让我们详细解释一下这行代码的各个部分:
-
`[hsv]`:这是一个包含HSV图像的列表。`hsv`是从原始BGR图像转换得到的HSV格式图像。反向投影将在这张图像上进行。
-
`[0]`:这个参数指定了反向投影计算的通道。在这里,`[0]`表示我们只考虑色调(Hue)通道。
-
`roi_hist`:这是之前计算的感兴趣区域(ROI)的色调直方图。这个直方图包含了ROI中颜色分布的统计信息。
-
`[0,180]`:这个参数指定了反向投影计算的色调值范围。在OpenCV中,色调值的范围通常是0到179,但这里我们使用0到180来覆盖整个色调范围。
-
`1`:这个参数表示反向投影的尺度因子。尺度因子用于控制结果图像的分辨率。值越大,结果图像的分辨率越低。在这里,我们使用1,意味着结果图像将与输入图像有相同的分辨率。
反向投影的计算过程如下:
-
对于输入图像中的每个像素,计算它与直方图(`roi_hist`)中定义的颜色分布的匹配程度。
-
将匹配程度作为该像素的强度值,构建一个新的图像(`dst`)。
-
在这个新图像中,亮度较高的区域表示该区域的像素颜色与直方图中定义的颜色分布匹配度较高,因此是跟踪算法寻找目标的好候选区域。
反向投影的结果通常用于CamShift算法中,以帮助确定目标对象的位置和形状。通过迭代地调整一个椭圆形状(称为搜索窗口),算法尝试找到与直方图最匹配的区域。
总之,`cv.calcBackProject` 函数创建了一个新的图像(`dst`),其中每个像素的值表示该像素与目标颜色分布的匹配程度。这个图像将被用作CamShift算法的输入,以找到并跟踪视频中的颜色对象。
cv.CamShift
ret, track_window = cv.CamShift(dst, track_window, term_crit)
执行CamShift算法后,这行代码返回两个值:
-
ret
:这是一个包含跟踪结果的元组,包括椭圆的中心、轴的长度、旋转角度等信息。 -
track_window
:这是更新后的跟踪窗口,包含了算法找到的最佳匹配区域的位置和大小。
cv.boxPoints
pts = cv.boxPoints(ret)
pts = np.int0(pts)
img2 = cv.polylines(frame,[pts],True, 255,2)
cv.imshow('img2',img2)
这段代码是在CamShift算法找到最佳匹配的旋转椭圆后,用于在原始视频帧上绘制表示该椭圆的边界。让我们逐行解释代码的作用:
-
pts = cv.boxPoints(ret)
:cv.boxPoints
函数将CamShift算法返回的旋转椭圆参数(由ret
给出)转换为椭圆的四个顶点。这些参数包括椭圆的中心、轴的长度和旋转角度。函数返回的pts
是一个包含这四个顶点坐标的数组。 -
pts = np.int0(pts)
:NumPy的np.int0
函数将顶点坐标的数据类型转换为整数。OpenCV在绘制函数中需要整数类型的坐标,因此这个转换是必需的。 -
img2 = cv.polylines(frame,[pts],True, 255,2)
:cv.polylines
函数在原始视频帧frame
上绘制由pts
定义的多边形(在这个场景中是一个近似椭圆的四边形)。参数[pts]
指定了多边形的顶点,True
表示多边形是闭合的,255
是线条的颜色(白色),2
是线条的厚度。 -
cv.imshow('img2',img2)
:cv.imshow
函数显示一个窗口,窗口名称为'img2'
,内容为绘制了椭圆边界的图像img2
。