目录
基于特征点的光流跟踪,在目标上提取一些特征点,然后在下一帧计算这些特征点的光流匹配点,统计得到目标的位置。在跟踪的过程中,需要不断补充新的特征点,删除置信度不佳的特征点,以此来适应目标在运动中的形状变化。本质上可以认为光流跟踪属于用特征点的集合来表征目标模型的方法。
一、光流法
光流:空间运动物体在观察成像平面上像素运动的瞬时速度。
光流法利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的一种方法。
通常将二维图像平面特定坐标点上的灰度瞬时变化率定义为光流矢量。
简单来说,光流就是瞬时速度,在时间间隔很小时,也等同于目标的位移,光流场是灰度图像的二维矢量场,它反映了图像上像素的变化趋势,可看成是带有灰度的像素点在图像平面上运动而产生的瞬时速度场,它包含的信息即是各像素点的瞬时运动速度矢量信息,既可以表现为物体运动的运动方向也可表现为物体运动的速率。

- 稀疏光流法LK
- 稠密光流法Gunner_Farnback
二、LK光流法
LK光流法有三个假设条件:
- 1:亮度恒定:对于灰度图像中运动目标,其像素点的亮度在相邻帧间不发生变化
- 2:时间连续或者运动足够小:在每次计算时,不会由于时间的变化而引起目标位置的剧烈变化,运动目标的像素点在相邻帧之间对应位置的变化比较小
- 3:空间一致:特征点附近所有相邻的像素点运动情况相似
LK光流法相比HS光流法避免了计算目标区域内所有像素点的光流,减小了运算开销,提高了程序的运行速度,但LK光流法的三个假设条件也会在应用稀疏光流法时产生一定的限制,比如当物体运动位移较大或者速度较快时,图像间的相关性较弱,并且较大的运动不满足泰勒公式的展开条件,导致无法求出最有匹配解而导致跟踪失败
为了避免大位移运动跟踪失败的情况,在较大的尺度上进行跟踪时,将图像金字塔与LK光流法相结合,使图像分辨率降低到一定程度时,原本较大的运动位移变得足够小,利用图像金字塔来自上而下的计算来得到准确的光流。
2.1 实现原理

- 首先选取第一帧,在第一帧图像中检测Shi-Tomasi角点,
- 然后使用LK算法来迭代的跟踪这些特征点。迭代的方式就是不断向cv2.calcOpticalFlowPyrLK()中传入上一帧图片的特征点以及当前帧的图片。
- 函数会返回当前帧的点,这些点带有状态1或者0,如果在当前帧找到了上一帧中的点,那么这个点的状态就是1,否则就是0。
2.2 API
cv2.calcOpticalFlowPyrLK函数计算一个稀疏特征集的光流,使用金字塔中的迭代 Lucas-Kanade 方法。
  
   - 
    
     
    
    
     
      nextPts,status,err = cv2.calcOpticalFlowPyrLK(prevImg,   
      #上一帧图片
     
    
- 
    
     
    
    
     
                                                    nextImg,   
      #当前帧图片
     
    
- 
    
     
    
    
     
                                                    prevPts,   
      #上一帧找到的特征点向量 
     
    
- 
    
     
    
    
     
                                                    nextPts    
      #与返回值中的nextPtrs相同
     
    
- 
    
     
    
    
     
                                                    [, status[, err[, winSize
     
    
- 
    
     
    
    
     
                                                    [, maxLevel[, criteria
     
    
- 
    
     
    
    
     
                                                    [, flags[, minEigThreshold]]]]]]])
     
    
返回值:
- nextPtrs 输出一个二维点的向量,这个向量可以是用来作为光流算法的输入特征点,也是光流算法在当前帧找到特征点的新位置(浮点数)
- status 标志,在当前帧当中发现的特征点标志status==1,否则为0
- err 向量中的每个特征对应的错误率
其他输入值:
- status 与返回的status相同
- err 与返回的err相同
- winSize 在计算局部连续运动的窗口尺寸(在图像金字塔中)
- maxLevel 图像金字塔层数,0表示不使用金字塔
- criteria 寻找光流迭代终止的条件
- flags 有两个宏,表示两种计算方法,
- OPTFLOW_USE_INITIAL_FLOW表示使用估计值作为寻找到的初始光流,
- OPTFLOW_LK_GET_MIN_EIGENVALS表示使用最小特征值作为误差测量
- minEigThreshold 该算法计算光流方程的2×2规范化矩阵的最小特征值,除以窗口中的像素数; 如果此值小于minEigThreshold,则会过滤掉相应的功能并且不会处理该光流,因此它允许删除坏点并获得性能提升。
关键点(角点)追踪 goodFeaturesToTrack()
  
   - 
    
     
    
    
     
      cv2.goodFeaturesToTrack(image,         
      #单通道
     
    
- 
    
     
    
    
     
                              maxCorners,    
      #角点数目最大值,若检测的角点超过此值,则只返回前maxCorners个强角点
     
    
- 
    
     
    
    
     
                              qualityLevel,  
      #角点的品质因子
     
    
- 
    
     
    
    
     
                              minDistance,   
      #如果在其周围minDistance范围内存在其他更强角点,则将此角点删除 
     
    
- 
    
     
    
    
     
                              corners        
      #存储所有角点
     
    
- 
    
     
    
    
     
                              mask,          
      #指定感兴趣区,若无指定,寻找全图
     
    
- 
    
     
    
    
     
                              blockSize,     
      #计算协方差矩阵时的窗口大小
     
    
- 
    
     
    
    
     
                              useHarrisDetector,  
      #bool 是否使用Harris角点检测,如不指定,则计算shi-tomasi角点
     
    
- 
    
     
    
    
     
                              k )           
      #Harris角点检测需要的k值
     
    
