运动目标检测——光流法与opencv代码实现

运动目标检测

运动目标的检测的其主要目的是获取目标对象的运动参数(位置、速度、加速度等)及运动轨迹,通过进一步分析处理,实现对目标行为更高层级上的理解。
运动目标检测技术目的是从序列图像中将变化区域从背景图像中提取出来,常用于视频监视、图像压缩、三维重构、异常检测等。

光流

背景介绍

运动目标检测主流方法有帧差法、背景差法、光流法等。光流法源于仿生学思想,更贴近于直觉,大量昆虫的视觉机理便是基于光流法。
二十世纪五十年代心理学家Gibson在他的著作“The Perception of Visual World”中首次提出了以心理学实验为基础的光流法基本概念,而直到八十年代才由Horn、Kanade、Lucash和Schunck创造性地将灰度与二维速度场相联系,引入光流约束方程的算法,对光流计算做了奠基性的工作。

光流(optical flow):由于目标对象或者摄像机的移动造成的图像对象在连续两帧图像中的移动。

  • 它是一个2D向量场,用来显示一个点从第一帧图像到第二帧图像的移动,可以通过二维平面的光流场恢复三维空间和运动参数和结构参数
  • 人眼观察运动物体时,物体景象在人眼视网膜上形成一系列连续变化图像,如连续变化的信息流过视网膜,类似光的一种流,因此称为光流。
  • 运动场为物体在真实三维世界的运动,光流场则是运动场在二维平面(如人眼或摄像头)上的投影,我们用图片序列的光流场还原不能直接得到的真实运动场。


    小球在连续五帧运动构成的光流

通俗说,对于一个图片序列,把每张图像每个像素在连续帧之间的运动速度和方向(某像素点在连续两帧上的位移矢量)找出来就是光流场。

第t帧的时A点的位置是(x1, y1),第t+1帧时A点位置是(x2,y2),则像素点A的位移矢量:(ux, vy) = (x2, y2) - (x1,y1)

如何知道第t+1帧的时候A点的位置涉及到不同的光流计算方法,主要有四种:基于梯度的方法、基于匹配的方法、基于能量的方法、基于相位的方法。

光流法依赖于三个假设:

  • 亮度恒定:目标像素强度在连续帧内不变
  • 相邻帧间隔时间足够短。
  • 相邻像素具有相似运动。

稠密光流与稀疏光流

根据所形成的光流场中二维矢量的疏密程度,光流法可分为稠密光流与稀疏光流。

1. 稠密光流
  • 稠密光流计算图像上所有点的偏移量,得到稠密的光流场,可进行像素级别图像配准。
  • Horn-Schunck算法以及基于区域匹配的大多数光流法都属于稠密光流的范畴
  • 稠密光流配准效果精准到像素级,但计算量大、时效性差。


    基于区域匹配生成的稠密光流场
2.稀疏光流

稀疏光流只对有明显特征的组点(如角点)进行跟踪,计算开销小。

基于特征匹配发生成的稀疏光流场

OpenCV中光流算法的API介绍

http://www.opencv.org.cn/opencvdoc/2.3.2/html/modules/video/doc/motion_analysis_and_object_tracking.html#calcopticalflowfarneback
(1)calcOpticalFlowPyrLK
基于金字塔LK光流算法,计算某些点集的稀疏光流。
参考论文《Pyramidal Implementation of the Lucas Kanade Feature TrackerDescription of the algorithm》
(2)calcOpticalFlowFarneback
基于Gunnar Farneback 的算法计算稠密光流。
参考论文《Two-Frame Motion Estimation Based on PolynomialExpansion》
(3)CalcOpticalFlowBM
通过块匹配的方法来计算光流
(4)CalcOpticalFlowHS
基于Horn-Schunck 的算法计算稠密光流。
参考论文《Determining Optical Flow》
(5)calcOpticalFlowSF
论文《SimpleFlow: A Non-iterative, Sublinear Optical FlowAlgo》的实现

代码实现

LK光流法
# 来自opencv\sources\samples\python\lk_track.py
import numpy as np
import cv2 as cv

cap = cv.VideoCapture("E:/opencv_vs/opencv/sources/samples/data/vtest.avi")
# params for ShiTomasi corner detection 设置 ShiTomasi 角点检测的参数
feature_params = dict(maxCorners=100,
                      qualityLevel=0.3,
                      minDistance=7,
                      blockSize=7)
# Parameters for lucas kanade optical flow 设置 lucas kanade 光流场的参数
# maxLevel 为使用的图像金字塔层数
lk_params = dict(winSize=(15, 15),
                 maxLevel=2,
                 criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))
