目标
- 本节要学习使用 Meanshift 和 Camshift 算法在视频中找到并跟踪
Meanshift
Meanshift 算法的基本原理是和很简单的。 假设有一堆点(比如直方图反向投影得到的点), 和一个小的圆形窗口, 要完成的任务就是将这个窗口移动到最大灰度密度处(或者是点最多的地方)。 如下图所示:
初始窗口是蓝色的“C1”,它的圆心为蓝色方框“C1_o”,
而窗口中所有点质心却是“C1_r”(小的蓝色圆圈),很明显圆心和点的质心没有重合。
所以移动圆心 C1_o
到质心 C1_r
,这样就得到了一个新的窗口。
这时又可以找到新窗口内所有点的质心,大多数情况下还是不重合的,
所以重复上面的操作:将新窗口的中心移动到新的质心。
就这样不停的迭代操作直到窗口的中心和其所包含点的质心重合为止(或者有一点小误差)。
按照这样的操作窗口最终会落在像素值(和)最大的地方。
如上图所示“C2”是窗口的最后位址,可以看出来这个窗口中的像素点最多。
整个过程如下图所示:
通常情况下要使用直方图方向投影得到的图像和目标对象的起始位置。
当目标对象的移动会反映到直方图反向投影图中。 就这样,meanshift 算法就把窗口移动到图像中灰度密度最大的区域了。
OpenCV 中的 Meanshift
要在 OpenCV 中使用 Meanshift 算法首先要对目标对象进行设置,
计算目标对象的直方图,
这样在执行 meanshift 算法时就可以将目标对象反向投影到每一帧中去了。
另外还需要提供窗口的起始位置。
在这里值计算 H(Hue)通道的直方图,同样为了避免低亮度造成的影响,
使用函数 cv2.inRange()
将低亮度的值忽略掉。
import numpy as np
import cv2
cap = cv2.VideoCapture('slow.flv')
# take first frame of the video
ret,frame = cap.read()
# setup initial location of window
r,h,c,w = 250,90,400,125 # simply hardcoded the values
track_window = (c,r,w,h)
# set up the ROI for tracking
roi = frame[r:r+h, c:c+w]
hsv_roi = cv2.cvtColor(frame, 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)
# Setup the termination criteria, either 10 iteration or move by atleast 1 pt
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# apply meanshift to get the new location
ret, track_window = cv2.meanShift(dst, track_window, term_crit)
# Draw it on image
x,y,w,h = track_window
img2 = cv2.rectangle(frame, (x,y), (x+w,y+h), 255,2)
cv2.imshow('img2',img2)
k = cv2.waitKey(60) & 0xff
if k == 27:
break
else:
cv2.imwrite(chr(k)+".jpg",img2)
else:
break
cv2.destroyAllWindows()
cap.release()
[ WARN:0@423.625] global ./modules/videoio/src/cap_gstreamer.cpp (1127) open OpenCV | GStreamer warning: Error opening bin: unexpected reference "slow" - ignoring [ WARN:0@423.625] global ./modules/videoio/src/cap_gstreamer.cpp (862) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created [ERROR:0@423.625] global ./modules/videoio/src/cap.cpp (164) open VIDEOIO(CV_IMAGES): raised OpenCV exception: OpenCV(4.6.0) ./modules/videoio/src/cap_images.cpp:253: error: (-5:Bad argument) CAP_IMAGES: can't find starting number (in the name of file): slow.flv in function 'icvExtractPattern'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In [3], line 14 11 track_window = (c,r,w,h) 13 # set up the ROI for tracking ---> 14 roi = frame[r:r+h, c:c+w] 15 hsv_roi = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) 16 mask = cv2.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.))) TypeError: 'NoneType' object is not subscriptable
下面是使用 meanshift 算法对一个视频前三帧分析的结果:
Camshift
认真看上面的结果了吗?这里面还有一个问题。窗口的大小是固定的, 而汽车由远及近(在视觉上)是一个逐渐变大的过程,固定的窗口是不合适的。 所以需要根据目标的大小和角度来对窗口的大小和角度进行修订。
OpenCVLabs 带来的解决方案(1988 年):一个被叫做 CAMshift 的算法。
这个算法首先要使用 meanshift,meanshift 找到(并覆盖)目标之后,再去调整窗口的大小,
。它还会计算目标对象的最佳外接椭圆的角度,并以此调节窗口角度。
然后使用更新后的窗口大小和角度来在原来的位置继续进行 meanshift。重复这个过程知道达到需要的精度。
OpenCV 中的 Camshift
与 Meanshift 基本一样,但是返回的结果是一个带旋转角度的矩形(这是结果), 以及这个矩形的参数(被用到下一次迭代过程中)。下面是代码:
import numpy as np
import cv2
cap = cv2.VideoCapture('slow.flv')
# take first frame of the video
ret,frame = cap.read()
# setup initial location of window
r,h,c,w = 250,90,400,125 # simply hardcoded the values
track_window = (c,r,w,h)
# set up the ROI for tracking
roi = frame[r:r+h, c:c+w]#需要使用Inteage类型
hsv_roi = cv2.cvtColor(frame, 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)
# Setup the termination criteria, either 10 iteration or move by atleast 1 pt
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# apply meanshift to get the new location
ret, track_window = cv2.CamShift(dst, track_window, term_crit)
# Draw it on image
pts = cv2.boxPoints(ret)
pts = np.int0(pts)
img2 = cv2.polylines(frame,[pts],True, 255,2)
cv2.imshow('img2',img2)
k = cv2.waitKey(60) & 0xff
if k == 27:
break
else:
cv2.imwrite(chr(k)+".jpg",img2)
else:
break
cv2.destroyAllWindows()
cap.release()
[ WARN:0@0.106] global ./modules/videoio/src/cap_gstreamer.cpp (1127) open OpenCV | GStreamer warning: Error opening bin: unexpected reference "slow" - ignoring [ WARN:0@0.106] global ./modules/videoio/src/cap_gstreamer.cpp (862) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created [ERROR:0@0.131] global ./modules/videoio/src/cap.cpp (164) open VIDEOIO(CV_IMAGES): raised OpenCV exception: OpenCV(4.6.0) ./modules/videoio/src/cap_images.cpp:253: error: (-5:Bad argument) CAP_IMAGES: can't find starting number (in the name of file): slow.flv in function 'icvExtractPattern'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In [2], line 14 11 track_window = (c,r,w,h) 13 # set up the ROI for tracking ---> 14 roi = frame[r:r+h, c:c+w]#需要使用Inteage类型 15 hsv_roi = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) 16 mask = cv2.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.))) TypeError: 'NoneType' object is not subscriptable
对三帧图像分析的结果如下:
练习
- OpenCV 的官方示例中有一个 camshift 的交互式演示, 是一个展示基于均值漂移的跟踪的演示。 选择一个颜色对象(例如您的脸)并对其进行跟踪。 这将从摄像机读取(默认为0,或用户输入的摄像机编号) http://www.robinhewitt.com/research/track/camshift.html。
用法:
camshift.py
[<视频源>]
要初始化跟踪,请用鼠标选择对象。
按键:
- ESC:退出
- b:切换背投概率可视化
import numpy as np
import cv2
# local module
import video
class App(object):
def __init__(self, video_src):
self.cam = video.create_capture(video_src)
ret, self.frame = self.cam.read()
cv2.namedWindow('camshift')
cv2.setMouseCallback('camshift', self.onmouse)
self.selection = None
self.drag_start = None
self.tracking_state = 0
self.show_backproj = False
def onmouse(self, event, x, y, flags, param):
x, y = np.int16([x, y])
if event == cv2.EVENT_LBUTTONDOWN:
self.drag_start = (x, y)
self.tracking_state = 0
# 官方示例中下面一行判断有问题,作如下修改就可以了
if self.drag_start and event == cv2.EVENT_MOUSEMOVE:
# print x,y
if flags==cv2.EVENT_FLAG_LBUTTON:
# print 'ok'
h, w = self.frame.shape[:2]
xo, yo = self.drag_start
x0, y0 = np.maximum(0, np.minimum([xo, yo], [x, y]))
x1, y1 = np.minimum([w, h], np.maximum([xo, yo], [x, y]))
self.selection = None
if x1-x0 > 0 and y1-y0 > 0:
self.selection = (x0, y0, x1, y1)
print self.selection
else:
self.drag_start = None
if self.selection is not None:
self.tracking_state = 1
def show_hist(self):
bin_count = self.hist.shape[0]
bin_w = 24
Cell In [1], line 6 def __init__(self, video_src): ^ IndentationError: expected an indented block after class definition on line 5