您的位置:首页 > 其它

图像分析之光流之经典

2016-01-21 13:03 218 查看
本文推导了几种经典的光流算法,分别来自文献[Lucas and Kanade, 1981],[Farneback, 2003] ,[Horn and Schunk, 1981]和[Brox et al. 2004]。为了公式符号的统一,没有采用原始文献的符号标记,为了简化推导,推导步骤与原始文献也不一样。此外,本文还给出了LK光流算法的实现。

更新记录

本文持续更新!如文中有错误,或你对本文有疑问或建议,欢迎留言或发邮件至quarrying#qq.com!

2016年01月20日,发表博文。

2016年01月23日,小修改。

2016年01月24日,增加内容,Farneback光流估计。

参考文献

[Lucas and Kanade, 1981] B.D. Lucas and T. Kanade, An Iterative Image Registration Technique with an Application to Stereo Vision

[Farneback, 2003] Two-Frame Motion Estimation Based on Polynomial Expansion

[Horn and Schunk, 1981] Determine Optical Flow

[Brox et al. 2004] High accuracy optical flow estimation based on a theory for warping

http://www.cnblogs.com/dzyBK/p/5096860.html

https://en.wikipedia.org/wiki/Lucas%E2%80%93Kanade_method

https://en.wikipedia.org/wiki/Horn%E2%80%93Schunck_method

https://en.wikipedia.org/wiki/Optical_Flow

相关代码

LK光流实现,仅展示原理,没有注重效率。

/**
* @brief	LK optical flow
* @author	quarryman
* @date		2016-01-20
*/
#include <cv.h>
#include <highgui.h>

#define KCV_IMAGE_ROW(img,type,row)\
((type*)((img)->imageData + (img)->widthStep*(row)))
#define KCV_IMAGE_ELEM_PTR(img,type,row,col)\
(((type*)((img)->imageData + (img)->widthStep*(row))) + (col))
#define KCV_IMAGE_ELEM(img,type,row,col)\
(((type*)((img)->imageData + (img)->widthStep*(row)))[(col)])

typedef struct tagKCvOpticalFlowProcessData
{
int frameNo;
int windowSize;
int skipStep;
IplImage* currFrameGray;
IplImage* prevFrame;
IplImage* frameSmooth;
IplImage* dx;
IplImage* dy;
IplImage* diff;
CvMat* A;
CvMat* At;
CvMat* AtA;
CvMat* b;
CvMat* Atb;
CvMat* x;
}KCvOpticalFlowProcessData;

typedef void* KCvHandle;

KCvHandle kcvOpticalFlowCreate(int w, int h, int windowSize, int skipStep)
{
KCvOpticalFlowProcessData* processData = NULL;
processData = (KCvOpticalFlowProcessData*)malloc(sizeof(*processData));

processData->frameNo = 0;
windowSize = windowSize | 1;
processData->windowSize = windowSize;
processData->skipStep = skipStep;
CvSize size = { w, h };
processData->currFrameGray = cvCreateImage(size, 8, 1);
processData->prevFrame = cvCreateImage(size, 8, 1);
processData->frameSmooth = cvCreateImage(size, 8, 1);
processData->dx = cvCreateImage(size, IPL_DEPTH_16S, 1);
processData->dy = cvCreateImage(size, IPL_DEPTH_16S, 1);
processData->diff = cvCreateImage(size, IPL_DEPTH_16S, 1);
processData->A = cvCreateMat(windowSize*windowSize, 2, CV_32F);
processData->At = cvCreateMat(2, windowSize*windowSize, CV_32F);
processData->AtA = cvCreateMat(2, 2, CV_32F);
processData->b = cvCreateMat(windowSize*windowSize, 1, CV_32F);
processData->Atb = cvCreateMat(2, 1, CV_32F);
processData->x = cvCreateMat(2, 1, CV_32F);

return (KCvHandle)processData;
}

