光流法_OpenCV_详解

一.基本概念

光流的概念是Gibson于1950年提出的。所谓光流是指图像中模式运动的速度,光流场是一种二维(2D)瞬时速度场,其中二维速度向量是可见的三维速度向量在成像平面上的投影。光流法是把检测区域的图像变为速度的矢量场,每一个向量表示了景物中一个点在图像中位置的瞬时变化。因此,光流场携带了有关物体运动和景物三维结构的丰富信息,通过对速度场(光流场)的分析可以判断在检测区域内车辆的有无。

思路:求得整个图像检测区域的速度场,根据每个像素点的速度向量特征,可以对图像进行分析。如果是静止背景即无车通过时,则光流向量在整个检测区域是连续变化的;当有车通过时,光流向量必然和其邻域背景的光流向量不同,从而检测出车辆及其出现的位置。

光流法的前提假设:
(1)相邻帧之间的亮度恒定值,
(2)相邻视频帧的取帧的时间连续,或者相邻帧之间物体的运动比较“微小”;
(3)保持空间一致性,即,同一子图像的像素点具有相同的运动。

原理:
(1)对一个连续的视频帧序列进行处理;
(2)针对每一个视频序列,利用一定的目标检测方法,检测可能出现的前景目标;
(3)如果某一帧出现了前景目标,找到其具有代表性的关键特征点(如shi-Tomasi算法);
(4)对之后的任意两个相邻视频帧而言,寻找上一帧中出现的关键特征点在当前帧中的最佳位置,从而得到前景目标在当前帧中的位置坐标;
(5)如此迭代进行,便可实现目标的跟踪;

二.程序中重要函数解释

      (1)void cvGoodFeaturesToTrack( const CvArr* image, CvArr* eig_image, CvArr* temp_image,CvPoint2D32f* corners, int* corner_count,double quality_level, double                   min_distance,const CvArr* mask=NULL );

                程序中此函数代码:cvGoodFeaturesToTrack(frame1_1C, eig_image, temp_image, frame1_features, &number_of_features, .01, .01, NULL);
                这是shi-Tomasi算法,该算法主要用于feature selection,即一张图中哪些是我 们感兴趣需要跟踪的点(interest point) 
                input:                     
                     * "frame1_1C" 输入图像. 
                     * "eig_image" and "temp_image" 只是给该算法提供可操作的内存区域
                     * 第一个".01" 规定了特征值的最小质量,因为该算法要得到好的特征点,哪就需要一个选择的阈值
                     * 第二个".01" 规定了像素之间最小的距离,用于减少运算复杂度,当然也一定程度降低了跟踪精度
                     * "NULL" 意味着处理整张图片,当然你也可以指定一块区域
                output: 
                     * "frame1_features" 将会包含fram1的特征值 
                     * "number_of_features" 将在该函数中自动填充上所找到特征值的真实数目

       (2)迭代算法的终止准则 

                 typedef struct CvTermCriteria  
                {  
    int    type;  /* CV_TERMCRIT_ITER 和CV_TERMCRIT_EPS二值之一,或者二者的组合 */  
  int    max_iter; /* 最大迭代次数 */  
            double epsilon; /* 结果的精确性 */  
                 } 

       (3) void cvCalcOpticalFlowPyrLK( const CvArr* prev, const CvArr* curr, CvArr* prev_pyr,CvArr* curr_pyr, const CvPoint2D32f* prev_features, CvPoint2D32f*curr_features, int count, CvSize win_size, int level, char* status,  float*track_error, CvTermCriteria criteria, int flags );

   计算一个稀疏特征集的光流,使用金字塔中的迭代 Lucas-Kanade 方法。参数:*prev在时间 t 的第一帧;*curr在时间 t + dt 的第二帧;*prev_pyr第一帧的金字塔缓存. 如果指针非 NULL , 则缓存必须有足够的空间来存储金字塔从层 1 到层 #level 的内容。尺寸 (image_width+8)*image_height/3 比特足够了;*curr_pyr与 prev_pyr 类似, 用于第二帧;*prev_features需要发现光流的点集;*curr_features包含新计算出来的位置的 点集;*count特征点的数目;*win_size每个金字塔层的搜索窗口尺寸;*level最大的金字塔层数。如果为 0 , 不使用金字塔 (即金字塔为单层), 如果为 1 , 使用两层,下面依次类推;*status数组。如果对应特征的光流被发现,数组中的每一个元素都被设置为 1,否则设置为 0;*error双精度数组,包含原始图像碎片与移动点之间的差。为可选参数,可以是 NULL ;*criteria准则,指定在每个金字塔层,为某点寻找光流的迭代过程的终止条件;*flags其它选项:(1)CV_LKFLOW_PYR_A_READY , 在调用之前,第一帧的金字塔已经准备好(2)CV_LKFLOW_PYR_B_READY , 在调用之前,第二帧的金字塔已经准备好(3)CV_LKFLOW_INITIAL_GUESSES , 在调用之前,数组 B 包含特征的初始坐标 (Hunnish: 在本节中没有出现数组 B,不知是指的哪一个)

函数 cvCalcOpticalFlowPyrLK 实现了金字塔中 Lucas-Kanade 光流计算的稀疏迭代版本([Bouguet00])。它根据给出的前一帧特征点坐标计算当前视频帧上的特征点坐标。函数寻找具有子象素精度的坐标值。

两个参数 prev_pyr 和 curr_pyr 都遵循下列规则:如果图像指针为 0, 函数在内部为其分配缓存空间,计算金字塔,然后再处理过后释放缓存。否则,函数计算金字塔且存储它到缓存中,除非设置标识 CV_LKFLOW_PYR_A[B]_READY 。 图像应该足够大以便能够容纳Gaussian 金字塔数据。调用函数以后,金字塔被计算而且相应图像的标识可以被设置,为下一次调用准备就绪 (比如:对除了第一个图像的所有图像序列,标识 CV_LKFLOW_PYR_A_READY 被设置).

三.程序源代码

#include <stdio.h>
#include <cv.h>
#include <highgui.h>
#include <math.h>
static const double pi = 3.14159265358979323846;


inline static double square(int a)
{
	return a * a;
}


/*此函数主要目的:给img分配内存空间(除非此图像已经非NULL),并设定图像大小、位深以及通道数。
  即使该图像的大小、深度或信道数与要求的不同,也会创建一个非NULL图像。*/
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);
	}
}


int main(void)
{
	CvCapture *input_video = cvCaptureFromFile("video1.avi");  //创建一个对象,读取avi视频。
	if (input_video == NULL)
	{
		fprintf(stderr, "Error: Can't open video.\n");  //视频不存在或格式不支持时显示
		return -1;
	}

	cvQueryFrame( input_video );  // 读取一帧是为了获得帧的长宽。
	                                
	CvSize frame_size;
	frame_size.height =	(int) cvGetCaptureProperty( input_video, CV_CAP_PROP_FRAME_HEIGHT );
	frame_size.width =	(int) cvGetCaptureProperty( input_video, CV_CAP_PROP_FRAME_WIDTH );

	long number_of_frames;  //视频帧的长度

	cvSetCaptureProperty( input_video, CV_CAP_PROP_POS_AVI_RATIO, 1. );  //跳到视频结束,以便获取视频长度(帧数)
	number_of_frames = (int) cvGetCaptureProperty( input_video, CV_CAP_PROP_POS_FRAMES );  //得到帧数

	cvSetCaptureProperty( input_video, CV_CAP_PROP_POS_FRAMES, 0. );  //重新回到视频开始,以便下续步骤进行

    
/*开始进行光流法*/
	cvNamedWindow("Optical Flow", CV_WINDOW_AUTOSIZE);  //创建窗口,显示输出,大小自动调节
	long current_frame = 0;
	while(true)
	{
		static IplImage *frame = NULL, *frame1 = NULL, *frame1_1C = NULL, *frame2_1C =
		NULL, *eig_image = NULL, *temp_image = NULL, *pyramid1 = NULL, *pyramid2 = NULL;

		
		cvSetCaptureProperty( input_video, CV_CAP_PROP_POS_FRAMES, current_frame );
	
		frame = cvQueryFrame( input_video );  //读取第一帧
		if (frame == NULL)
		{
			fprintf(stderr, "Error: Hmm. The end came sooner than we thought.\n"); 
			return -1;
		}

		allocateOnDemand( &frame1_1C, frame_size, IPL_DEPTH_8U, 1 );  //分配给frame1_1C内存空间
		cvConvertImage(frame, frame1_1C, CV_CVTIMG_FLIP);  //将帧数据赋给frame1_1C

		allocateOnDemand( &frame1, frame_size, IPL_DEPTH_8U, 3 );  //把具有全部颜色信息的原帧保存,以备最后在屏幕上显示用
		cvConvertImage(frame, frame1, CV_CVTIMG_FLIP);

		frame = cvQueryFrame( input_video );  //读取第二帧
		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, CV_CVTIMG_FLIP);

	/* 施和托马斯特征跟踪! */

		allocateOnDemand( &eig_image, frame_size, IPL_DEPTH_32F, 1 );  //分配需要的内存
		allocateOnDemand( &temp_image, frame_size, IPL_DEPTH_32F, 1 );  //分配需要的内存

		CvPoint2D32f frame1_features[400];  //创建数组,存放一帧的特征

		int number_of_features;  //函数运行前先设定特征数的最大值,运行后将是找到的特征数真正数量
		number_of_features = 400;  //可以改变此值,折中精确性

		cvGoodFeaturesToTrack(frame1_1C, eig_image, temp_image, frame1_features, &number_of_features, .01, .01, NULL);  //施和托马斯算法

	/* 金字塔的 Lucas Kanade 光流法! */
	
		CvPoint2D32f frame2_features[400];  //数组用于存放帧二中来自帧一的特征。
		char optical_flow_found_feature[400];  //当且仅当frame1中的特征值在frame2中找到时,对应值为非零。
		float optical_flow_feature_error[400];  //数组第i个元素表对应点光流误差
		
		CvSize optical_flow_window = cvSize(3,3);  //lucas-kanade光流法运算窗口,可以去其他大小,不过运算量加大

		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 );

        //跑Lucas Kanade算法
		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;  //如果Lucas Kanade算法没找到特征点,跳过
			
			int line_thickness; 
			line_thickness = 1;
			
			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 - 3 * hypotenuse * cos(angle)); //放大三倍
			q.y = (int) (p.y - 3 * hypotenuse * sin(angle));

			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 );
		}

		cvShowImage("Optical Flow", frame1);

		int key_pressed;
		key_pressed = cvWaitKey(33);  //隔33ms显示一帧

		/* 触发B键,视频回一帧 */
		if (key_pressed == 'b' || key_pressed == 'B') current_frame--;
		else current_frame++;

		/* 不要超出视频的头和尾 */
		if (current_frame < 0) current_frame = 0;
		if (current_frame >= number_of_frames - 1) current_frame = number_of_frames - 2;
	}
}
【整合】
【链接】 http://download.csdn.net/detail/u012756029/6518397(《Optical Flow》 Written by David Stavens有ppt和注释,详细的英文解释)


  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您想了解关于稀疏在 Python OpenCV 中的应用和使用方是吗?那么我可以简单地为您介绍一下。 稀疏是一种计算图像中像素运动的方,它可以通过检测两个连续帧之间的像素强度变化来计算像素的运动向量。Python OpenCV提供了实现稀疏的函数 cv2.calcOpticalFlowPyrLK()。 以下是一个简单的示例代码,演示了如何使用稀疏来计算两个连续帧之间的像素运动: ``` import cv2 # 读取两个连续帧 frame1 = cv2.imread('frame1.jpg') frame2 = cv2.imread('frame2.jpg') # 转换为灰度图像 gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY) # 设置参数 lk_params = dict(winSize=(15, 15), maxLevel=2, criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)) # 获取关键点 p0 = cv2.goodFeaturesToTrack(gray1, mask=None, maxCorners=100, qualityLevel=0.3, minDistance=7) # 计算 p1, st, err = cv2.calcOpticalFlowPyrLK(gray1, gray2, p0, None, **lk_params) # 选择好的点 good_new = p1[st == 1] good_old = p0[st == 1] # 绘制运动向量 for i, (new, old) in enumerate(zip(good_new, good_old)): a, b = new.ravel() c, d = old.ravel() frame1 = cv2.circle(frame1, (a, b), 5, (0, 255, 0), -1) frame1 = cv2.line(frame1, (a, b), (c, d), (255, 0, 0), 2) # 显示结果 cv2.imshow('frame', frame1) cv2.waitKey(0) cv2.destroyAllWindows() ``` 在上述示例代码中,我们首先读取了两个连续帧,然后将它们转换为灰度图像。接下来,我们使用 cv2.goodFeaturesToTrack() 函数获取了第一帧中的关键点,并将这些关键点作为稀疏的输入。然后,我们使用 cv2.calcOpticalFlowPyrLK() 函数计算了两个连续帧之间的稀疏。最后,我们选择好的点,并使用 cv2.circle() 和 cv2.line() 函数绘制了运动向量。 希望这个简单的示例代码可以帮助您更好地理解稀疏在 Python OpenCV 中的应用和使用方

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值