【opencv学习】lucas金字塔光流算法的实现——基于opencv3.0+vs2013+windows10

来源:互联网 发布:淘宝双十一销量排行 编辑:程序博客网 时间:2024/04/30 09:39

1. 代码简介

程序平台:vs2013 opencv3.0 win10

安装与配置方法:http://blog.csdn.net/poem_qianmo/article/details/19809337

程序能够实现从摄像头或avi格式的视频文件读取每帧信息,用shi-Tomasi算法实现角点(特征点)的识别,用金字塔Lucas Kanade实现光流算法,最后根据算法识别出的信息画出箭头。

源代码来自

http://robotics.stanford.edu/~dstavens/cs223b/ 

http://blog.csdn.net/wendychueng/article/details/7388463

2.   代码

#include <stdio.h>  #include <windows.h>#include "cv.h"  #include "cxcore.h"  #include "highgui.h"  #include <opencv2\opencv.hpp>  using namespace cv;  static const double pi = 3.14159265358979323846;inline static double square(int a){return a * a;}/*该函数目的:给img分配内存空间,并设定format,如位深以及channel数*/inline static void allocateOnDemand(IplImage **img, CvSize size, int depth, int channels){if (*img != NULL) return;*img = cvCreateImage(size, depth, channels);if (*img == NULL){fprintf(stderr, "Error: Couldn't allocate image.  Out of memory?\n");exit(-1);}}/*主函数,原程序是读取avi视频文件,然后处理,我简单改成从摄像头直接读取数据*/int main(int argc, char *argv[]){     //读取摄像头VideoCapture cap(0);     //读取视频文件 //VideoCapture cap; cap.open("optical_flow_input.avi");if (!cap.isOpened()){return -1;}Mat frame;/*bool stop = false;while (!stop){cap >> frame;//cvtColor(frame, edges, CV_RGB2GRAY);//GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);//Canny(edges, edges, 0, 30, 3);//imshow("当前视频", edges);imshow("当前视频", frame);if (waitKey(30) >= 0)stop = true;} *///CvCapture *input_video = cvCaptureFromFile("optical_flow_input.avi");//cv::VideoCapture cap = *(cv::VideoCapture *) userdata;   //if (input_video == NULL)//{//fprintf(stderr, "Error: Can't open video device.\n");//return -1;//}/*先读取一帧,以便得到帧的属性,如长、宽等*///cvQueryFrame(input_video);/*读取帧的属性*/CvSize frame_size;frame_size.height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);frame_size.width  = cap.get(CV_CAP_PROP_FRAME_WIDTH); /*********************************************************//*用于把结果写到文件中去,非必要int frameW = frame_size.height; // 744 for firewire cameras  int frameH = frame_size.width; // 480 for firewire cameras  VideoWriter writer("VideoTest.avi", -1, 25.0, cvSize(frameW, frameH), true);/*开始光流法*///VideoWriter writer("VideoTest.avi", CV_FOURCC('D', 'I', 'V', 'X'), 25.0, Size(640, 480), true);while (true){static IplImage *frame = NULL, *frame1 = NULL, *frame1_1C = NULL,*frame2_1C = NULL, *eig_image = NULL, *temp_image = NULL,*pyramid1 = NULL, *pyramid2 = NULL;Mat framet;/*获取第一帧*///cap >> framet;cap.read(framet);Mat edges;//黑白抽象滤镜模式// cvtColor(framet, edges, CV_RGB2GRAY);//GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);//Canny(edges, edges, 0, 30, 3);//转换mat格式到lpiimage格式frame = &IplImage(framet);if (frame == NULL){fprintf(stderr, "Error: Hmm. The end came sooner than we thought.\n");return -1;}/*由于opencv的光流函数处理的是8位的灰度图,所以需要创建一个同样格式的IplImage的对象*/allocateOnDemand(&frame1_1C, frame_size, IPL_DEPTH_8U, 1);/* 把摄像头图像格式转换成OpenCV惯常处理的图像格式*/cvConvertImage(frame, frame1_1C, 0);/* 我们需要把具有全部颜色信息的原帧保存,以备最后在屏幕上显示用*/allocateOnDemand(&frame1, frame_size, IPL_DEPTH_8U, 3);cvConvertImage(frame, frame1, 0);/* 获取第二帧 *///cap >> framet;cap.read(framet);//cvtColor(framet, edges, CV_RGB2GRAY);//GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);//Canny(edges, edges, 0, 30, 3);frame = &IplImage(framet);if (frame == NULL){fprintf(stderr, "Error: Hmm. The end came sooner than we thought.\n");return -1;}/*原理同上*/allocateOnDemand(&frame2_1C, frame_size, IPL_DEPTH_8U, 1);cvConvertImage(frame, frame2_1C, 0);/*********************************************************开始shi-Tomasi算法,该算法主要用于feature selection,即一张图中哪些是我们感兴趣需要跟踪的点(interest point)input:* "frame1_1C" 输入图像.* "eig_image" and "temp_image" 只是给该算法提供可操作的内存区域.* 第一个".01" 规定了特征值的最小质量,因为该算法要得到好的特征点,哪就需要一个选择的阈值* 第二个".01" 规定了像素之间最小的距离,用于减少运算复杂度,当然也一定程度降低了跟踪精度* "NULL" 意味着处理整张图片,当然你也可以指定一块区域output:* "frame1_features" 将会包含fram1的特征值* "number_of_features" 将在该函数中自动填充上所找到特征值的真实数目,该值<= 400**********************************************************//*开始准备该算法需要的输入*//* 给eig_image,temp_image分配空间*/allocateOnDemand(&eig_image, frame_size, IPL_DEPTH_32F, 1);allocateOnDemand(&temp_image, frame_size, IPL_DEPTH_32F, 1);/* 定义存放frame1特征值的数组,400只是定义一个上限 */CvPoint2D32f frame1_features[400];int    number_of_features = 400;/*开始跑shi-tomasi函数*/cvGoodFeaturesToTrack(frame1_1C, eig_image, temp_image,frame1_features, &number_of_features, .01, .01, NULL);/**********************************************************开始金字塔Lucas Kanade光流法,该算法主要用于feature tracking,即是算出光流,并跟踪目标。input:* "frame1_1C" 输入图像,即8位灰色的第一帧* "frame2_1C" 第二帧,我们要在其上找出第一帧我们发现的特征点在第二帧的什么位置* "pyramid1" and "pyramid2" 是提供给该算法可操作的内存区域,计算中间数据* "frame1_features" 由shi-tomasi算法得到的第一帧的特征点.* "number_of_features" 第一帧特征点的数目* "optical_flow_termination_criteria" 该算法中迭代终止的判别,这里是epsilon<0.3,epsilon是两帧中对应特征窗口的光度之差的平方,这个以后的文章会讲* "0" 这个我不知道啥意思,反正改成1就出不来光流了,就用作者原话解释把means disable enhancements.  (For example, the second array isn'tpre-initialized with guesses.)output:* "frame2_features" 根据第一帧的特征点,在第二帧上所找到的对应点* "optical_flow_window" lucas-kanade光流算法的运算窗口,具体lucas-kanade会在下一篇详述* "5" 指示最大的金字塔层数,0表示只有一层,那就是没用金字塔算法* "optical_flow_found_feature" 用于指示在第二帧中是否找到对应特征值,若找到,其值为非零* "optical_flow_feature_error" 用于存放光流误差**********************************************************//*开始为pyramid lucas kanade光流算法输入做准备*/CvPoint2D32f frame2_features[400];/* 该数组相应位置的值为非零,如果frame1中的特征值在frame2中找到 */char optical_flow_found_feature[400];/* 数组第i个元素表对应点光流误差*/float optical_flow_feature_error[400];/*lucas-kanade光流法运算窗口,这里取3*3的窗口,可以尝试下5*5,区别就是5*5出现aperture problem的几率较小,3*3运算量小,对于feature selection即shi-tomasi算法来说足够了*/CvSize optical_flow_window = cvSize(5, 5);// CvSize optical_flow_window = cvSize(5, 5);/* 终止规则,当完成20次迭代或者当epsilon<=0.3,迭代终止,可以尝试下别的值*/CvTermCriteria optical_flow_termination_criteria= cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, .3);/*分配工作区域*/allocateOnDemand(&pyramid1, frame_size, IPL_DEPTH_8U, 1);allocateOnDemand(&pyramid2, frame_size, IPL_DEPTH_8U, 1);/*开始跑该算法*/cvCalcOpticalFlowPyrLK(frame1_1C, frame2_1C, pyramid1, pyramid2,frame1_features, frame2_features, number_of_features,optical_flow_window, 5, optical_flow_found_feature,optical_flow_feature_error, optical_flow_termination_criteria, 0);/*画光流场,画图是依据两帧对应的特征值,这个特征值就是图像上我们感兴趣的点,如边缘上的点P(x,y)*/for (int i = 0; i< number_of_features; i++){/* 如果没找到对应特征点 */if (optical_flow_found_feature[i] == 0)continue;int line_thickness;line_thickness = 1;/* CV_RGB(red, green, blue) is the red, green, and blue components* of the color you want, each out of 255.*/CvScalar line_color;line_color = CV_RGB(255, 0, 0);/*画箭头,因为帧间的运动很小,所以需要缩放,不然看不见箭头,缩放因子为3*/CvPoint p, q;p.x = (int)frame1_features[i].x;p.y = (int)frame1_features[i].y;q.x = (int)frame2_features[i].x;q.y = (int)frame2_features[i].y;double angle;angle = atan2((double)p.y - q.y, (double)p.x - q.x);double hypotenuse;hypotenuse = sqrt(square(p.y - q.y) + square(p.x - q.x));/*执行缩放*/q.x = (int)(p.x - 5 * hypotenuse * cos(angle));q.y = (int)(p.y - 5 * hypotenuse * sin(angle));/*画箭头主线*//* "frame1"要在frame1上作画.* "p" 线的开始点.* "q" 线的终止点.* "CV_AA" 反锯齿.* "0" 没有小数位.*/cvLine(frame1, p, q, line_color, line_thickness, CV_AA, 0);/* 画箭的头部*/p.x = (int)(q.x + 9 * cos(angle + pi / 4));p.y = (int)(q.y + 9 * sin(angle + pi / 4));cvLine(frame1, p, q, line_color, line_thickness, CV_AA, 0);p.x = (int)(q.x + 9 * cos(angle - pi / 4));p.y = (int)(q.y + 9 * sin(angle - pi / 4));cvLine(frame1, p, q, line_color, line_thickness, CV_AA, 0);}/*显示图像*//*创建一个名为optical flow的窗口,大小自动改变*/cvNamedWindow("Optical Flow", CV_WINDOW_NORMAL);cvFlip(frame1, NULL, 2);cvShowImage("Optical Flow", frame1);/*延时,要不放不了*/ cvWaitKey(33);/*写入到文件中去*/// cv::Mat m = cv::cvarrToMat(frame1);//转换lpimgae到mat格式// writer << m;//opencv3.0 version writer}cap.release();cvWaitKey(33);system("pause");}


