目录
一、光流法
二、LK光流法
2.1 实现原理
2.2 API
三、代码
四、总结
基于特征点的光流跟踪,在目标上提取一些特征点,然后在下一帧计算这些特征点的光流匹配点,统计得到目标的位置。在跟踪的过程中,需要不断补充新的特征点,删除置信度不佳的特征点,以此来适应目标在运动中的形状变化。本质上可以认为光流跟踪属于用特征点的集合来表征目标模型的方法。
光流:空间运动物体在观察成像平面上像素运动的瞬时速度。
光流法利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的一种方法。
通常将二维图像平面特定坐标点上的灰度瞬时变化率定义为光流矢量。
简单来说,光流就是瞬时速度,在时间间隔很小时,也等同于目标的位移,光流场是灰度图像的二维矢量场,它反映了图像上像素的变化趋势,可看成是带有灰度的像素点在图像平面上运动而产生的瞬时速度场,它包含的信息即是各像素点的瞬时运动速度矢量信息,既可以表现为物体运动的运动方向也可表现为物体运动的速率。
LK光流法有三个假设条件:
LK光流法相比HS光流法避免了计算目标区域内所有像素点的光流,减小了运算开销,提高了程序的运行速度,但LK光流法的三个假设条件也会在应用稀疏光流法时产生一定的限制,比如当物体运动位移较大或者速度较快时,图像间的相关性较弱,并且较大的运动不满足泰勒公式的展开条件,导致无法求出最有匹配解而导致跟踪失败
为了避免大位移运动跟踪失败的情况,在较大的尺度上进行跟踪时,将图像金字塔与LK光流法相结合,使图像分辨率降低到一定程度时,原本较大的运动位移变得足够小,利用图像金字塔来自上而下的计算来得到准确的光流。
cv2.calcOpticalFlowPyrLK
函数计算一个稀疏特征集的光流,使用金字塔中的迭代 Lucas-Kanade 方法。
nextPts,status,err = cv2.calcOpticalFlowPyrLK(prevImg, #上一帧图片
nextImg, #当前帧图片
prevPts, #上一帧找到的特征点向量
nextPts #与返回值中的nextPtrs相同
[, status[, err[, winSize
[, maxLevel[, criteria
[, flags[, minEigThreshold]]]]]]])
返回值:
其他输入值:
关键点(角点)追踪 goodFeaturesToTrack()
cv2.goodFeaturesToTrack(image, #单通道
maxCorners, #角点数目最大值,若检测的角点超过此值,则只返回前maxCorners个强角点
qualityLevel, #角点的品质因子
minDistance, #如果在其周围minDistance范围内存在其他更强角点,则将此角点删除
corners #存储所有角点
mask, #指定感兴趣区,若无指定,寻找全图
blockSize, #计算协方差矩阵时的窗口大小
useHarrisDetector, #bool 是否使用Harris角点检测,如不指定,则计算shi-tomasi角点
k ) #Harris角点检测需要的k值
实现流程:
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()
光流法对于动态背景环境的适应性通常比帧间差分法好,但是需要非常复杂的计算,并且提取的运动对象精度不高。
不用事先进行运动区域背景的提取,直接在实时运动中实现运动目标分割,可适当解决遮挡、重合等问题。
受光照影响大、计算复杂、运算时间长、抗噪声能力差,很难实现视频流的实时处理。
适用于复杂背景下且前景中有多个运动目标的分析问题。