目标追踪(object tracking)是指先给定视频的第一帧中的目标以及它的位置,之后不断的追踪目标,预测目标的轨迹。

1 目标跟踪的困难点

  • 形态变化 :姿态变化是目标跟踪中常见的干扰问题。运动目标发生姿态变化时,会导致它的特征以及外观模型发生改变,容易导致跟踪失败。比如体育比赛中的运动员、马路上的行人。
  • 尺度变化 :尺度的自适应也是目标跟踪中的关键问题。当目标尺度缩小时,由于跟踪框不能自适应跟踪,会将很多背景信息包含在内,导致目标模型的更新错误;当目标尺度增大时,由于跟踪框不能将目标完全包括在内,跟踪框内目标信息不全,也会导致目标模型的更新错误。因此, 实现尺度自适应跟踪是十分必要的。
  • 遮挡与消失 :目标在运动过程中可能出现被遮挡或者短暂的消失情况。当这种情况发生时,跟踪框容易将遮挡物以及背景信息包含在跟踪框内,会导致后续帧中的跟踪目标漂移到遮挡物上面。若目标被完全遮挡时,由于找不到目标的对应模型,会导致跟踪失败。
  • 图像模糊 :光照强度变化, 目标快速运动,低分辨率等情况会导致图像模型,尤其是在运动目标与背景相似的情况下更为明显。因此,选择有效的特征对目标和背景进行区分非常必要。

2 目标跟踪方法

按照时间顺序,目标跟踪的方法经历了从经典算法到基于核相关滤波算法,再到基于深度学习的跟踪算法的过程。

1)经典算法

经典跟踪算法主要有:光流法(利用视频序列在相邻帧之间的像素关系,寻找像素的位移变化来判断目标的运动状态)、Meanshfit(基于概率密度分布的跟踪方法,适用于目标的色彩模型和背景差异比较大的情形)、粒子滤波(基于粒子分布统计的方法,不对目标的特征建模,而是对目标的运动模型进行了建模,常用于估计目标在下一帧的位置)

早期的目标跟踪算法主要是根据目标建模或者对目标特征进行跟踪。

基于目标模型建模的方法是通过对目标外观模型进行建模,然后在之后的帧中找到目标。例如,区域匹配、特征点跟踪、基于主动轮廓的跟踪算法、光流法等。最常用的是特征匹配法,首先提取目标特征,然后在后续的帧中找到最相似的特征进行目标定位,常用的特征有:SIFT特征、SURF特征、Harris角点等。

经典算法的缺点比较明显:没有将背景信息考虑在内,导致在目标遮挡,光照变化以及运动模糊等干扰下容易出现跟踪失败;以及跟踪算法执行速度慢。

2)基于核相关滤波的跟踪算法

后来,人们将通信领域的相关滤波(衡量两个信号的相似程度)引入到了目标跟踪中。一些基于相关滤波的跟踪算法(比如MOSSE、KCF、BACF、SAMF等)也随之产生,速度可以达到数百帧每秒,可以广泛地应用于实时跟踪系统中。

3)基于深度学习的跟踪算法

随着深度学习方法的广泛应用,人们开始考虑将其应用到目标跟踪中。人们开始使用深度特征并取得了很好的效果,然后开始考虑用深度学习建立全新的跟踪框架,进行目标跟踪。

基于深度学习的目标跟踪大致可分为两类,一类是采用卷积神经网络提取目标特征,然后与其他跟踪方式进行融合来实现目标跟踪。另一类是训练出端到端的神经网络模型,目标跟踪的所有步骤均由神经网络来实现。

3 opencv 目标跟踪算法

1 )光流估计

光流:光的流动,比如人眼感受到的夜空中划过的流行。在计算机视觉中,定义图像中对象的移动,这个移动可以是相机移动或者物体移动引起的。具体是指视频图像的一帧中的代表同一对象(物体)像素点移动到下一帧的移动量,使用二维向量表示。

根据是否选取图像稀疏点进行光流估计,可以将光流估计分为稀疏光流和稠密光流。

opencv中提供了光流估计的接口,包括稀疏光流估计算法cv2.calcOpticalFlowPyrLK(), 和稠密光流估计算法cv2.calcOpticalFlowFarneback()。其中稀疏光流估计算法为Lucas-Kanade算法,该算法比较经典也比较容易理解。