# Create some random colors 产生随机的颜色值
color = np.random.randint(0, 255, (100, 3))
# Take first frame and find corners in it 获取第一帧,并寻找其中的角点
(ret, old_frame) = cap.read()
old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# Create a mask image for drawing purposes 创建一个掩膜为了后面绘制角点的光流轨迹
mask = np.zeros_like(old_frame)

# 视频文件输出参数设置
out_fps = 12.0  # 输出文件的帧率
fourcc = cv.VideoWriter_fourcc('M', 'P', '4', '2')
sizes = (int(cap.get(cv.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)))
out = cv.VideoWriter('E:/video/v5.avi', fourcc, out_fps, sizes)

while True:
    (ret, frame) = cap.read()
    frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    # calculate optical flow 能够获取点的新位置
    p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
    # Select good points 取好的角点,并筛选出旧的角点对应的新的角点
    good_new = p1[st == 1]
    good_old = p0[st == 1]
    # draw the tracks 绘制角点的轨迹
    for i, (new, old) in enumerate(zip(good_new, good_old)):
        a, b = new.ravel()
        c, d = old.ravel()
        mask = cv.line(mask, (a, b), (c, d), color[i].tolist(), 2)
        frame = cv.circle(frame, (a, b), 5, color[i].tolist(), -1)
    img = cv.add(frame, mask)
    cv.imshow('frame', img)
    out.write(img)
    k = cv.waitKey(200) & 0xff
    if k == 27:
        break
    # Now update the previous frame and previous points 更新当前帧和当前角点的位置
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1, 1, 2)

out.release()
cv.destroyAllWindows()
cap.release()
LK光流法效果
Gunner_Farneback 算法计算稠密光流
# 来自opencv\sources\samples/python2/opt_flow.py
import cv2 as cv
import numpy as np

es = cv.getStructuringElement(cv.MORPH_ELLIPSE, (10, 10))
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3, 3))
cap = cv.VideoCapture("E:/opencv_vs/opencv/sources/samples/data/vtest.avi")
frame1 = cap.read()[1]
prvs = cv.cvtColor(frame1, cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[..., 1] = 255

# 视频文件输出参数设置
out_fps = 12.0  # 输出文件的帧率
fourcc = cv.VideoWriter_fourcc('M', 'P', '4', '2')
sizes = (int(cap.get(cv.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)))
out1 = cv.VideoWriter('E:/video/v6.avi', fourcc, out_fps, sizes)
out2 = cv.VideoWriter('E:/video/v8.avi', fourcc, out_fps, sizes)

while True:
    (ret, frame2) = cap.read()
    next = cv.cvtColor(frame2, cv.COLOR_BGR2GRAY)
    flow = cv.calcOpticalFlowFarneback(prvs, next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
    mag, ang = cv.cartToPolar(flow[..., 0], flow[..., 1])
    hsv[..., 0] = ang * 180 / np.pi / 2
    hsv[..., 2] = cv.normalize(mag, None, 0, 255, cv.NORM_MINMAX)

    bgr = cv.cvtColor(hsv, cv.COLOR_HSV2BGR)

    draw = cv.cvtColor(bgr, cv.COLOR_BGR2GRAY)
    draw = cv.morphologyEx(draw, cv.MORPH_OPEN, kernel)
    draw = cv.threshold(draw, 25, 255, cv.THRESH_BINARY)[1]

    image, contours, hierarchy = cv.findContours(draw.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

    for c in contours:
        if cv.contourArea(c) < 500:
            continue
        (x, y, w, h) = cv.boundingRect(c)
        cv.rectangle(frame2, (x, y), (x + w, y + h), (255, 255, 0), 2)

    cv.imshow('frame2', bgr)

    cv.imshow('draw', draw)
    cv.imshow('frame1', frame2)
    out1.write(bgr)
    out2.write(frame2)

    k = cv.waitKey(200) & 0xff
    if k == 27 or k == ord('q'):
        break
    elif k == ord('s'):
        cv.imwrite('opticalfb.png', frame2)
        cv.imwrite('opticalhsv.png', bgr)
    prvs = next

out1.release()
out2.release()
cap.release()
cv.destroyAllWindows()
稠密光流法效果
参考博客

https://blog.csdn.net/zhanghenan123/article/details/80853523
https://blog.csdn.net/tengfei461807914/article/details/80978947
https://blog.csdn.net/wsp_1138886114/article/details/84400392
https://wenku.baidu.com/view/7a2cb968ff00bed5b8f31d6c.html?from=search
https://zhuanlan.zhihu.com/p/36389954
https://zhuanlan.zhihu.com/p/42942198

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335