主题

之前我们在 (Python)从零开始,简单快速学机器仿人视觉Opencv—运用三:物体运动跟踪 中已经学习了关于Meanshift和Camshift的运算,本章节将对其进行更好的优化。 没有看过之前章节内容的读者不用着急,为了方便观看,已经将前提章节中的内容移植了过来,第一看我写的文章的可以顺着往下读;如果是老读者的话,可以直接跳到下方 “优化” 区域,进行继续阅读。  

均值漂移(Meanshift)

说到物体跟踪,首先我们需要了解什么是均值漂移,该算法是一种寻找概率函数离散样本的最大密度区域的算法,我们可以认为我们图像中感兴趣的区域就是离散样本密度最大的区域(这句话看不懂没关系,先往下看,后面就明白了)   首先我们要有一个均值漂移的基本概念:这个算法会向我们需要跟踪的区域的方向那慢慢移动过去。 设想在一个有N个样本点的特征空间 初始确定一个中心点center(随便选一个),计算在设置的半径为D的圆形空间内所有的点(xi)与中心点center的向量 计算整个圆形空间内所有向量的平均值,得到一个偏移均值 将中心点center移动到偏移均值所指向的位置 重复移动,直到没有办法继续增加圈内的点或移动距离过小后结束   整个过程如下图所示,points的值为圈内点的数量:   微信图片_20201111222450   使用这种算法,就能让我们的圆逐步向点多的地方走去,这就是均值漂移的基本概念。  

彩色直方图

接下来我们需要学习彩色直方图的概念。

 

彩色直方图的x轴表示色彩的值,y轴表示这个色彩的像素有多少个,灰度图因为只有一个通道所以只有一个直方图,但如果是HSV或者BGR这种的,就可以对每一个通道进行直方图的构建,这里我们只需要对HSV图像的H通道进行跟踪即可。(因为我们接下来是根据普通的颜色来进行的跟踪,一个通道就足够了)

 

彩色直方图的输出如下图所示(对不同通道使用出来的直方图也不同,这张图并不是H通道的直方图,只是让大家明白直方图长啥样):

  微信图片_20201111222532   构建图像的直方图需要使用到函数cv2.calcHist,其常用函数语法如下所示:  
hist=cv2.calcHist(images, channels, mask, histSize, ranges) 

images:输入的图像
channels:选择图像的通道,如果是三通道的话就可以是[0],[1],[2]
mask:掩膜,是一个大小和image一样的np数组,其中把需要处理的部分指定为1,不需要处理的部分指定为0,一般设置为None,如果有mask,会先对输入图像进行掩膜操作
histSize:使用多少个bin(柱子),一般为256,但如果是H值就是181
ranges:像素值的范围,一般为[0,255]表示0~255,对于H通道而言就是[0,180]
  需要注意的是,这里除了mask以外,其余的几个参数都要加上[],如下所示:  
hist=cv2.calcHist([img],[0],mask,[181],[0,180]) 
  这个时候我们还需要使用一种归一化的方法来对彩色直方图中的数量值进行规范化。   现有的直方图中的数值为对应像素的数量,其中图中出现数量最多的像素的数量值(最高的柱子对应的y轴数值)我们记为max的话,整个直方图y方向上的取值范围就是[0,max],我们需要把这个范围缩减到[0,255],为什么是255后面会进行解释。  

归一化:cv2.normalize

这里我们需要使用到cv2.normalize函数,函数主要语法如下所示:  
cv2.normalize(src,dst, alpha,beta, norm_type)
·src-输入数组。
·dst-与SRC大小相同的输出数组。
·α-范数值在范围归一化的情况下归一化到较低的范围边界。
·β-上限范围在范围归一化的情况下;它不用于范数归一化。
·范式-规范化类型(见下面详细介绍)。
  这里我们需要注意的是范式-规范化类型,这里有以下几种选择。  
NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围,线性归一化。
NORM_INF:归一化数组的(切比雪夫距离)L∞范数(绝对值的最大值)
NORM_L1: 归一化数组的(曼哈顿距离)L1-范数(绝对值的和)
NORM_L2: 归一化数组的(欧几里德距离)L2-范数
  上面的名词看起来很高大上,其实是很简单,我们一一讲解下。(不是很感兴趣的只要看下第一个NORM_MINMAX即可,剩下的三个可以不看)   首先是NORM_MINMAX,这个是我们最常用的一种归一化方法。举个例子,我们上面提到的最高的柱子对应的y轴坐标为max,如果我们使用这种方法,想要缩放到的指定的范围为[0,255],那么max就会直接被赋值为255,其余的柱子也会随之一样被压缩(类似于相似三角形那样的缩放感觉)。   没错,很简单得就介绍完了一种,不是很想了解其他几个的读者可以直接跳过本小节剩下来的内容了,因为剩下三种不是很常用。   接下来是NORM_INF,他会对我们每一个柱子的y轴坐标进行如下操作:用当前柱子的y轴坐标,除以所有柱子中y值的绝对值最大的那个作为新的y轴的值,公式如下所示(借几张图,自己做有点麻烦(咕咕咕))。   微信图片_20201111222727   接下来是NORM_L1,他会对我们每一个柱子的y轴坐标进行如下操作:用当前柱子的y轴坐标,除以(所有柱子的y值的和)的绝对值作为新的y轴的值,公式如下所示:   微信图片_20201111222749   接下来是NORM_L2,他会对我们每一个柱子的y轴坐标进行如下操作:用当前柱子的y轴坐标,除以(所有柱子的y值的平方和)的根号作为新的y轴的值,公式如下所示:   微信图片_20201111222813  

直方图反投影

之前我们最上面提到过一个叫做“离散样本密度”的玩意儿,我们现在来更加具体化的讲解下。   这里我们需要名为“直方图反投影”的一些知识,简单来说,它会输出与输入图像(待搜索)同样大小的图像,其中的每一个像素值代表了输入图像上对应点属于目标对象(我们需要跟踪的目标)的概率。用更简单的话来解释,输出图像中像素值越高(越白)的点就越可能代表我们要搜索的目标 (在输入图像所在的位置)。   而对于灰度图而言,其只有一个通道,取值范围为0到255,所以我们之前在归一化的时候将直方图的y轴坐标的取值范围压缩到了0-255的范围内,就是为了这里可以直接赋值。   上面一段可能讲的还是有些含糊,下面我举一个例子来说明:   微信图片_20201111222904   我们假设上面这张图就是我们要跟踪的对象所对应的彩色直方图(一个很大的跟踪对象由很多种不同的像素组成,然后我们将他们统计一下,每一种颜色分别对应着几个像素,有的颜色可能有很多像素,有的颜色可能一个也没有)。 上图左边红色的那个突出的柱子对应的是最大y轴值,他有100000个点,其他的柱子对应的像素的数目就是在[0,100000]之间,然后我们将彩色直方图归一化到[0,255]之间后,红色突出的柱子对应的最大y轴值就变成了255,其他的颜色对应的y轴的值也一一等比例改变到了0-255之间,这个我们要跟踪物体所构成的归一化彩色直方图我们称为Hist。   然后我们对我们要处理的图像(即包含我们要跟踪的对象的整个图片)根据我们上面得到的Hist来判断要处理的图像中的每一个像素是否属于我们跟踪对象或者说属于我们跟踪对象的概率有多大。   例如,我们要处理的图像中有一个点为红色,那么他就会去看我们跟踪对象的直方图Hist,发现直方图中的红色对应的属于跟踪对象的可能性(y轴的值)为255,则就会直接赋值为255(纯白色);如果要处理的图像中有一个点为棕色,然后去看直方图,发现发现直方图中的棕色对应的属于跟踪对象的可能性(y轴的值)为0,就会直接赋值为0(黑色),由此构成我们的直方图反投影图。   所以我们最后得到的图像就是一个与原图同样大小的灰度图像。例如我跟踪了下面绿色部分的物体,得到绿色部分的彩色直方图后,其对应的直方图反投影图就如下所示:   微信图片_20201111222925   微信图片_20201111222941   越暗的地方说明属于跟踪部分的可能性越低,越亮的地方属于跟踪部分的可能性越高。   这里使用到的函数为cv2.calcBackProject,函数语法如下所示:  
dst=cv2.calcBackProject(image,channel,hist,range,scale)

