OpenCV-Python Meanshift 和 Camshift

在本章中,

  • 我们将学习如何让利用 Meanshift 和 Camshift 算法用以在视频中寻找并跟踪对象

译者注:

Meanshift 中译均值漂移聚类, Camshift 则是连续自适应 Meanshift 算法,
以下均用英文表示这两个名词。

Meanshift

Meanshift 背后的直观感受是很简单的。想象一下,你有一堆的随机散列点(这些点可以是像直方图反向投影那样的像素分布)。同时你拥有一个小窗口(或许是一个圆圈),现在你需要移动这个窗口直到窗口内的像素密度最大(或者说所含点的数量最多)。这在下面这幅图像中是很容易说明的:

Meanshift

meanshift image

起始窗口“C1”如蓝色圆圈所示,其中心区域被蓝色矩形所标记,且命名为“C1_o”。但是,如果你寻找那个窗口的质心,你将会得到点“C1_r”(用蓝色小圆圈标记),它是窗口真正的质心。显而易见它们并不匹配。因此移动窗口似的新窗口的中心与之前的质心相匹配,此时将再次找到新的质心。最有可能的是,质心与中心两个依旧不匹配。因此,再次移动窗口,并持续迭代下去,使得窗口中心与质心落在同一位置(或者期望误差较小)。所以最终你获得的便是一个包含有最大像素分布的窗口。将其标注为绿色圆圈,命名为“C2”。正如你在图片中看到的那样,这个窗口所包含的点的数量也是最多的。整个过程在下面的动图中有演示:

Meanshift

meanshift face image

所以我们通常会传递直方图反向投影图像和起始目标位置。当物体移动的时候,显然运动将会反应在直方图的反向投影图像中。最终,meanshift 算法将窗口移动到具有最大密度的新位置。

在 opencv 中使用 Meanshift

为了在 OpenCV 中使用 meanshift,首先我们需要设置目标,寻找其直方图以便我们可以对于反向投影每一帧的直方图用以利用 Meanshift 算法进行计算。我们还需要提供窗口的起始位置。对于直方图,在此时仅需考虑色相。此外,为了避免由于低亮度引起的错误值,使用cv2.inRange()函数丢弃低亮度值。

import numpy as np
import cv2 as cv

cap = cv.VideoCapture('slow.flv')

# 获取视频的第一帧
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)

        # 运行 meanshift 算法用以获取新的位置
        ret, track_window = cv.meanShift(dst, track_window, term_crit)

        # 绘制到新图像中
        x,y,w,h = track_window
        img2 = cv.rectangle(frame, (x,y), (x+w,y+h), 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()

我使用的视频中的三个帧如下:

在 opencv 中使用 Meanshift

meanshift result image

Camshift

你细致的观察最后的结果了吗?这里其实有一个问题。就是无论汽车离得摄像机很远还是很近我们的窗口大小总是一样的。这并不好。我们需要根据目标的大小和旋转来适应调整窗口大小。该解决方案又一次来自“OpenCV 实验室”,这个算法被称为 CAMshift(连续自适应 Meanshift 算法),并由 Gary Bradsky 于 1998 年在他的论文“用于感知用户界面的计算机视觉面部跟踪”中发布。

这个算法首先利用了 meanshift。一旦 meanshift 收敛,他将自动将窗口的大小更新为s = 2 \times \sqrt{\frac{M_{00}}{256}}。并且寻找出最佳拟合椭圆的方向。同样的,在缩放后的窗口和先前窗口都会应用 meanshift 算法。该过程将持续到精确度满足要求。

Camshift

camshift face image

在 OpenCV 里使用 Camshift

它大部分与 meanshift 相同,但是其返回值是一个旋转的矩阵(这是我们得出的结果)和 box 参数(用于在下一次迭代中搜索窗口传递)

代码如下:

import numpy as np
import cv2 as cv

cap = cv.VideoCapture('slow.flv')

# 获取视频的第一帧
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()

我使用的视频中的三个帧如下:

在 OpenCV 里使用 Camshift

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程