这是《opencv2.4.9tutorial.pdf》的highgui的三个例子。通过简短的介绍来实现不同函数的理解,省去一些不需要说的东西。
一、增加滑动条
这是opencv中为数不多的可以用来交互的东西,其实因为opencv的定位不是界面性编程,所以也没打算提供多好的交互性。可以在“highgui.h”文件中查找提供了的GUI用法,滑动条主要是用来处理鼠标事件的。对于滚动条来说,需要先创建个回调函数用来用户自定义当激发滑动条事件的时候该做什么样的操作。
int createTrackbar(const string& trackbarname, const string& winname, int* value, int count, TrackbarCallback onChange=0, void* userdata=0);
这就是创建滑动条的函数原型,其中的函数列表:滑动条的名字、滑动条所依附的窗口的名字、滑动条传递的参数、滑动条的最大值(最小值不得小于0)、回调函数的函数指针、用户的数据指针。
值得注意的是这里的最大值可以用户指定,但是最小值默认总是0;回调函数的类型只能是void Foo(int,void*);其中回调函数第一个参数就是滑动条当前的值,第二个参数就是所谓的用户数据指针。void*userdata 所表示的指针指向的值可以用来在不使用全局变量的时候进行回调函数的参数传递,例如传递一个包含着所需要数据的结构体指针。
一般的流程就是:
1、在main函数外进行回调函数的声明 然后在后面定义或者直接定义:
/// 全局变量的声明与初始化 const int alpha_slider_max = 100; int alpha_slider;//滚动条 double alpha; double beta; /// 声明存储图像的变量 Mat src1; Mat src2; Mat dst;省事的做法:在函数外进行声明回调函数所需要用到的参数。
void on_trackbar( int, void* )//函数名字自己定 { /*your operation*/ }2、然后定义回调函数,其中的操作就是当鼠标点击滑动条,也就是触发事件之后就会调用这个函数,那么里面的操作就是我们需要执行的。
int main( int argc, char** argv ) { //other operation alpha_slider = 0;//先将滑动条上的值初始化为0 /// 创建窗体 namedWindow("winName", 1); /// 在创建的窗体中创建一个滑动条控件 string str = "myTrackbar"; createTrackbar( TrackbarName,str, &alpha_slider, alpha_slider_max, on_trackbar ); /// 结果在回调函数中显示 on_trackbar(alpha_slider,0);//这里需要调用一次,算是在窗口创建之后的第一次刷新。可以试着把这个函数注销也行,(MFC中也有这种情况,某些时候需要调用一次来初始化的刷新,不然会觉得怪异。) /// 按任意键退出 waitKey(0);//为了与用户进行交互 return 0; }可以创建多个滑动条,但是却只使用一个回调函数,每次处理不同的值(在创建的时候就是指定传递的值)。
所以其实滑动条的建立和使用其实相当简单。
对应的有getTrackbarPos()和setTrackbarPos()两个对滑动条的滑块位置进行操作的函数:
int getTrackbarPos(const string& trackbarname, const string& winname);
这就是函数的原型,参数列表:滑动条的名字、滑动条所依附的窗口的名字。返回的是滑动条滑块当前的位置。
void setTrackbarPos(const string& trackbarname, const string& winname, int pos);
这就是设置滑动条位置函数的原型,参数列表:滑动条名字、滑动条所依附的窗口的名字,设置滑动条滑块的值。
二、视频的输入
针对opencv来说,视频的输入可以分为视频文件和摄像头实时获取视频两种方式。
总的来说,视频捕获需要的所有函数都集成在 VideoCapture C++ 类里面。虽然它底层依赖另一个FFmpeg开源库,但是它已经被集成在OpenCV里所以你不需要额外地关注它的具体实现方法。
1、输入视频
VideoCapture capVideoFile("your video name")//采用字符串打开视频文件
VideoCapture capVideoCam(0)//采用数字打开摄像头获取视频,0表示第1个摄像头capVideoFile.open( "your video name")//采用字符串打开视频文件
capVideoCam.open(0)//采用数字打开视频文件
CV_Assert(capVideoFile.isOpened());//检测是否打开
CV_Assert(capVideoCam.isOpened());//检测是否打开
可以采用构造函数的方式或者使用open()来打开所需要的视频输入。
2、释放视频
当析构函数调用时,会自动关闭视频。如果你希望提前关闭的话,你可以调用 release 函数.
capVideoCam.release();//释放视频
capVideoFile.release();//释放视频
3、视频的图像帧获取
Mat frame;
capVideoFile >> frame;//或者capVideoCam >> frame;
capVideoFile.read(frame);//或者capVideoCam.read(frame);
可以直接用重载操作符>>或者使用read函数来进行不断的获取,这里每操作一次,capVideoFile中内部指向的视频图像帧就会自动往后移动,无需人为的像迭代器那样自增。
读取视频帧的时候也会自动进行解码操作。你可以通过调用 grab 和 retrieve 函数来显示地进行这两项操作。
4、视频的操作
视频通常拥有很多除了视频帧图像以外的信息,像是帧数之类,有些时候数据较短,有些时候用4个字节的字符串来表示。所以 get 函数返回一个double(8个字节)类型的数据来表示这些属性。然后你可以使用位操作符来操作这个返回值从而得到想要的整型数据等。这个函数有一个参数,代表着试图查询的属性ID。
int VideoCapture::get(int propId);用来获取视频的其他信息。如果所获取的属性不被VideoCapture类支持的话,返回值为0.
对于参数proId可以为以下参数值:
– CV_CAP_PROP_POS_MSEC //视频文件在以微秒为单位的当前的位置或者视频抓取的时间戳。
– CV_CAP_PROP_POS_FRAMES //解码基于0索引的帧,然后自动抓取下一帧.
– CV_CAP_PROP_POS_AVI_RATIO //相对于视频文件的位置: 0 表示视频的开始, 1 表示视频的结尾.
– CV_CAP_PROP_FRAME_WIDTH //视频流中帧的宽度.
– CV_CAP_PROP_FRAME_HEIGHT //视频流中帧的高度.
– CV_CAP_PROP_FPS //帧速率.
– CV_CAP_PROP_FOURCC //4-character code of codec.
– CV_CAP_PROP_FRAME_COUNT //视频文件中帧的总数.
– CV_CAP_PROP_FORMAT //通过 retrieve()来返回Mat对象的格式 .
– CV_CAP_PROP_MODE //Backend-specific 的值指示当前的拍摄模式
– CV_CAP_PROP_BRIGHTNESS //图像的亮度 (only for cameras).
– CV_CAP_PROP_CONTRAST //图像的对比度 (only for cameras).
– CV_CAP_PROP_SATURATION //图像的饱和度 (only for cameras).
– CV_CAP_PROP_HUE //图像的色调 (only for cameras).
– CV_CAP_PROP_GAIN //图像的增益 (only for cameras).
– CV_CAP_PROP_EXPOSURE //图像的曝光度(only for cameras).
– CV_CAP_PROP_CONVERT_RGB //布尔标识来表示是否需要将图像转换成RGB
– CV_CAP_PROP_WHITE_BALANCE //Currently not supported,待开发
– CV_CAP_PROP_RECTIFICATION //立体相机的整流标志 (note: only supported by DC1394 v 2.x backend currently
例如:
Size refS = Size((int) capVideoFile.get(CV_CAP_PROP_FRAME_WIDTH), (int) capVideoFile.get(CV_CAP_PROP_FRAME_HEIGHT));//获取视频的高和宽
当你需要设置这些值的时候你可以调用 set 函数。函数的第一个参数是需要设置的属性ID,第二个参数是需要设定的值,如果返回true的话就表示成功设定,否则就是false。
bool VideoCapture::set(int propId, double value);
三、创建视频
首先,你需要知道一个视频文件是什么样子的。每一个视频文件本质上都是一个容器,文件的扩展名只是表示容器格式(例如 avi , mov ,或者 mkv)而不是视频和音频的压缩格式。容器里可能会有很多元素,例如视频流,音频流和一些字幕流等等。这些流的储存方式是由每一个流对应的编解码器(codec)决定的。通常来说,音频流很可能使用 mp3 或 aac 格式来储存。而视频格式就更多些,通常是 XVID , DIVX , H264 或 LAGS (Lagarith Lossless Codec)等等。
然而OpenCV只是个计算机视觉库而不是一个视频处理编码库。所以开发者们试图将这个部分尽可能地精简,结果就是OpenCV能够处理的视频只剩下 avi 扩展名的了。另外一个限制就是你不能创建超过2GB的单个视频,还有就是每个文件里只能支持一个视频流,不能将音频流和字幕流等其他数据放在里面。尽管如此,任何系统支持的编解码器在这里应该都能工作。如果这些视频处理能力不够你使用的话,我想你应该去找一些专门处理视频的库例如 FFMpeg 或者更多的编解码器例如 HuffYUV , CorePNG 和 LCL 。你可以先用OpenCV创建一个原始的视频流然后通过其他编解码器转换成其他格式并用 VirtualDub 和 AviSynth 这样的软件去创建各种格式的视频文件。
要创建一个视频文件,你需要创建一个 VideoWriter 类的对象。可以通过和输入视频部分一样的构造函数或者使用 open 函数来打开,不过这里就是生成视频文件,所以参数都是字符串形式。
1、创建视频文件的名字
输出的文件名中包含了容器的类型,当然在现在仅仅支持 avi 格式:
string source = "input.mp4" ; // 原视频文件名
string::size_type pAt = source.find_last_of('.'); // 找到点的位置,并返回之前的char类型的个数,这里为5 string NAME = source.substr(0, pAt) + ".avi"; // 创建视频文件名 substr(a,b)是提取字符串中【a,b)的子字符串上面为提取现有视频文件的名字,然后对应的生成需要输出的视频文件的名字,有助于一一对应。
2、设置编码器
然后决定使用的编解码器,现在所有的视频编解码器都使用最多四个字节的名称来标识,例如 XVID, DIVX, 和 H264 等。这个被称作FourCC(four character code)。你可以通过 get 函数来向视频询问这个编码, get 函数会返回一个double数,仅仅是因为double包含了64位数据而已,由于FourCC编码只占据了其中低位的4个字节,所以可以直接通过强制转换成int型来扔掉高位的四个字节。
VideoCapture inputVideo(source); // 打开视频输入 int ex = static_cast<int>(inputVideo.get(CV_CAP_PROP_FOURCC)); // 得到编码器的int表达式上面就是获取存在的视频文件,然后提取这个视频文件的编码方式
char EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};
上面就是将编码的4个字节进行从低到高进行逐个提取。
或者使用联合体来得到这4个字节。
union { int v; char c[5];} uEx ; uEx.v = ex; // 通过联合体来分解字符串 uEx.c[4]='\0'; 反过来,当你需要修改视频的格式时,你都需要修改FourCC码,而更改ForCC的时候都要做逆转换来指定新类型。如果你已经知道这个FourCC编码的具体字符的话,可以直接使用 *CV_FOURCC* 宏来构建这个int数:联合体就是只存储其中的一个类型的数据,而当读取的时候按照所指定的类型进行输出,所以这里先使用对器中的int类型进行赋值,但是却对char类型最后进行修改,如果再次读取的话直接使用uEx.c[1~4]就行了。不过记得char类型后面加个‘\0’;
如果传入的参数是一个负数的话,就会在执行时弹出一个对话框,在里面包括了所有已安装的编码器类型,你可以在里面选择一个并使用之。
3.创建视频文件
VideoWriter outputVideo;
outputVideo.open(NAME , ex, inputVideo.get(CV_CAP_PROP_FPS),refS, true);参数列表:需要创建文件的文件名极其后缀“.avi”、编码器类型、帧率、视频的尺寸、彩色(true)或是黑白(false)视频。
ex=-1的时候就会跳出个对话框让用户选择编码器的。
接着使用isOpened()函数测试是否所需要创建的视频文件已经打开,如果返回true,那么就可以接下来的对这个文件塞入图像帧了。就可以用 write 函数向这个对象按照序列发送一些图像帧了,另外使用重载操作符 << 也可以完成这些操作。最后,视频会在 VideoWriter 对象析构时自动关闭。
outputVideo.write(frame);
或者outputVideo< 结束的时候VideoWriter类会自动析构。ps:顺带把那边的图像帧的通道分离和合并的代码贴着;
Mat src,res; vector<Mat> spl;split(src, spl); // 分离三个通道 参数列表:原图像,目标数组 for( int i =0; i < 3; ++i) if (i != channel) spl[i] = Mat::zeros(S, spl[0].type());/创建相同大小的黑色图像。通过将所需要的通道使用spl[]的形式访问 merge(spl, res); //重新合并参数列表:所需要合并的数组,目标图像