int kcvOpticalFlowRelease(KCvHandle* handle)
{
if ((handle != NULL) && (*handle != NULL))
{
KCvOpticalFlowProcessData* processData = (KCvOpticalFlowProcessData*)*handle;
cvReleaseImage(&processData->currFrameGray);
cvReleaseImage(&processData->prevFrame);
cvReleaseImage(&processData->frameSmooth);
cvReleaseImage(&processData->dx);
cvReleaseImage(&processData->dy);
cvReleaseImage(&processData->diff);
cvReleaseMat(&processData->A);
cvReleaseMat(&processData->At);
cvReleaseMat(&processData->AtA);
cvReleaseMat(&processData->b);
cvReleaseMat(&processData->Atb);
cvReleaseMat(&processData->x);

free(processData);
*handle = NULL;
}
return 0;
}

int kcvOpticalFlowProcess(KCvHandle handle, const IplImage* currFrame,
IplImage* velx, IplImage* vely)
{
KCvOpticalFlowProcessData* processData = (KCvOpticalFlowProcessData*)handle;
int frameNo = processData->frameNo;
int windowSize = processData->windowSize;
int windowRadius = windowSize >> 1;
int skipStep = processData->skipStep;
IplImage* currFrameGray = processData->currFrameGray;
IplImage* prevFrame = processData->prevFrame;
IplImage* frameSmooth = processData->frameSmooth;
IplImage* dx = processData->dx;
IplImage* dy = processData->dy;
IplImage* diff = processData->diff;
CvMat* A = processData->A;
CvMat* At = processData->At;
CvMat* AtA = processData->AtA;
CvMat* b = processData->b;
CvMat* Atb = processData->Atb;
CvMat* x = processData->x;

if (currFrame->nChannels == 1)
{
cvCopy(currFrame, currFrameGray);
}
else
{
cvCvtColor(currFrame, currFrameGray, CV_BGR2GRAY);
}

if (frameNo < 1)
{
cvSetZero(velx);
cvSetZero(vely);
}
else
{
cvSmooth(currFrameGray, frameSmooth);
cvSobel(frameSmooth, dx, 1, 0);
cvSobel(frameSmooth, dy, 0, 1);
cvSub(prevFrame, currFrameGray, diff);

int w = dx->width;
int h = dx->height;
int sstep = dx->widthStep;
int dstep = velx->widthStep;
const short* ptrDx = (short*)dx->imageData;
const short* ptrDy = (short*)dy->imageData;
const short* ptrDiff = (short*)diff->imageData;
float* ptrVelx = KCV_IMAGE_ELEM_PTR(velx, float, windowRadius, windowRadius);
float* ptrVely = KCV_IMAGE_ELEM_PTR(vely, float, windowRadius, windowRadius);

sstep /= sizeof(ptrDx[0]);
dstep /= sizeof(ptrVelx[0]);
for (int i = windowRadius; i < h - windowRadius; i += skipStep)
{
for (int j = windowRadius; j < w - windowRadius; j += skipStep)
{
int index = 0;
const short* ptrDx2 = ptrDx + j;
const short* ptrDy2 = ptrDy + j;
const short* ptrDiff2 = ptrDiff + j;
for (int ii = -windowRadius; ii <= windowRadius; ++ii)
{
for (int jj = -windowRadius; jj <= windowRadius; ++jj)
{
CV_MAT_ELEM(*A, float, index, 0) = ptrDx2[jj] / 4.0;
CV_MAT_ELEM(*A, float, index, 1) = ptrDy2[jj] / 4.0;
CV_MAT_ELEM(*b, float, index, 0) = ptrDiff2[jj];
++index;
}
ptrDx2 += sstep;
ptrDy2 += sstep;
ptrDiff2 += sstep;
}
cvTranspose(A, At);
cvMatMul(At, A, AtA);
cvMatMul(At, b, Atb);
cvSolve(AtA, Atb, x);
ptrVelx[j] = CV_MAT_ELEM(*x, float, 0, 0);
ptrVely[j] = CV_MAT_ELEM(*x, float, 1, 0);
}
ptrDx += sstep * skipStep;
ptrDy += sstep * skipStep;
ptrDiff += sstep * skipStep;
ptrVelx += dstep * skipStep;
ptrVely += dstep * skipStep;
}
}

cvCopy(currFrameGray, prevFrame);
++processData->frameNo;

return 0;
}

void kcvDrawFrameNo(IplImage* img, int frameNo, CvScalar clr)
{
char text[64] = { 0 };
CvFont font = cvFont(1, 1);
sprintf(text, "%d", frameNo);
cvPutText(img, text, cvPoint(10, 20), &font, clr);
}