3.   问题与解决方法

3.1源代码老旧问题

源代码使用的opencv某一些函数比较老,在opencv3.0环境下能识别视频信息,但是不能打开摄像头,

比如CvCapture *input_video = cvCreateCameraCapture(0); 这段代码在opencv3.0环境下能打开窗口,但是内容全黑,不能读取帧信息,具体原因不明。

解决方法:

使用VideoCapture cap(0);成功打开摄像头,      

使用Mat frame; cap.read(framet);可以读取摄像头帧信息,但是会引发第二个问题。

3.2新老代码函数转换问题

Opencv3.0支持mat 来读取帧信息,但是源代码里大多用IplImage处理帧信息,两个函数结构不能通用

使用转换函数转换两个函数,    

转换mat格式到lpiimage格式 frame =&IplImage(framet);

转换lpimgae到mat格式,这里有一个问题是:大多数网上的转换函数都是这个

Mat M(srcImg);Mat M(srcImg, false);Mat M = srcImg;

然而在opencv3.0下并不能用,后来找到这个函数cv::Mat m = cv::cvarrToMat(frame1);终于可以用了,出处:http://blog.sina.com.cn/s/blog_500bd63c0102vsf5.html

3.3老代码写视频问题

老代码写入视频的函数是

writer=cvCreateVideoWriter("out.avi",CV_FOURCC('P','I','M','1'),fps,cvSize(frameW,frameH),isColor);  和cvWriteFrame(writer,frame1);

然而运行完了时候输出的视频只有6k

后来找到opencv3.0版本的一个函数 

VideoWriter writer("VideoTest.avi",CV_FOURCC('D', 'I','V', 'X'), 25.0, Size(640, 480), true);和writer << m(m是要写入帧的名字)用了新代码终于可以些视频了,但是输出的视频无法打开,正在找解决方案。

3.4   视频翻转问题

想用镜面的方式显示视频,用的cvFlip(frame1,NULL, 2);这函数,“2“那个参数的意思是,只要这个参数大于0就是水平翻转,我的理解是这样的。

3.5   准确性修改

实现的代码,箭头太小,箭头各种乱飞,解决方法CvSize optical_flow_window = cvSize(5, 5);光流窗口改成5*5,绘制箭头的缩放因子改成5,看起来明显一点;

 

4.   相关代码信息

【OpenCV入门教程之十六】OpenCV角点检测之Harris角点检测:http://blog.csdn.net/poem_qianmo/article/details/29356187

 

OpenCV成长之路(10):视频的处理:http://blog.jobbole.com/84231/

 

我的OpenCV学习笔记(12):VideoCapture类:http://blog.csdn.net/thefutureisour/article/details/7530344


效果:



0 0
原创粉丝点击