三、代码
实现流程:
- 加载视频。
- 调用 GoodFeaturesToTrack 函数寻找兴趣点(关键点)。
- 调用 CalcOpticalFlowPyrLK 函数计算出两帧图像中兴趣点的移动情况。
- 删除未移动的兴趣点。
- 在两次移动的点之间绘制一条线段。
  
   - 
    
     
    
    
     
      import numpy 
      as np
     
    
- 
    
     
    
    
     
      import cv2
     
    
- 
    
     
    
    
     
      import sys
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      cap = cv2.VideoCapture(
      "E:\Python-Code/videodataset/test.mp4")
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      # ShiTomasi corner detection 角点检测的参数
     
    
- 
    
     
    
    
     
      feature_params = 
      dict( maxCorners = 
      100,  
      # 角点数目最大值,若检测的角点超过此值,则只返回前maxCorners个强角点
     
    
- 
    
     
    
    
     
                             qualityLevel = 
      0.3,  
      # 角点的品质因子
     
    
- 
    
     
    
    
     
                             minDistance = 
      7,  
      # 如果在其周围minDistance范围内存在其他更强角点,则将此角点删除
     
    
- 
    
     
    
    
     
                             blockSize = 
      7 
      # 计算协方差矩阵时的窗口大小
     
    
- 
    
     
    
    
     
                             )
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      # LK光流法参数
     
    
- 
    
     
    
    
     
      # winSize 在计算局部连续运动的窗口尺寸(在图像金字塔中)
     
    
- 
    
     
    
    
     
      # maxLevel 为使用的图像金字塔层数
     
    
- 
    
     
    
    
     
      # criteria 寻找光流迭代终止的条件
     
    
- 
    
     
    
    
     
      lk_params = 
      dict(winSize  = (
      15,
      15),
     
    
- 
    
     
    
    
     
                       maxLevel = 
      2,
     
    
- 
    
     
    
    
     
                       criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 
      10, 
      0.03))
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      # # 创建随机生成的颜色
     
    
- 
    
     
    
    
     
      color = np.random.randint(
      0,
      255,(
      100,
      3))
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      ret, old_frame = cap.read()  
      # 取视频第一帧
     
    
- 
    
     
    
    
     
      old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)  
      # 将第一帧灰度化
     
    
- 
    
     
    
    
     
      p0 = cv2.goodFeaturesToTrack(old_gray,
     
    
- 
    
     
    
    
     
                                   mask = 
      None, 
      # 指定感兴趣区,若无指定,寻找全图
     
    
- 
    
     
    
    
     
                                   **feature_params) 
      # 选取好的特征点,返回特征点列表
     
    
- 
    
     
    
    
     
      mask = np.zeros_like(old_frame)  
      # 为绘制创建掩码图片
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      while(
      1):
     
    
- 
    
     
    
    
     
          ret,frame = cap.read()
     
    
- 
    
     
    
    
         
      if frame 
      is 
      None:
     
    
- 
    
     
    
    
             
      break
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      # 计算光流以获取点的新位置
     
    
- 
    
     
    
    
     
          frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
     
    
- 
    
     
    
    
     
          p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, 
      None, **lk_params) 
      # 计算新的一副图像中相应的特征点d的位置
     
    
- 
    
     
    
    
         
      # 选择good points
     
    
- 
    
     
    
    
     
          good_new = p1[st==
      1]
     
    
- 
    
     
    
    
     
          good_old = p0[st==
      1]
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      # 绘制跟踪框
     
    
- 
    
     
    
    
         
      for i,(new,old) 
      in 
      enumerate(
      zip(good_new,good_old)):
     
    
- 
    
     
    
    
     
              a,b = new.ravel() 
      #ravel()函数用于降维并且不产生副本
     
    
- 
    
     
    
    
     
              c,d = old.ravel()
     
    
- 
    
     
    
    
             
      # mask = cv2.line(mask, (a,b),(c,d), color[i].tolist(), 2)
     
    
- 
    
     
    
    
             
      # frame = cv2.circle(frame,(a,b),5,color[i].tolist(),-1)
     
    
- 
    
     
    
    
     
              mask = cv2.line(mask, (
      int(a), 
      int(b)), (
      int(c), 
      int(d)), color[i].tolist(), 
      2)
     
    
- 
    
     
    
    
     
              frame = cv2.circle(frame, (
      int(a), 
      int(b)), 
      5, color[i].tolist(), -
      1)
     
    
- 
    
     
    
    
     
          img = cv2.add(frame,mask)
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
          cv2.imshow(
      'frame',img)
     
    
- 
    
     
    
    
     
          k = cv2.waitKey(
      30) & 
      0xff
     
    
- 
    
     
    
    
         
      if k == 
      27:
     
    
- 
    
     
    
    
             
      break
     
    
- 
    
     
    
    
     
          old_gray = frame_gray.copy()
     
    
- 
    
     
    
    
     
          p0 = good_new.reshape(-
      1,
      1,
      2)
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      cv2.destroyAllWindows()
     
    
- 
    
     
    
    
     
      cap.release()
     
    
 四、总结
光流法对于动态背景环境的适应性通常比帧间差分法好,但是需要非常复杂的计算,并且提取的运动对象精度不高。
 不用事先进行运动区域背景的提取,直接在实时运动中实现运动目标分割,可适当解决遮挡、重合等问题。
 受光照影响大、计算复杂、运算时间长、抗噪声能力差,很难实现视频流的实时处理。
适用于复杂背景下且前景中有多个运动目标的分析问题。
转载:https://blog.csdn.net/weixin_45823221/article/details/128533098
 
					