1.什麼是光流
通俗的講就是通過一個圖片序列,把每張圖像中每個像素的運動速度和運動方向找出來就是光流場。那怎麼找呢?咱們直觀理解肯定是:第t幀的時候A點的位置是(x1, y1),那麼我們在第t+1幀的時候再找到A點,假如它的位置是(x2,y2),那麼我們就可以確定A點的運動了:(ux, vy) = (x2, y2) - (x1,y1)。那怎麼知道第t+1幀的時候A點的位置呢? 這就存在很多的光流計算方法了。
1981年,Horn和Schunck創造性地將二維速度場與灰度相聯繫,引入光流約束方程,得到光流計算的基本算法。人們基於不同的理論基礎提出各種光流計算方法,算法性能各有不同。Barron等人對多種光流計算技術進行了總結,按照理論基礎與數學方法的區別把它們分成四種:基於梯度的方法、基於匹配的方法、基於能量的方法、基於相位的方法。近年來神經動力學方法也頗受學者重視。
稀疏光流:只計算某些點集的光流。稠密光流:圖像上所有像素點的光流都計算出來。光流場是圖片中每個像素都有一個x方向和y方向的位移,所以在上面那些光流計算結束後得到的光流flow是個和原來圖像大小相等的雙通道圖像。
2.計算代碼示例
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
#define UNKNOWN_FLOW_THRESH 1e9
// Color encoding of flow vectors from:
// http://members.shaw.ca/quadibloc/other/colint.htm
// This code is modified from:
// http://vision.middlebury.edu/flow/data/
void makecolorwheel(vector<Scalar> &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<Scalar> 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<Vec2f>(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<Vec2f>(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(int, char**)
{
VideoCapture cap;
cap.open(0);
//cap.open("test_02.wmv");
if( !cap.isOpened() )
return -1;
Mat prevgray, gray, flow, cflow, frame;
namedWindow("flow", 1);
Mat motion2color;
for(;;)
{
double t = (double)cvGetTickCount();
cap >> frame;
cvtColor(frame, gray, CV_BGR2GRAY);
imshow("original", frame);
if( prevgray.data )
{
calcOpticalFlowFarneback(prevgray, gray, flow, 0.5, 3, 15, 3, 5, 1.2, 0);
motionToColor(flow, motion2color);
imshow("flow", motion2color);
}
if(waitKey(10)>=0)
break;
std::swap(prevgray, gray);
t = (double)cvGetTickCount() - t;
cout << "cost time: " << t / ((double)cvGetTickFrequency()*1000.) << endl;
}
return 0;
}