第一个程序——显示图像
#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");//销毁窗口
}
注意点:
- cvNameWindow:第一个参数指定窗口标题,当该窗口与其他函数进行交互时,利用该参数引用此窗口。cvShowImage显示图像,第一个参数指定窗口参数。注意在上面的代码中,我创建了两个窗口,而显示了一个图像,运行代码会发现该图像会出现在窗口“Example2”中。
- cvReleaseImage释放IplImage*所指向的内存,cvDestroyWindow也会同时释放为该窗口所分配的内存。养成随时释放内存的好习惯。
- 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" );
}
注意点:
- cvQueryFrame返回的IplImage*使用在CvCapture*中分配好的内存,并不像cvLoadImage一样为图像新分配内存,也就不需要通过cvReleaseImage释放。只需要最后统一释放CvCapture*.内存垃圾处理在opencv中很重要,但是我们只需要释放自己显示分配的内存空间。
- 上面的程序中设置帧间隔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);
}
注意点:
- 上述函数在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();
}
注意:
- OpenCv利用CV_FOURCC()宏来指定编码格式,参考官方文档上所写:fourcc – 4-character code of codec used to compress the frames.更多的格式缩写