image:输入图像
channel:用来计算反向投影的通道数,与产生直方图对应的通道应一致
hist:作为输入的直方图
range:直方图的取值范围
scale:输出图像的缩放比,一般为1,保持与输入图像一样的大小
dst:输出图像

注意:除了hist和scale外,其他的参数都要加上[]
  例如:  
dst=cv2.calcBackProject([hsv],[0],hist,[0,180],1)
 

meanshift跟踪实现程序

上述讲述的都是关于meanshift(均值漂移)的跟踪方法(其实Camshift也是这个样子的原理)。现在有了一切的基础知识后,我们就可以进行物体跟踪的实现了,这里我们打算跟踪一个绿色的物体来看看,首先我们载入我们要处理的视频文件(或者直接用摄像头也行):  
import cv2
import numpy as np
cap=cv2.VideoCapture('1.mp4')
  然后我们设置下我们第一个起始框的位置和长宽(可以理解为上面均值漂移原理中起始圆的起始位置和圆的大小)  
#r为rows,c为columns,h为height,w为weight
r,h,c,w=(400,500,400,500)
#跟踪框设置
track_window=(c,r,w,h)
  然后我们读取第一帧,先将图像转换为HSV图,方便目标跟踪,然后通过掩膜操作来得到图像中的绿色部分的掩膜:  
ret,frame=cap.read()
hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
#绿色物体的HSV上下限范围为(35,43,46),(77,255,255)
mask=cv2.inRange(hsv,np.array((35,43,46)),np.array((77,255,255)))
  (不同颜色的hsv上下限可以点击这个网址)   接着我们使用cv2.calcHist函数来得到图像中的绿色部分,并计算这部分的直方图,我们只要收集第0通道:H的数据就好了,因为是H通道,其取值范围为0-180,所以需要181根柱子,H通道像素的取值为0-180(柱子数量不是与像素取值范围不能一一对应的话,柱与柱之间会有点压缩,即x轴方向上会产生柱子与柱子之间的融合):  
hist=cv2.calcHist([hsv],[0],mask,[181],[0,180])
cv2.normalize(hist,hist,0,255,cv2.NORM_MINMAX)
  然后我们来设置均值漂移meanshift的一次活动的终止条件: cv2.TERM_CRITERIA_EPS:代表一次均值漂移累计的移动次数,EPS表示epsilon,这里我们设置为10。 cv2.TERM_CRITERIA_COUNT:表示一次均值漂移移动的最小偏移像素,如果一次漂移的像素值低于这个值,就会终止这次活动,这里我们设置为1。  
term_crit=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,10,1)
  这里可能有读者会有点疑惑为什么要加上这个终止条件,其实这是为了让视频处理(摄像头)变得流畅而设置的,不然如果不设置这个条件,对于每一帧传入的图像,meanshift都要找到当前图像中最好的地方(密度最大的地方),然后才会开始处理下一帧,这样图像看起来就会变得异常卡顿。我们程序也没必要每一帧都完完全全处在跟踪物体的最佳位置上,容许有些许的偏差,只要能极大部分跟踪到了物体就可以了。 然后我们在第一帧中得到了要跟踪的物体的颜色直方图后,我们开始处理图像中的后续帧:  
while 1:
    ret,frame=cap.read()
    if ret== True:
    	#将整个画面转为HSV型
        hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
        #计算对于hist而言的直方图反向投影,得到概率分布图像dst
        dst=cv2.calcBackProject([hsv],[0],hist,[0,180],1)
        #让我们设置的跟踪框从初始位置开始根据概率分布密度进行移动
        #终止条件为之前设置的term_crit
        ret,track_window=cv2.meanShift(dst,track_window,term_crit)
        #得到新的跟踪框位置
        x,y,w,h=track_window
        #画出来
        img=cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
        cv2.namedWindow('img',cv2.WINDOW_NORMAL)
        cv2.imshow('img',img)
        if cv2.waitKey(1)==ord('q'):
            break
    else:
        break
cap.release()
cv2.destroyAllWindows()
  然后我们就能够实现绿色物体的跟踪了,运行结果如下所示:   微信图片_20201111223208   完整代码如下所示:  
import cv2
import numpy as np
cap=cv2.VideoCapture('1.mp4')
ret,frame=cap.read()
r,h,c,w=(400,500,400,500)
#跟踪框
track_window=(c,r,w,h)
#获得绿色的直方图
hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
mask=cv2.inRange(hsv,np.array((35,43,46)),np.array((77,255,255)))
hist=cv2.calcHist([hsv],[0],mask,[181],[0,180])
cv2.normalize(hist,hist,0,255,cv2.NORM_MINMAX)
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],hist,[0,180],1)
        ret,track_window=cv2.meanShift(dst,track_window,term_crit)
        x,y,w,h=track_window
        img=cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
        cv2.namedWindow('img',cv2.WINDOW_NORMAL)
        cv2.imshow('img',img)
        if cv2.waitKey(1)==ord('q'):
            break
    else:
        break
cap.release()
cv2.destroyAllWindows()
   

Camshift跟踪

但我们可以注意到的是,我们矩形框的大小是我们一开始就直接设置好的,在整个跟踪过程中其大小是不会改变的(不管我们的跟踪物体是否变小或者变大了) 为了解决这个问题,我们可以在meanshift的基础上,让他自适应跟踪物体的大小来调整矩形框的大小,这就是Camshift。CamShift算法的全称是"Continuously Adaptive Mean-SHIFT",称为连续自适应的meanshift算法,算法部分不变,只是能让他能够自我适应跟踪物体大小而已。 代码方面也和meanshift差不多,只要在while循环里改几行就可以了:  
#之前的全部不变
while 1 :
    ret,frame=cap.read()
    if ret == True:
        hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
        dst=cv2.calcBackProject(hsv,[0],hist,[0,180],1)
        #下面开始改变,方法改为Camshift
        ret,track_window=cv2.CamShift(dst,track_window,term_crit)
        #注意这里不是用的track_window而是ret!
        boxes=cv2.boxPoints(ret)
        #因为要划线,所以下标必须为整形,所以将点的类型转为int
        pts=np.int0(boxes)
        #开始划线
        img2=cv2.polylines(frame,[pts],True,(0,255,0),2)
        #之后全部不变
        cv2.imshow('img2',img2)
        if cv2.waitKey(1)==ord('q'):
            break
    else:
        break
    然后运行结果中的绿色矩形框就能够根据跟踪的对象而自适应改变框的大小了(注意:这个Camshift很容易就会检测出错)  

优化(老读者可以直接从这里开始阅读)

在之前的章节中,我们学习了使用Meanshift和Camshift来进行物体运动的跟踪,但是不知道各位有没有注意到的是这里面其实有一个很不和谐的地方,那就是我们需要提前设置好我们跟踪框的初始位置。 初始框的位置很重要。以Meanshift为例,它的工作原理是根据概率密度来寻找最大的密度区域,但如果我们一开始将跟踪框放置在了一个直方图反向投影图中全黑的区域(密度为0),这会导致其无法正确向物体方向进行移动,从而导致卡死在那里。 我们已追踪视频的初始帧(第一帧)为例,我们假设想要跟踪其中的一个物体,我们就得将跟踪框放置到跟踪物体周边的区域才能让程序正常运行,但我们其实很难知道一张图片中跟踪物体的具体位置。   举个简单的例子,比如我现在有一张图片,我们要跟踪图片右下角的一个物体,但是我不知道这个物体的坐标范围,所以我只能一次一次的去尝试(在代码中修改初始框的位置后,看看程序的运行情况)来保证代码能够正常运行,但这样代码的普适性很差,因为每当要更改跟踪对象的时候,都需要反复的修改,才能应对当前的情况,这样实在是有点麻烦。   这里介绍一个函数,起名为:cv2.selectROI,使用这个函数,我们就能够实现手动画取我们的跟踪框,其函数语法如下所示:  
track_window=cv2.selectROI('frameName', frame)