为了将光流估计进行建模,Lucas-Kanade做了三个重要假设:

1 亮度恒定:同一点随着时间的变化,其亮度不会发生改变。

2 小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。

3 空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知量,所以需要联立n多个方程求解。

 opencv中提供的稀疏光流估计算法 cv2.calcOpticalFlowPyrLK()

参数说明:

prevImage:前一帧图像

nextImage:当前帧图像

prevPts:待跟踪的特征点向量(特征点可以用特征检测方法计算:Harris角点检测,SIFT关键点检测,shi-Tomasi角点检测等)

winSize:搜索窗口的大小

maxLevel:最大的金字塔层数

返回:

nextPts:输出跟踪特征点向量

status:特征点是否找到,找到的状态为1,未找到为0

光流估计法的重点是找到待跟踪的特征向量,以下用shi-Tomasi角点检测来找第一帧的特征点向量,示例视频为vtest.avi。

import cv2
import numpy as np

#读取视频
cap = cv2.VideoCapture('vtest.avi')

#读取第一帧图片,提取其特征点向量
ret,old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame,cv2.COLOR_BGR2GRAY)

#角点(特征点)检测 (shi-Tomasi角点检测)
old_pts = cv2.goodFeaturesToTrack(old_gray,maxCorners=100,qualityLevel=0.3,minDistance=10)
# print(old_pts)

#创建一个mask
mask = np.zeros_like(old_frame)
#随机颜色
color = np.random.randint(0,255,size=(100,3))
print(color[1].tolist())

while True:
    ret,frame = cap.read()
    if frame is None:
        break
        
    gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)

    #光流估计
    next_pts,status,err = cv2.calcOpticalFlowPyrLK(old_gray,gray,old_pts,None,winSize = (15,15),maxLevel=4)
#     print(len(next_pts))

    #哪些特征点找到了,哪些特征点没有找到
    good_new = next_pts[status == 1]
    good_old = old_pts[status == 1]
    print(good_new)
    
    #绘制特征点的轨迹
    for i,(new,old) in enumerate(zip(good_new,good_old)):
        x1,y1 = new
        x0,y0 = old
        mask = cv2.line(mask,(x1,y1),(x0,y0),color[i].tolist(),2)
        frame = cv2.circle(frame,(x1,y1),5,color[i].tolist(),-1)
    
    img = cv2.add(mask,frame)  #把轨迹和当前帧图片融合
    
    cv2.imshow('mask',mask)
    cv2.imshow('video',img)
    key =cv2.waitKey(100)
    if key == ord('q'):
        break
        
    #更新
    old_gray = gray.copy()
    old_pts = good_new.reshape(-1,1,2)   #要把 good_new 的维度变回old_pts一样
    
cap.release()
cv2.destroyAllWindows()

 可以看出,这种算法的缺陷主要有两个:一是跟踪的目标被遮挡后,跟踪会丢失;二是如果不是第一帧出现的人物,不会被标记跟踪。

当然,除了使用shi-Tomasi角点检测来找到待跟踪的特征向量,还可以用SIFT特征、SURF特征等,但是要注意这些算法返回的不一定是需要的特征向量,而是keypoint对象。

使用SIFT关键点检测来找到待跟踪的特征向量如下:

import cv2
import numpy as np

#读取视频
cap = cv2.VideoCapture('cars.mp4')

#读取第一帧图片,提取其特征点向量
ret,old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame,cv2.COLOR_BGR2GRAY)

#创建sift对象  (SIFT关键点检测)
sift = cv2.xfeatures2d.SIFT_create(nfeatures=100)  #只需要100个关键点,不指定nfeatures的话关键点太多
#进行检测
kpoint = sift.detect(old_gray)
old_pts = cv2.KeyPoint_convert(kpoint)  #把关键点转换为坐标
old_pts = old_pts.reshape(-1,1,2)  #维度变换

#创建一个mask
mask = np.zeros_like(old_frame)
#随机颜色
color = np.random.randint(0,255,size=(100,3))
print(color[1].tolist())

while True:
    ret,frame = cap.read()
    if frame is None:
        break
        
    gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)

    #光流估计
    next_pts,status,err = cv2.calcOpticalFlowPyrLK(old_gray,gray,old_pts,None,winSize = (15,15),maxLevel=4)
#     print(len(next_pts))

    #哪些特征点找到了,哪些特征点没有找到
    good_new = next_pts[status == 1]
    good_old = old_pts[status == 1]
#     print(good_new)
    
    #绘制特征点的轨迹
    for i,(new,old) in enumerate(zip(good_new,good_old)):
        x1,y1 = new
        x0,y0 = old
        mask = cv2.line(mask,(x1,y1),(x0,y0),color[i].tolist(),2)
        frame = cv2.circle(frame,(x1,y1),5,color[i].tolist(),-1)
    
    img = cv2.add(mask,frame)  
    
    cv2.imshow('mask',mask)
    cv2.imshow('video',img)
    key =cv2.waitKey(100)
    if key == ord('q'):
        break
        
    #更新
    old_gray = gray.copy()
    old_pts = good_new.reshape(-1,1,2)  #要把 good_new 的维度变回old_pts一样
    
cap.release()
cv2.destroyAllWindows()

 

 2)opencv的常用目标追踪算法

opencv上面有8种不同的目标追踪算法:

BOOSTING Tracker:和Harr cascades(AdaBoost)背后所用的机器学习算法相同,比较老的算法,追踪速度慢,表现不好。

MIL Tracker:比上一个追踪更精确,但是失败率高。

KCF Tracker:比上面两个都快,但是在有遮挡的时候表现不佳。

CSRT Tracker:比KCF更精确,但是速度慢一些。

MedianFlow Tracker:出色的跟踪故障报告。运动是可预测且没有遮挡的时候,表现很好。但是对于快速跳动或快速移动的物体,模型会失效。

TLD Tracker:在多帧遮挡下效果最好,但是TLD误报非常多。

MOSSE Tracker:速度非常快,但是不如CSRT和KCF那么精确,准确率稍低。

GOTURN Tracker:opencv中唯一一个以深度学习为基础的目标检测器,需要额外的模型才能运行。

opencv目标跟踪算法使用步骤:

  1. 创建MultiTracker对象。
  2. 读取视频或摄像头数据。
  3. 框选ROI区域。
  4. 给MultiTracker对象添加实际的追踪算法。
  5. 对每一帧进行目标追踪。

前面7种目标追踪算法实例如下:

import cv2
import numpy as np

#定义opencv中的七种目标追踪算法
OPENCV_OBJECT_TRACKERS = {
    'boosting':cv2.TrackerBoosting_create,
    'csrt':cv2.TrackerCSRT_create,
    'kcf':cv2.TrackerKCF_create,
    'mil':cv2.TrackerMIL_create,
    'tld':cv2.TrackerTLD_create,
    'medianflow':cv2.TrackerMedianFlow_create,
    'mosse':cv2.TrackerMedianFlow_create
}

trackers = cv2.MultiTracker_create()  #创建MultiTracker对象

cap = cv2.VideoCapture('D:/videos/los_angeles.mp4')

while True:
    ret,frame = cap.read()
    
    if frame is None:
        break
    #绘制追踪到的矩形区域(要在imshow之前)
    success,boxes = trackers.update(frame)
    
    for box in boxes:  #显示追踪框
        (x,y,w,h) = [int(v) for v in box] #box是浮点型,画图需要整型
        cv2.rectangle(frame,(x,y),(x+w,y+h),[0,0,255],2)
    
    cv2.imshow('viedo',frame)
    
    key = cv2.waitKey(100)
    
    if key == ord('s'):  #按s选择需要追踪的目标
        roi = cv2.selectROI('viedo',frame,showCrosshair = False)
        #创建一个实际的目标追踪器
        tracker = OPENCV_OBJECT_TRACKERS['tld']()
        trackers.add(tracker,frame,roi)
        
    if key == 27:
        break

cap.release()
cv2.destroyAllWindows()

  

 

按s键选择待追踪目标,然后按任意键进行目标追踪,可以尝试上面的7种目标跟踪算法,根据具体情况选择合适的算法。