Meanshift 和 Camshift

目标

在本章中,

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

译者注:

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

Meanshift

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

meanshift_basics

meanshift image

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

meanshift_face

meanshift face image

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

在 opencv 中使用 Meanshift

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

  1. import numpy as np
  2. import cv2 as cv
  3. cap = cv.VideoCapture('slow.flv')
  4. # 获取视频的第一帧
  5. ret,frame = cap.read()
  6. # 设置窗口的初始位置
  7. r,h,c,w = 250,90,400,125 # 简单地硬编码值
  8. track_window = (c,r,w,h)
  9. # 设置 ROI(图像范围)以进行跟踪
  10. roi = frame[r:r+h, c:c+w]
  11. hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
  12. mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
  13. roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
  14. cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
  15. # 设置结束标志,10 次迭代或至少 1 次移动
  16. term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
  17. while(1):
  18. ret ,frame = cap.read()
  19. if ret == True:
  20. hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
  21. dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
  22. # 运行 meanshift 算法用以获取新的位置
  23. ret, track_window = cv.meanShift(dst, track_window, term_crit)
  24. # 绘制到新图像中
  25. x,y,w,h = track_window
  26. img2 = cv.rectangle(frame, (x,y), (x+w,y+h), 255,2)
  27. cv.imshow('img2',img2)
  28. k = cv.waitKey(60) & 0xff
  29. if k == 27:
  30. break
  31. else:
  32. cv.imwrite(chr(k)+".jpg",img2)
  33. else:
  34. break
  35. cv.destroyAllWindows()
  36. cap.release()

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

meanshift_result

meanshift result image

Camshift

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

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

camshift_face

camshift face image

在 OpenCV 里使用 Camshift

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

代码如下:

  1. import numpy as np
  2. import cv2 as cv
  3. cap = cv.VideoCapture('slow.flv')
  4. # 获取视频的第一帧
  5. ret,frame = cap.read()
  6. # 设置窗口的初始位置
  7. r,h,c,w = 250,90,400,125 # 简单地硬编码值
  8. track_window = (c,r,w,h)
  9. # 设置 ROI(图像范围)以进行跟踪
  10. roi = frame[r:r+h, c:c+w]
  11. hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
  12. mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
  13. roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
  14. cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
  15. # 设置结束标志,10 次迭代或至少 1 次移动
  16. term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
  17. while(1):
  18. ret ,frame = cap.read()
  19. if ret == True:
  20. hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
  21. dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
  22. # 运行 Camshift 用以获取新的位置
  23. ret, track_window = cv.CamShift(dst, track_window, term_crit)
  24. # 绘制到新图像中
  25. pts = cv.boxPoints(ret)
  26. pts = np.int0(pts)
  27. img2 = cv.polylines(frame,[pts],True, 255,2)
  28. cv.imshow('img2',img2)
  29. k = cv.waitKey(60) & 0xff
  30. if k == 27:
  31. break
  32. else:
  33. cv.imwrite(chr(k)+".jpg",img2)
  34. else:
  35. break
  36. cv.destroyAllWindows()
  37. cap.release()

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

camshift_result

camshift result image

其他资源

  1. 关于Camshift的法语维基百科页面。(上面的两幅动图出自于此)
  2. Bradski, G.R.的 “Real time face and object tracking as a component of a perceptual user interface “论文

练习

  1. OpenCV 附带了一个关于 camshift 交互式演示的 Python 示例。使用它,拆解它,理解它。