上节说到过的calcOpticalFlowPyrLK光流算法,可以看到它实际上是一种稀疏特征点的光流算法,也就是说我们先找到那些(特征)点需要进行处理,然后再处理,该节介绍下一个全局性的密集光流算法,也就是对每一个点都进行光流计算,函数为calcOpticalFlowFarneback。
首先介绍参数,详细的介绍 参见opencv手册
参数一大推,得看一会。有些参数可能带来的影响不是很大,那么使用它推荐的参数即可。完整的参数达10个。按顺序:
prevImg:输入第一个图
nextImg:输入第二个图
Flow:输出的光流矩阵。矩阵大小同输入的图像一样大,但是矩阵中的每一个元素可不是一个值,而是两个值,分别表示这个点在x方向与y方向的运动量(偏移量)。所以要把这个光流场矩阵显示出来还真的需要费点力。那么上面说的两幅图像与这个光流场是什么关系呢?如下:
pyrScale:一个构造图像金字塔的参数,一般就认为是0.5最好了,也就是将图像缩小一半。那么为什么要构造金字塔呢?这应该是与算法本身的设计有关,其实很多地方在检测特征的时候都会涉及到图像的金字塔,设想下如果有个特征点在原始尺寸与其缩小的尺寸下都是特征点的话,那么这个特征点就很有效了吧。
Levels:依然是与金字塔有关参数,常设值1.
Winsize:相当于一个均值滤波的作用,窗口大小决定了其噪声的抑制能力什么的。
Iterations:在每层金字塔上的迭代次数。
polyN:点与附近领域点之间的联系作用,一般为5,7等等即可。
polySigma :像素点的一个平滑水平,一般1-1.5即可。
Flags:一个标记,决定计算方法。
具体怎么影响结果的,可以自己去尝试。
下面对上节使用到的两幅图,通过这个方法来计算这两帧图像中存在的光流场,也就是把上述的Flow找出来,那些参数也决定了Flow找出来的不一样。简单的程序如下:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
#define UNKNOWN_FLOW_THRESH 1e9
void makecolorwheel(vector &colorwheel)
{
int RY = 15;
int YG = 6;
int GC = 4;
int CB = 11;
int BM = 13;
int MR = 6;
int i;
for (i = 0; i < RY; i++) colorwheel.push_back(Scalar(255, 255*i/RY, 0));
for (i = 0; i < YG; i++) colorwheel.push_back(Scalar(255-255*i/YG, 255, 0));
for (i = 0; i < GC; i++) colorwheel.push_back(Scalar(0, 255, 255*i/GC));
for (i = 0; i < CB; i++) colorwheel.push_back(Scalar(0, 255-255*i/CB, 255));
for (i = 0; i < BM; i++) colorwheel.push_back(Scalar(255*i/BM, 0, 255));
for (i = 0; i < MR; i++) colorwheel.push_back(Scalar(255, 0, 255-255*i/MR));
}
void motionToColor(Mat flow, Mat &color)
{
if (color.empty())
color.create(flow.rows, flow.cols, CV_8UC3);
static vector colorwheel; //Scalar r,g,b
if (colorwheel.empty())
makecolorwheel(colorwheel);
// determine motion range:
float maxrad = -1;
// Find max flow to normalize fx and fy
for (int i= 0; i < flow.rows; ++i)
{
for (int j = 0; j < flow.cols; ++j)
{
Vec2f flow_at_point = flow.at(i, j);
float fx = flow_at_point[0];
float fy = flow_at_point[1];
if ((fabs(fx) > UNKNOWN_FLOW_THRESH) || (fabs(fy) > UNKNOWN_FLOW_THRESH))
continue;
float rad = sqrt(fx * fx + fy * fy);
maxrad = maxrad > rad ? maxrad : rad;
}
}
for (int i= 0; i < flow.rows; ++i)
{
for (int j = 0; j < flow.cols; ++j)
{
uchar *data = color.data + color.step[0] * i + color.step[1] * j;
Vec2f flow_at_point = flow.at(i, j);
float fx = flow_at_point[0] / maxrad;
float fy = flow_at_point[1] / maxrad;
if ((fabs(fx) > UNKNOWN_FLOW_THRESH) || (fabs(fy) > UNKNOWN_FLOW_THRESH))
{
data[0] = data[1] = data[2] = 0;
continue;
}
float rad = sqrt(fx * fx + fy * fy);
float angle = atan2(-fy, -fx) / CV_PI;
float fk = (angle + 1.0) / 2.0 * (colorwheel.size()-1);
int k0 = (int)fk;
int k1 = (k0 + 1) % colorwheel.size();
float f = fk - k0;
//f = 0; // uncomment to see original color wheel
for (int b = 0; b < 3; b++)
{
float col0 = colorwheel[k0][b] / 255.0;
float col1 = colorwheel[k1][b] / 255.0;
float col = (1 - f) * col0 + f * col1;
if (rad <= 1)
col = 1 - rad * (1 - col); // increase saturation with radius
else
col *= .75; // out of range
data[2 - b] = (int)(255.0 * col);
}
}
}
}
int main()
{
Mat I1;
Mat I2;
Mat flow;
//读取两个图像---相邻帧
I1 = imread("I1.jpg",0);//读取为灰度图像
I2 = imread("I2.jpg",0);
calcOpticalFlowFarneback(I1,I2,flow,0.5,3,20,3,5,1.2,0);
//cout<(10,10)<
//flow = abs(flow);
Mat motion2color;
motionToColor(flow, motion2color);
imshow("flow",motion2color);
waitKey(0);
return 0;
}
可以看到程序的大部分是在如何把计算的光流flow可视化出来,虽然我们已经知道它是一个矩阵,而且矩阵中每个元素都包括2个值。但是显示出来也比较费劲,显示的函数是参考博客:
光流Optical Flow介绍与OpenCV实现
该博客里面的内容也是值得一看的。
那么我们这里的一个结果如下(原始图见上篇):
颜色越深表示该部分存在的运动变化越大。
早点版本(3.0之前)的opencv中还有好几个光流计算函数,什么块匹配BM法,HS法,LK法等等,每一种方法几乎都对应一篇相关文章而来。后几种方法的结果无非也都是计算出光流场(在x方向的光流与在y方向的光流)。3.0的opencv似乎把块匹配、HS等方法舍弃了?没有找到,想使用那些方法的恐怕还要使用以前的opencv版本才行。