光流的概念是Gibson在1950年首先提出来的。它是空间运动物体在观察成像平面上的像素运动的瞬时速度,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的相应关系。从而计算出相邻帧之间物体的运动信息的一种方法。一般而言,光流是因为场景中前景目标本身的移动、相机的运动,或者两者的共同运动所产生的。
研究光流场的目的就是为了从图片序列中近似得到不能直接得到的运动场。运动场,事实上就是物体在三维真实世界中的运动。光流场,是运动场在二维图像平面上(人的眼睛或者摄像头)的投影。
那通俗的讲就是通过一个图片序列,把每张图像中每一个像素的运动速度和运动方向找出来就是光流场。
那怎么找呢?咱们直观理解肯定是:第t帧的时候A点的位置是(x1, y1),那么我们在第t+1帧的时候再找到A点,假如它的位置是(x2,y2),那么我们就能够确定A点的运动了:(ux, vy) = (x2, y2) - (x1,y1)
。
那怎么知道第t+1帧的时候A点的位置呢? 这就存在非常多的光流计算方法了。
1981年,Horn和Schunck创造性地将二维速度场与灰度相联系,引入光流约束方程,得到光流计算的基本算法。人们基于不同的理论基础提出各种光流计算方法,算法性能各有不同。Barron等人对多种光流计算技术进行了总结。依照理论基础与数学方法的差别把它们分成四种:基于梯度的方法、基于匹配的方法、基于能量的方法、基于相位的方法。
近年来神经动力学方法也颇受学者重视。
通过金字塔Lucas-Kanade 光流方法计算某些点集的光流(稀疏光流),满足以下要点
用Gunnar Farneback 的算法计算稠密光流(即图像上全部像素点的光流都计算出来,大致含义为计算当前帧与上一帧画面像素相对位移
计算稀疏光流api
void calcOpticallFlowPyrLK (InuputArray prevImg,
InputArray nextImg,
InputArray prevPts,
InputOutputArray nextPts,
OutputArray status,
OutputArray err,
Size winSize = Size(21,21),
int maxLevel = 3,
TermCriteriacriteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
int flags = 0,
double minEigThreshold = 1e-4);
参数说明
计算密集光流函数api
void cv::calcOpticalFlowFarneback( InputArray _prev0,
InputArray _next0,
OutputArray _flow0,
double pyr_scale,
int levels,
int winsize,
int iterations,
int poly_n,
double poly_sigma,
int flags )
参数说明
#include
#include
#include
using namespace std;
using namespace cv;
Mat frame,gray; //当前帧 当前灰度
Mat prev_frame,prev_gray; //前一帧数据 前一帧灰度图
vector<Point2f> features; //当前实时检测出的 shi-tomas角点检测 特征数据
vector<Point2f> iniPoints; //初始化角点检测
vector<Point2f> fpts[2]; //保持当前帧和前一帧的特征点位置 函数参数
vector<uchar> status; //特征点跟踪成功标志位 函数参数
vector<float> err; //跟踪区域误差和 函数参数
//检测角点
void detect_features(Mat &inFrame,Mat &inGray);
//绘制角点
void draw_feature(Mat &inFrame);
void draw_trac_lines(void);
//光流追踪计算
void klt_track_feature(void);
int main(void)
{
VideoCapture cap;
namedWindow("inputvideo",0);
resizeWindow("inputvideo",272,480);
if(!cap.open("/work/opencv_video/color_check.mp4"))
{
cout << "video is err" << endl;
return -1;
}
cout << "video is open" << endl;
while(true)
{
cap>>frame;
if(frame.empty())
{
break;
}
//图片转化为灰度
cvtColor(frame,gray,COLOR_BGR2GRAY);
//由于视频图像不断播放 会导致画面特征点不断变化、减少,光流检测理想状态多数情况下并不存在
//当特征点数量过于少的时候 重新检测角点 为下一次光流检测做准备
if(fpts[0].size()<40)
{
detect_features(frame,gray); //再次检测
fpts[0].insert(fpts[0].end(),features.begin(),features.end()); //更新数据
iniPoints.insert(iniPoints.end(),features.begin(),features.end()); //更新数据
}else
{
printf("tttttttt\n");
}
detect_features(frame,gray); //检测角点
//程序初始化运行的时候 把第一帧图像进行保存
if(prev_gray.empty())
{
gray.copyTo(prev_gray);
}
//计算光流
klt_track_feature();
draw_feature(frame); //绘制
//更新前一帧的数据
gray.copyTo(prev_gray);
frame.copyTo(prev_frame);
imshow("inputvideo",frame);
if(waitKey(70)>0)
{
cout << "video is close" << endl;
break;
}
}
cap.release();
destroyAllWindows();
return 0;
}
void detect_features(Mat &inFrame,Mat &inGray)
{
double maxCorners = 5000;
double qualitylevel = 0.01;
double minDistance = 10;
double blockSize = 3;
double k = 0.04;
//检测角点
goodFeaturesToTrack(inGray,features,maxCorners,qualitylevel,minDistance,Mat(),blockSize,false,k);
cout << "features Size : "<< features.size() <<endl;
}
void draw_feature(Mat &inFrame)
{
for(size_t i=0;i<fpts[0].size();i++)
{
circle(inFrame,fpts[0][i],2,Scalar(0,0,255),2,8);
}
}
void klt_track_feature(void)
{
calcOpticalFlowPyrLK(prev_gray,gray,fpts[0],fpts[1],status,err);
int k = 0;
for(size_t i=0;i<fpts[1].size();i++)
{
//计算位移
double dist = abs(fpts[0][i].x-fpts[1][i].x) + abs(fpts[0][i].y-fpts[1][i].y);
if(dist>2 && status[i]) //位移范围够大 该点被标记过
{
iniPoints[k] = iniPoints[i];
fpts[1][k++] = fpts[1][i];
}
}
draw_trac_lines();
iniPoints.resize(k);
fpts[1].resize(k);
swap(fpts[1],fpts[0]);
}
void draw_trac_lines(void)
{
for(size_t i=0;i<fpts[0].size();i++)
{
//初始位置 指向 当前位置的直线
line(frame,iniPoints[i],fpts[1][i],Scalar(255,0,0),2,8,0);
}
}
#include
#include
#include
using namespace std;
using namespace cv;
Mat frame,gray; //当前帧 当前灰度
Mat prev_gray; //前一帧数据 前一帧灰度图
Mat flow_result,flowdata;
void draw_optical_flowhf(const Mat &flowdata,Mat &image);
int main(void)
{
VideoCapture cap;
namedWindow("inputvideo",WINDOW_AUTOSIZE);
namedWindow("flowvideo",WINDOW_AUTOSIZE);
if(!cap.open("/work/opencv_video/vtest.avi"))
{
cout << "video is err" << endl;
return -1;
}
cout << "video is open" << endl;
cap>>frame;
if(frame.empty())
{
return -1;
}
//赋值给上一帧画面
cvtColor(frame,prev_gray,COLOR_BGR2GRAY);
while(true)
{
cap>>frame;
if(frame.empty())
{
break;
}
//图片转化为灰度
cvtColor(frame,gray,COLOR_BGR2GRAY);
//根据图片相对位移计算位移差
calcOpticalFlowFarneback(prev_gray,gray,flowdata,0.5,3,15,3,5,1.2,0);
cvtColor(prev_gray,flow_result,COLOR_GRAY2BGR);
//绘制光流
draw_optical_flowhf(flowdata,flow_result);
imshow("inputvideo",frame);
imshow("flowvideo",flow_result);
if(waitKey(10)>0)
{
cout << "video is close" << endl;
break;
}
}
cap.release();
destroyAllWindows();
return 0;
}
//绘制光流
void draw_optical_flowhf(const Mat &flowdata,Mat &image)
{
for(int row =0;row<image.rows;row++)
for(int col =0;col<image.cols;col++)
{
const Point2f fxy = flowdata.at<Point2f>(row,col); //读取相对偏移
if(fxy.x>2 || fxy.y>2) //偏移量需要大于一定范围
{
line(image,Point(col,row),Point(cvRound(col+fxy.x),cvRound(row+fxy.y)),Scalar(0,255,0),2,8);
circle(image,Point(col,row),2,Scalar(0,0,255),2);
}
}
}