·framename:显示窗口的画布名
·frame:具体的帧
·track_window:选定的跟踪框
  本块示例代码如下所示:  
import cv2
import numpy as np
cap=cv2.VideoCapture('1.mp4')
ret,frame=cap.read()
#我这里画面太大了所以缩小点
frame=cv2.resize(frame,None,None,fx=1/2,fy=1/2,interpolation=cv2.INTER_CUBIC)
#跟踪框
track_window=cv2.selectROI('img', frame)

#获得绿色的直方图
hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
mask=cv2.inRange(hsv,np.array((35,43,46)),np.array((77,255,255)))
hist=cv2.calcHist([hsv],[0],mask,[181],[0,180])
cv2.normalize(hist,hist,0,255,cv2.NORM_MINMAX)
term_crit=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,10,1)
while 1:
    ret,frame=cap.read()
    frame = cv2.resize(frame, None, None, fx=1 / 2, fy=1 / 2, interpolation=cv2.INTER_CUBIC)
    if ret== True:
        hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
        dst=cv2.calcBackProject([hsv],[0],hist,[0,180],1)
        ret,track_window=cv2.meanShift(dst,track_window,term_crit)
        x,y,w,h=track_window
        img=cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
        (x,y)=img.shape[:2]
        cv2.imshow('img',img)
        if cv2.waitKey(1)==ord('q'):
            break
    else:
        break
cap.release()
cv2.destroyAllWindows()
  运行上面的代码,会跳出一个窗口,窗口上显示的就是我们载入的视频的第一帧,我们用鼠标拖动,画出我们要跟踪的物体的位置:   微信图片_20201111223350   画出我们要跟踪的物体后,按下Esc键即可退出选框界面,接着开始物体的跟踪:   微信图片_20201111223419   但是这里需要注意的是,这个函数每次调用只能画一个矩形(C++版本中的OpenCV可以一次画好多个),如果想画好多个矩形的话,可以使用while循环:  
bboxes = []
colors = [] 
while 1:
    bbox = cv2.selectROI('MultiTracker', frame)
    bboxes.append(bbox)
    colors.append((randint(0, 255), randint(0, 255), randint(0, 255)))
    print("按下q键退出,按下其他键继续画下一个框")
    if cv2.waitKey(0) & 0xFF==ord('q'):
        break
print('选取的边框为{}'.format(bboxes))
 

卡尔曼滤波(Kalman)

在上面的跟踪中,我们使用的都是Meanshift或者是基于Meanshift的Camshift来进行的跟踪,这里我们还可以对其进行改良:引入卡尔曼滤波的概念。 卡尔曼滤波的概念较为复杂,这里我们可以理解为:从一个含有噪音的输入中,得出一个具有统计意义上较为良好的估计值。   我们举个简单的例子,比如我现在有一个物体,正在以1m/s的速度(过去测量得到的值)离我远去,当前它离我1m远,因为我们知道他正在以1m/s的速度远离我,所以下一秒我就能估计出它离我2m远,这个是显然的。但是如果这个物体撞到了什么东西后,速度改变了,我们就会根据它实际位置上的值(含有噪音的模糊输入)来矫正我们的猜测。 卡尔曼滤波一般我们设置为4个参数,其中两个是可以直接得到的。举个例子,我们使用函数来创建一个卡尔曼滤波器:  
klm=cv2.KalmanFilter(4,2)
  这里的4表示的是有四个参数分别是(x,y,dx,dy),前两个是我们输入进去的参数,物体跟踪里面我们假定为我们跟踪物体的中心的坐标,dx和dy表示的是物体移动的速度,为观测量,不能直接得到,所以其实我们能直接输入进去的参数就只有坐标,x和y两个值,所以初始化的时候4后面跟一个2,所以就是(4,2) 然后我们需要设置卡尔曼滤波器的几个矩阵:  
klm.measurementMatrix = np.array([[1,0,0,0],[0,1,0,0]],np.float32)
klm.transitionMatrix = np.array([[1,0,1,0],[0,1,0,1],[0,0,1,0],[0,0,0,1]],np.float32)
klm.processNoiseCov = np.array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],np.float32)
  这里我们来一一说明一下,首先是第一个measurementMatrix,测量矩阵,这里表示的是我们能直接测量得到的参数(x,y,dx,dy)中的前两位,所以只有[1,0,0,0],[0,1,0,0],来标记第一位和第二位。 第二个是transitionMatrix,状态转移矩阵,为[1,0,1,0],[0,1,0,1],[0,0,1,0],[0,0,0,1],表示的是(x+dx,y+dy),这里的含义其实就是算上速度后下一个时刻的估计。 最后一个是processNoiseCov,这个是在为预测进行修正卡尔曼滤波里面几个方程中的参数,依次参考的次序为x,y,dx,dy。   现在我们对上文优化的基础上,再加入卡尔曼滤波,我们把矩形框的中间位置作为输入,加载到卡尔曼滤波器中,预测出现在物体的位置后,以预测位置为圆心画一个圆,示例代码如下所示:  
import cv2
import numpy as np

def center(points):
    x=(points[0][0]+points[1][0]+points[2][0]+points[3][0])/4
    y = (points[0][1] + points[1][1] + points[2][1] + points[3][1]) / 4
    return np.array([np.float32(x),np.float32(y)],np.float32)

cap=cv2.VideoCapture('1.mp4')
ret,frame=cap.read()
#我这里画面太大了所以缩小点
frame=cv2.resize(frame,None,None,fx=1/2,fy=1/2,interpolation=cv2.INTER_CUBIC)
#跟踪框
track_window=cv2.selectROI('img', frame)

#获得绿色的直方图
hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
mask=cv2.inRange(hsv,np.array((35,43,46)),np.array((77,255,255)))
hist=cv2.calcHist([hsv],[0],mask,[181],[0,180])
cv2.normalize(hist,hist,0,255,cv2.NORM_MINMAX)
term_crit=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,10,1)

#设置卡尔曼滤波器
klm=cv2.KalmanFilter(4,2)
klm.measurementMatrix = np.array([[1,0,0,0],[0,1,0,0]],np.float32)
klm.transitionMatrix = np.array([[1,0,1,0],[0,1,0,1],[0,0,1,0],[0,0,0,1]],np.float32)
klm.processNoiseCov = np.array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],np.float32)
while 1:
    ret,frame=cap.read()
    frame = cv2.resize(frame, None, None, fx=1 / 2, fy=1 / 2, interpolation=cv2.INTER_CUBIC)
    if ret== True:
        hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
        dst=cv2.calcBackProject([hsv],[0],hist,[0,180],1)
        ret,track_window=cv2.meanShift(dst,track_window,term_crit)
        x,y,w,h=track_window
        
        #获得中间坐标
        cent = center([[x, y], [x + w, y], [x, y + h], [x + w, y + h]])
        #修正参数
        klm.correct(cent)
        #预测
        c = klm.predict()
        #画出预测位置
        cv2.circle(frame, (int(c[0]), int(c[1])), 30, (255, 255, 0), -1)
        #画出矩形框
        img=cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
        (x,y)=img.shape[:2]
        cv2.imshow('img',img)
        if cv2.waitKey(1)==ord('q'):
            break
    else:
        break
cap.release()
cv2.destroyAllWindows()
  运行后,选定我们要跟踪的物体的跟踪框后,按下Esc,运行结果如下所示:   微信图片_20201111223632   如果观察仔细点的话就会发现,这个圆球并非一直处在矩形框中间的位置,如果你加速向右移动的话,圆球就会处在矩形框偏右的位置上;如果你加速向左移动的话,圆球就会处在矩形框偏左的位置上,当速度趋于稳定的时候,又会回到矩形框中间的位置。由此我们可以看出的是,这个圆球的位置是与物体运动速度有关的,并且没有时间的累加性,即,即时在一段时间内不断的向右偏,如果速度回归正常,依旧会回到矩形框的中间位置,而不是在偏右的位置上停下,不回到中间。   由此可以看出,使用kalman+Meanshift的方法,可以使得跟踪更加准确。  

总结

本系列不定期更新,每月至少更新一篇,欢迎大家点波收藏关注,谢谢大家的支持啦。