sitf+LK+pnp 识别、跟踪图片,并求三维旋转角度(四) -----LK光流跟踪
解释下上篇遗留问题,为什么会用到光流法,因为模型要在手机甚至眼镜端上运行,这就要求速度,和用户体验感,我用sift一次对比的时间大概在200ms,人眼1s差不多在12帧,所以光用sift是达不到1秒12帧的,但是光流一整个流程下来只要35ms,加上数据传输30ms,加起来65ms,能达到一秒十五帧。因此使用sift结合光流的方法。
光流法 opencv里面有七八中方法,这里我使用了LK光流法,网上对于lk光流介绍的有很多文章,我就这放一篇我最推荐的文章(懒人就直接看下面代码吧):Opencv Python版学习笔记(五)光流跟踪 Lucas-Kanade(LK)算法,里面有python版本的Lk光流代码,本人亲测可用,但是这些代码仅限于跑跑demo,而且这个代码在逻辑顺序上(代码中的序号1,2)有点问题(当然是我这么想的,可能作者本身有其他的意思,我这就按照我的想法更改过来了),sift+光流的方法,是在这个代码的基础上去改进的:
#encoding:utf-8
'''
Lucas-Kanade tracker
====================
Lucas-Kanade sparse optical flow demo. Uses goodFeaturesToTrack
for track initialization and back-tracking for match verification
between frames.
Usage
-----
lk_track.py [<video_source>]
Keys
----
ESC - exit
'''
import numpy as np
import cv2
#from common import anorm2, draw_str
from time import clock
lk_params = dict( winSize = (15, 15),
maxLevel = 2,
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
feature_params = dict( maxCorners = 500,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
class App:
def __init__(self, video_src):#构造方法,初始化一些参数和视频路径
self.track_len = 10
self.detect_interval = 5
self.tracks = []
self.cam = cv2.VideoCapture(video_src)
self.frame_idx = 0
def run(self):#光流运行方法
while True:
ret, frame = self.cam.read()#读取视频帧
if ret == True:
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)#转化为灰度虚图像
vis = frame.copy()
#原来的代码这个是是下一个的if下面 这里标为序号2
if self.frame_idx % self.detect_interval == 0:#每5帧检测一次特征点
mask = np.zeros_like(frame_gray)#初始化和视频大小相同的图像
mask[:] = 255#将mask赋值255也就是算全部图像的角点
for x, y in [np.int32(tr[-1]) for tr in self.tracks]:#跟踪的角点画圆
cv2.circle(mask, (x, y), 5, 0, -1)
p = cv2.goodFeaturesToTrack(frame_gray, mask = mask, **feature_params)#像素级别角点检测
if p is not None:
for x, y in np.float32(p).reshape(-1, 2):
self.tracks.append([(x, y)])#将检测到的角点放在待跟踪序列中
#原来的代码这个是在上一个的if前面 这里标为序号1
if len(self.tracks) > 0:#检测到角点后进行光流跟踪
img0, img1 = self.prev_gray, frame_gray
p0 = np.float32([tr[-1] for tr in self.tracks]).reshape(-1, 1, 2)
p1, st, err = cv2.calcOpticalFlowPyrLK(img0, img1, p0, None, **lk_params)#前一帧的角点和当前帧的图像作为输入来得到角点在当前帧的位置
p0r, st, err = cv2.calcOpticalFlowPyrLK(img1, img0, p1, None, **lk_params)#当前帧跟踪到的角点及图像和前一帧的图像作为输入来找到前一帧的角点位置
d = abs(p0-p0r).reshape(-1, 2).max(-1)#得到角点回溯与前一帧实际角点的位置变化关系
good = d < 1#判断d内的值是否小于1,大于1跟踪被认为是错误的跟踪点
new_tracks = []
for tr, (x, y), good_flag in zip(self.tracks, p1.reshape(-1, 2), good):#将跟踪正确的点列入成功跟踪点
if not good_flag:
continue
tr.append((x, y))
if len(tr) > self.track_len:
del tr[0]
new_tracks.append(tr)
cv2.circle(vis, (x, y), 2, (0, 255, 0), -1)
self.tracks = new_tracks
cv2.polylines(vis, [np.int32(tr) for tr in self.tracks], False, (0, 255, 0))#以上一振角点为初始点,当前帧跟踪到的点为终点划线
#draw_str(vis, (20, 20), 'track count: %d' % len(self.tracks))
self.frame_idx += 1
self.prev_gray = frame_gray
cv2.imshow('lk_track', vis)
ch = 0xFF & cv2.waitKey(1)
if ch == 27:
break
def main():
import sys
try: video_src = sys.argv[1]
except: video_src = "E:\Megamind.avi"
print __doc__
App(video_src).run()
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
函数原型:corners = cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance[, corners[, mask[, blockSize[, useHarrisDetector[, k]]]]])
参数说明:image 输入的单通道图像,可以为8-bit或32-bit
maxCorners 最大的角点数,如果检测出的角点多余最大角点数,将取出最强最大角点数个角点
qualityLevel 最小可接受的角点质量
minDistance 角点间的最小欧几里得距离(也就是两个角点间不能太近)
corners 输出的检测到的角点
mask 需要检测角点的区域,mask标记区域是我自己写的,不过百度上也有很多方法。
blocksize 计算离散卷积块的大小(没用到)
useHarrisDetector 是否使用Harris角点(没用到,用到可能会更准确些?)
具体改进的思路和方法我会用文字和代码段的形式说明,整体代码因为保密原因,所以就不全贴了。
通过上段代码我们可以知道,光流法是用cv2.goodFeaturesToTrack方法去计算有哪些可以跟踪的点,并且这些点都是good点,为了方便与sift结合,将cv2.goodFeaturesToTrack检测特征点和cv2.calcOpticalFlowPyrLK跟踪两个if分别写了下面两个函数,sift走完后会触发检测特征点的初始化操作,并记录状态(flag):
def goodFeaturesToTrack(self, grayimg,h,w):
mask = self.creatMask(grayimg, self.four_points, h,w)
#mask 是我根据前面所写的四个像素点创建的区域,跟着看过来的都知道是哪四个点,h,w 是grayimg的宽高
self.corners = cv2.goodFeaturesToTrack(grayimg,self.max_corners,self.quality_level, self.min_distance, mask=mask)
self.preimg = copy.deepcopy(grayimg)
#grayimg是做为sift识别的图以及要作为下一帧光流的初始化的图
self.track_status.clear()
return 0
当sift走完并触发了检测特征点的状态后,下一帧的图片传入,和上一帧的图片做光流跟踪(grayimg已经深拷贝了) ,此时就会调用到下面的方法进行跟踪并返会下一帧的点
def calcOpticalFlowPyrLK(self, grayimg, **kwargs):
"""
:param grayimg:
:param kwargs:
:return: list of dict
"""
result = []
if self.corners is None or self.preimg.shape != grayimg.shape:
return []
#self.corners 就是检测的特征点中的self.corners
# 跟踪点
next_corners, status, err = cv2.calcOpticalFlowPyrLK(self.preimg, grayimg, self.corners, None)
#next_corners就是光流跟踪后的特征点
goodcnt = list(status).count(1)
if goodcnt < 6:#判断good点的个数是否达标
return []
return next_corners
在Python函数原型为:def calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts, status=None, err=None, winSize=None, maxLevel=None, flags=None)
参数说明:prevImage 前一帧8-bit图像
nextImage 当前帧8-bit图像
prevPts 待跟踪的特征点向量
nextPts 输出跟踪特征点向量
status 特征点是否找到,找到的状态为1,未找到的状态为0
err 输出错误向量,(不太理解用途...)
winSize 搜索窗口的大小
maxLevel 最大的金字塔层数
flags 可选标识:OPTFLOW_USE_INITIAL_FLOW OPTFLOW_LK_GET_MIN_EIGENVALS
做sift与光流结合不难,难的是如何将sift与光流同步,这只是个小项目,我的做法是,第一帧或者当跟踪不上的时候使用sift方法去识别。如果有其他好的方法,希望大佬能不吝赐教。
sitf+LK+pnp 识别、跟踪图片,并求三维旋转角度(一) -----特征匹配
上一篇: DataTable 转换成 实体类model List
下一篇: 基于LK光流的点特征跟踪