void kcvDrawArrow(CvArr* img, CvPoint pt1, CvPoint pt2, int length, int theta,
CvScalar color, int thickness = 1, int line_type = 8, int shift = 0)
{
// 箭头主线的倾斜角度
double angle = atan2((double)pt1.y - pt2.y, (double)pt1.x - pt2.x);

// 绘制箭头主线
cvLine(img, pt1, pt2, color, thickness, CV_AA, 0);
// 绘制箭头上方短线
CvPoint arrow;
arrow.x = (int)(pt2.x + length * cos(angle + CV_PI*theta / 180));
arrow.y = (int)(pt2.y + length * sin(angle + CV_PI*theta / 180));
cvLine(img, arrow, pt2, color, thickness, CV_AA, 0);
// 绘制箭头上方短线
arrow.x = (int)(pt2.x + length * cos(angle - CV_PI*theta / 180));
arrow.y = (int)(pt2.y + length * sin(angle - CV_PI*theta / 180));
cvLine(img, arrow, pt2, color, thickness, CV_AA, 0);
}

void kcvDrawArrow(CvArr* img, CvPoint pt, double lineLength, double lineTheta,
int length, int theta, CvScalar color, int thickness = 1, int line_type = 8, int shift = 0)
{
CvPoint pt2;
pt2.x = static_cast<int>(pt.x + lineLength*cos(CV_PI*lineTheta / 180));
pt2.y = static_cast<int>(pt.y + lineLength*sin(CV_PI*lineTheta / 180));

kcvDrawArrow(img, pt, pt2, length, theta, color, thickness, line_type, shift);
}

int main(int argc, char** argv)
{
CvCapture* capture = cvCaptureFromFile("E:\\百度云同步盘\\2视觉之Datasets\\测试视频之常用\\PEA-120412.avi");
if (capture == NULL)
{
fprintf(stderr, "Can not open video file\n");
return -1;
}
int w = (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH);
int h = (int)cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT);
int windowSize = 15;
int skipStep = windowSize;

KCvHandle handle = kcvOpticalFlowCreate(w, h, windowSize, skipStep);
IplImage* velx = cvCreateImage(cvSize(w, h), 32, 1);
IplImage* vely = cvCreateImage(cvSize(w, h), 32, 1);

int key = 0, isPaused = 1;
int frameNo = 0, skipFrames = 0;
IplImage* frame = NULL;
while (frame = cvQueryFrame(capture))
{
if (frameNo < skipFrames){
frameNo++;
continue;
}

kcvOpticalFlowProcess(handle, frame, velx, vely);
kcvDrawFrameNo(frame, frameNo++, CV_RGB(255, 255, 255));

int w = velx->width;
int h = velx->height;
int step = velx->widthStep;
int windowRadius = (windowSize | 1) >> 1;
const float* ptrVelx = KCV_IMAGE_ELEM_PTR(velx, float, windowRadius, windowRadius);
const float* ptrVely = KCV_IMAGE_ELEM_PTR(vely, float, windowRadius, windowRadius);
step /= sizeof(ptrVelx[0]);
for (int i = windowRadius; i < h - windowRadius; i += skipStep)
{
for (int j = windowRadius; j < w - windowRadius; j += skipStep)
{
double len = abs(ptrVely[j]) + abs(ptrVelx[j]);
double angle = atan2(ptrVely[j], ptrVelx[j]) * 180 / CV_PI;
if (len > 0.8)
{
kcvDrawArrow(frame, cvPoint(j, i), 10 * MIN(len, 2), angle, 3, 45, CV_RGB(255, 0, 0));
}
}
ptrVelx += step * skipStep;
ptrVely += step * skipStep;
}
cvShowImage("frame", frame);

if (key == ' '){
if (isPaused == 0){
key = cvWaitKey(0);
isPaused = 1;
}
else if (isPaused == 1){
key = cvWaitKey(10);
isPaused = 0;
}
}
else if (key == '\x1b'){
cvDestroyAllWindows();
break;
}
else{
if (isPaused == 0){
key = cvWaitKey(10);
}
else if (isPaused == 1){
key = cvWaitKey(0);
}
}
}
cvWaitKey(0);
kcvOpticalFlowRelease(&handle);
cvReleaseCapture(&capture);

return 0;
}


正文

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: