learning opencv chapter2

本文介绍如何使用OpenCV进行视频处理,包括视频播放控制、图像变换、从摄像头读取数据及写入AVI视频文件等操作。文章通过具体示例展示了如何实现视频的平滑滤波、缩放和边缘检测等功能。

第一个程序——显示图像

#include "opencv2/highgui/highgui.hpp"

int main( int argc, char** argv )
{
  IplImage* img = cvLoadImage( argv[1] ); //IplImage储存图像文件的数据结构
  //cvNameWindow创建窗口,第一个参数指定窗口标题,并且其他函数通过此参数来与该窗口交互
  //第二个参数定义窗口属性,0窗口大小可调,CV_WINDOW_AUTOSIZE固定
  cvNamedWindow("Example1", CV_WINDOW_AUTOSIZE );
  cvNamedWindow("Example2", CV_WINDOW_AUTOSIZE );
  //cvShowImage显示IplImgae*指针对应的图像,第一个参数用于指定窗口,第二个参数为IplImage*
  cvShowImage("Example2", img );
  cvWaitKey(0);//等待键盘输入
  cvReleaseImage( &img );//释放IplImage*
  cvDestroyWindow("Example1");//销毁窗口
}


注意点:

  1. cvNameWindow:第一个参数指定窗口标题,当该窗口与其他函数进行交互时,利用该参数引用此窗口。cvShowImage显示图像,第一个参数指定窗口参数。注意在上面的代码中,我创建了两个窗口,而显示了一个图像,运行代码会发现该图像会出现在窗口“Example2”中。
  2. cvReleaseImage释放IplImage*所指向的内存,cvDestroyWindow也会同时释放为该窗口所分配的内存。养成随时释放内存的好习惯。
  3. IplImage, cvLoadImage跟Mat, imread有什么区别?IplImage, cvLoadImage是C接口,Mat, imread是C++接口,并且自动帮助处理内存问题,而且字母更少,推荐使用。


第二个程序——播放avi视频

#include "opencv2/highgui/highgui.hpp"

int main( int argc, char** argv ) { 
    cvNamedWindow( "Example2", CV_WINDOW_AUTOSIZE );
    //CvCapture* capture = cvCaptureFromAVI( argv[1] ); // either one will work
	//cvCreateFileCapture读入avi文件并返回指向CvCapture的指针,返回的CvCapture结构被初始化到对应avi文件开头
    CvCapture* capture = cvCreateFileCapture( argv[1] );
    IplImage* frame;
	//while循环读入avi文件
    while(1) {
		//cvQueryFrame参数为CvCapture指针,用来将下一帧视频文件载入内存,并且更新CvCapture指针
		//注意cvQueryFrame返回的图像内存问题
        frame = cvQueryFrame( capture );
        if( !frame ) break;
        cvShowImage( "Example2", frame );
		//每帧之间间隔33ms,也可以由用户按Esc(ASCLL码27)键退出视频
        char c = cvWaitKey(33);
        if( c == 27 ) break;
    }
    cvReleaseCapture( &capture );
    cvDestroyWindow( "Example2" );
}


注意点:

  1. cvQueryFrame返的IplImage*使用在CvCapture*中分配好的内存,并不像cvLoadImage一样为图像新分配内存,也就不需要通过cvReleaseImage释放。只需要最后统一释放CvCapture*.内存垃圾处理在opencv中很重要,但是我们只需要释放自己显示分配的内存空间。
  2. 上面的程序中设置帧间隔33ms,其实也根据CvCapture结构体读取视频实际帧率(见第四章)

视频播放控制

添加进度条来调整视频播放位置:cvCreateTrackerbar添加进度条,以视频总帧数作为进度条长度,进度条位置代表在视频中的帧数,每次进度条位置变化则自动调用回调函数来改变读取的帧(cvSetCaptureProperty)

#include <stdio.h>
#include <iostream>
#include <fstream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"

using namespace std;

//通过全局变量来表示滚动条位置
int        g_slider_position = 0;
//回调函数需要使用CvCapture对象,将其也设为全局变量
CvCapture* g_capture         = NULL;

//回调函数更新变量并重新设置视频读入位置
void onTrackbarSlide(int pos) {
	//cvSet(Get)CaptureProperty可以设置(查看)CvCapture对象的各种属性
	//参数CV_CAP_PROP_POS_FRAMES利用帧数来设置读入位置(CV_CAP_PROP_POS_AVI_RATIO利用视频长度比例来设置读入位置)
	cvSetCaptureProperty(
		g_capture,
		CV_CAP_PROP_POS_FRAMES,
		pos
		);
}


int main( int argc, char** argv ) {
	cvNamedWindow( "Example2_3", 0 );
	g_capture = cvCreateFileCapture( argv[1] );
	//IplImage *foo = cvQueryFrame( g_capture);

	//读取总帧数
	int frames = (int) cvGetCaptureProperty(
		g_capture,
		CV_CAP_PROP_FRAME_COUNT
		);

	if( frames )
	{
		//创建滚动条,参数依次为:滚动条名称,所属窗口,绑定变量,最大值,回调函数(在不需要时置为空)
		cvCreateTrackbar(
			"Position",
			"Example2_3",
			&g_slider_position,
			frames,
			onTrackbarSlide
			);
	}

	IplImage* frame;
	frames = 0;
	while(1) {
		frame = cvQueryFrame( g_capture );
		if( !frame ) break;
		//cvGetCaptureProperty读取当前所在帧
		int frames = cvGetCaptureProperty( g_capture, CV_CAP_PROP_POS_FRAMES);
		//更新进度条
		cvSetTrackbarPos("Position","Example2_3",frames);
		cvShowImage( "Example2_3", frame );
		char c = (char)cvWaitKey(10);
		if( c == 27 ) break;
	}
	cvReleaseCapture( &g_capture );
	cvDestroyWindow( "Example2_3" );
	return(0);
}


注意点:

  1. 上述函数在linux平台上可能运行错误,详情可以下载原始示例代码,其中有说明以及针对性的代码变化


更多变换

基本的计算机视觉任务包括对视频流的滤波,实现方法是随着视频的播放对其中每一帧进行简单的运算。下面的代码分别是对图像进行Gaussian平滑滤波,比例为2的缩放处理,以及canny边缘检测。

#include "opencv2/highgui/highgui.hpp"
#include <opencv/cv.h>

IplImage* doSmooth(IplImage*);
IplImage* doPyrDown(IplImage*, int);
IplImage* doCanny(IplImage* in, double, double, double);

int main( int argc, char** argv )
{
  IplImage* img = cvLoadImage( argv[1] );
  cvNamedWindow("Example1", CV_WINDOW_AUTOSIZE );
  cvNamedWindow("Example2", CV_WINDOW_AUTOSIZE );
  IplImage* img2 = doSmooth(img);
  //IplImage* img2 = doPyrDown( img, IPL_GAUSSIAN_5x5 );
  //IplImage* img2 = doCanny( img, 10, 100, 3 );
  cvShowImage("Example1", img );
  cvShowImage("Example2", img2);
  cvWaitKey(0);
  cvReleaseImage( &img );
  cvReleaseImage( &img2);
  cvDestroyAllWindows();
}

/**********************变换*************************/

IplImage* doSmooth( IplImage* in )
{
    //cvCeateImage 分配自己的图像结构空间来存储平滑后的图像,第一个参数说明当前图像大小
	//第二个参数指明各通道像素点的数据类型(或者说图像元素的位深度,e.g image->depth),第三个参数说明通道数
    IplImage* out = cvCreateImage(
        cvGetSize(in),
        IPL_DEPTH_8U,
        3
    );
    
    //高斯平滑,后面会再学
    cvSmooth( in, out, CV_GAUSSIAN, 5, 5 );
    cvSmooth( out,	 out, CV_GAUSSIAN, 5, 5 );
    return out;
}


//比例为2的缩放处理
IplImage* doPyrDown(
  IplImage* in,
  int       filter = IPL_GAUSSIAN_5x5)
{

    // Best to make sure input image is divisible by two.
    //
    assert( in->width%2 == 0 && in->height%2 == 0 );

    IplImage* out = cvCreateImage( 
        cvSize( in->width/2, in->height/2 ),
        in->depth,
        in->nChannels
    );
    cvPyrDown( in, out );
    return( out );
};

//canny边缘检测
IplImage* doCanny(
    IplImage* in,
    double    lowThresh,
    double    highThresh,
    double    aperture)
{
    if (in->nChannels != 1)
        return(0); // Canny only handles gray scale images
    IplImage* out = cvCreateImage( 
        cvGetSize( in ),
        in->depth, //IPL_DEPTH_8U,    
        1);
    cvCanny( in, out, lowThresh, highThresh, aperture );
    return( out );
};


在 learning opencv上提到,对前面的函数进行嵌套调用是个很不好的注意,这样将会对内存空间的释放造成困难。建议的方式是使用“自清理”模式,在每一个封装中加入

cvRealeaseImage( &in );

但是如此一来就无法对中间步骤的图像进行处理。或者改用每个独立阶段释放内存的方式简化图像处理流程。嵌套调用以及改进的方式如下:

//嵌套调用
int main( int argc, char** argv )
{
  cvNamedWindow("Example Gray", CV_WINDOW_AUTOSIZE );
  cvNamedWindow("Example Pyr", CV_WINDOW_AUTOSIZE );
  cvNamedWindow("Example Canny", CV_WINDOW_AUTOSIZE );
  IplImage* img_rgb  = cvLoadImage( argv[1] );
  IplImage* img_gry  = cvCreateImage( cvSize( img_rgb->width,img_rgb->height ), img_rgb->depth, 1);
  cvCvtColor(img_rgb, img_gry ,CV_BGR2GRAY);
  IplImage* img_pyr  = doPyrDown( img_gry, IPL_GAUSSIAN_5x5 );
  IplImage* img_pyr2 = doPyrDown( img_pyr, IPL_GAUSSIAN_5x5 );
  IplImage* img_cny  = doCanny( img_pyr2, 10, 100, 3 );

  cvShowImage("Example Gray", img_gry );
  cvShowImage("Example Pyr", img_pyr2 );
  cvShowImage("Example Canny", img_cny );
  cvWaitKey(0);
  cvReleaseImage( &img_cny);
  cvReleaseImage( &img_rgb);
  cvReleaseImage( &img_pyr);
  cvReleaseImage( &img_pyr2);
  cvReleaseImage( &img_gry);
  cvDestroyAllWindows();
}

//每个独立阶段释放内存
int main( int argc, char** argv )
{
  cvNamedWindow("Example Gray", CV_WINDOW_AUTOSIZE );
  cvNamedWindow("Example Pyr", CV_WINDOW_AUTOSIZE );
  cvNamedWindow("Example Canny", CV_WINDOW_AUTOSIZE );
  IplImage* img_rgb = cvLoadImage( argv[1] );
  IplImage* out;

  out = cvCreateImage( cvSize( img_rgb->width,img_rgb->height ), img_rgb->depth, 1);
  cvCvtColor(img_rgb, out ,CV_BGR2GRAY);
  cvReleaseImage(&img_rgb);

  cvShowImage("Example Gray", out );
  out = doPyrDown( out );
  out = doPyrDown( out );
  cvShowImage("Example Pyr", out );
  out = doCanny( out, 10, 100, 3 );
  cvShowImage("Example Canny", out );

  cvWaitKey(0);
  cvReleaseImage( &out);
  cvDestroyAllWindows();
}



从摄像机读入数据

只要将上面示例代码2中的

cvCreateFileCapture(const char* fname);
改为

cvCreateCameraCapture(0);
他们都是同样返回CvCapture*指针,CvCapture*结构初始化后,从视频文件或摄像设备读入图像没有区别。


写入AVI视频文件

创建一个视频流文件以便写入视频文件,实现该功能的函数是cvCreateVideoWriter(),利用cvWriterFrame逐帧将视频流写入文件

#include "opencv/cv.h"
#include "opencv/highgui.h"


// Convert a video to log-polar
// argv[1]: input video file
// argv[2]: name of new output file


int main( int argc, char* argv[] ) {
	cvNamedWindow( "Example2_10", CV_WINDOW_AUTOSIZE );
	cvNamedWindow( "Log_Polar", CV_WINDOW_AUTOSIZE );
	CvCapture* capture = cvCreateFileCapture( argv[1] );
	if (!capture){
		return -1;
	}
	//用于读取原始视频帧
	IplImage* bgr_frame;

	//fps视频帧率
	double fps = cvGetCaptureProperty (
		capture,
		CV_CAP_PROP_FPS
		);
	//视频图像大小
	CvSize size = cvSize(
		(int)cvGetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH),
		(int)cvGetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT)
		);



	//CvVideoWriter创建视频流文件,参数依次为:文件名称,
	//文件格式(OpenCv利用CV_FOURCC()宏来指定编码格式),帧率,视频图像大小
	CvVideoWriter* writer = cvCreateVideoWriter(
		argv[2],
		CV_FOURCC('M','J','P','G'),    
		fps,
		size
		);


	//对数极坐标格式帧
	IplImage* logpolar_frame = cvCreateImage(
		size,
		IPL_DEPTH_8U,
		3
		);

	//逐帧处理图像并且利用cvWriteFramw()将视频流写入视频文件
	while( (bgr_frame=cvQueryFrame(capture)) != NULL ) {
		cvShowImage( "Example2_10", bgr_frame );
		cvLogPolar( bgr_frame, logpolar_frame,  //This is just a fun conversion that mimic's the human visual system
			cvPoint2D32f(bgr_frame->width/2,
			bgr_frame->height/2), 
			40, 
			CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS );
		cvShowImage( "Log_Polar", logpolar_frame );

		cvWriteFrame( writer, logpolar_frame );

		char c = cvWaitKey(10);
		if( c == 27 ) break;
	}

	cvReleaseVideoWriter( &writer );
	cvReleaseImage( &logpolar_frame );
	cvReleaseImage( &bgr_frame );
	cvReleaseCapture( &capture );
	cvDestroyAllWindows();
}

注意:

  1. OpenCv利用CV_FOURCC()宏来指定编码格式,参考官方文档上所写:fourcc – 4-character code of codec used to compress the frames.更多的格式缩写


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值