OpenCV 前景/背景标注

光流法原理

运动场: 物体在三维真实世界中的运动;

光流场: 运动场在二维图像平面上的投影。

给图像中的每个像素点赋予一个速度矢量,这样就形成了一个运动矢量场。在某一特定时刻,图像上的点与三维物体上的点一一对应,这种对应关系可以通过投影来计算得到。根据各个像素点的速度矢量特征,可以对图像进行动态分析。如果图像中没有运动目标,则光流矢量在整个图像区域是连续变化的。当图像中有运动物体时,目标和背景存在着相对运动。运动物体所形成的速度矢量必然和背景的速度矢量有所不同,如此便可以计算出运动物体的位置。需要提醒的是,利用光流法进行运动物体检测时,计算量较大,无法保证实时性和实用性。

通常情况下运动物体为图像前景, 静止部分为图像背景, 因此, 可以使用光流法比较方便地区分图像的前景背景.

CalcOpticalFlowFarneback函数

OpenCV提供了CalcOpticalFlowFarneback函数用于计算光流. 具体地是基于Gunnar Farneback的算法计算全局性的稠密光流算法.

算法包含以下参数:

1
2
3
4
5
6
# C++
void calcOpticalFlowFarneback(InputArray prevImg, InputArray nextImg, InputOutputArray flow, double pyrScale, int levels, int winsize, int iterations, int polyN, double polySigma, int flags)
# C
void cvCalcOpticalFlowFarneback(const CvArr* prevImg, const CvArr* nextImg, CvArr* flow, double pyrScale, int levels, int winsize, int iterations, int polyN, double polySigma, int flags)
# Python
cv2.calcOpticalFlowFarneback(prevImg, nextImg, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags[, flow]) → flow
参数 含义
prevImg 输入前一帧图像
nextImg 输入后一帧图像
flow 输出的光流
pyr_scale 金字塔上下两层之间的尺度关系
levels 金字塔层数
winsize 均值窗口大小,越大越能denoise并且能够检测快速移动目标,但会引起模糊运动区域
iterations 迭代次数
poly_n 像素领域大小,一般为5,7等
poly_sigma 高斯标注差,一般为1-1.5
flags 计算方法, 主要包括OPTFLOW_USE_INITIAL_FLOW和OPTFLOW_FARNEBACK_GAUSSIAN

CalcOpticalFlowFarneback函数的功能就是计算图像中每个像素的光流,使得

20190531172827.png

从而计算出相邻帧之间物体的运动信息.

程序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#! /usr/local/bin/python
# -*- coding: UTF-8 -*-
import numpy as np
import cv2

capture = cv2.VideoCapture(0) #打开视频,参数为路径;参数为0表示打开笔记本的内置摄像头
print capture.isOpened() #显示打开是否成功
ret, img1 = capture.read() #先获取第一帧 ret-帧状态布尔值,img-图像矩阵
# cap = cv2.VideoCapture(0)
prvs = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(img1) #新建图像用于存放hsv图像

#遍历每一行的第1列
hsv[...,1] = 255


while(1):
ret, img2 = capture.read()
next = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
#返回一个两通道的光流向量,实际上是每个点的像素位移值
flow = cv2.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)# 计算光流
prvs = next
#笛卡尔坐标转换为极坐标,获得极轴和极角
mag, ang = cv2.cartToPolar(flow[...,0], flow[...,1])
hsv[...,0] = ang*180/np.pi/2
hsv[...,2] = cv2.normalize(mag,None,0,255,cv2.NORM_MINMAX)
rgb = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR)
cv2.imshow('光流图像',rgb)
#处理光流图像,在原图像中显示背景/前景
lower_hsv = np.array([0, 0, 0])
upper_hsv = np.array([255, 255, 10])
mask = cv2.inRange(hsv, lowerb=lower_hsv, upperb=upper_hsv)
img3 = cv2.add(img2, np.zeros(np.shape(img2), dtype=np.uint8), mask=mask)
cv2.imshow('前景/背景图像',img3)
key=cv2.waitKey(1) #等待键盘输入,间隔1ms waits for a key event infinitely (when [delay]< 0 ) or for [delay] milliseconds,
# print key 鼠标需要点一下视频窗口,使程序接收键盘输入而不是命令行接收键盘输入
if key == 27 : #ESC键的ASCII码
print "detect ESC"
break #退出while循环
capture.release() #释放笔记本摄像头
cv2.destroyAllWindows() #关闭所有图像窗口

运行效果

20190531214609.png

可以看出运动前景基本被标识出来,但有一些背景中的部分也被误判为前景,需要进一步改进

0%