为程序界面添加滑动条

  • 在以前的教程中 (例如 linear blending 和 brightness and contrast adjustments)你有可能注意到需要 input 一些数值到我们的程序中, 例如  和  。我们会在代码中输入这些数据来配合程序的运行。

  • 好, 下面来介绍所要用到的一个 GUI 中的工具. OpenCV 提供的 GUI 库在(highgui.h)里. 这是一个 滑动条 的样子

  • 在这里教程指导我们修改以前的方案,以便从滑动条中获得那些需要输入数据.

目标

在这节教程中你将学习:

  • 使用OpenCV函数 createTrackbar 在窗口中添加一个滑动条:

代码

首先来修改一下教程中 使用OpenCV对两幅图像求和(求混合(blending)) 这个例子. 用滑动条来动态输入  这个值.

#include <cv.h>
#include <highgui.h>using namespace cv;/// 全局变量的声明与初始化
const int alpha_slider_max = 100;
int alpha_slider;
double alpha;
double beta;/// 声明存储图像的变量
Mat src1;
Mat src2;
Mat dst;/**
 * @function on_trackbar
 * @定义响应滑动条的回调函数
 */
void on_trackbar( int, void* )
{alpha = (double) alpha_slider/alpha_slider_max ;beta = ( 1.0 - alpha );addWeighted( src1, alpha, src2, beta, 0.0, dst);imshow( "Linear Blend", dst );
}int main( int argc, char** argv )
{/// 加载图像 (两图像的大小与类型要相同)src1 = imread("../../images/LinuxLogo.jpg");src2 = imread("../../images/WindowsLogo.jpg");if( !src1.data ) { printf("Error loading src1 \n"); return -1; }if( !src2.data ) { printf("Error loading src2 \n"); return -1; }/// 初始化为零alpha_slider = 0;/// 创建窗体namedWindow("Linear Blend", 1);/// 在创建的窗体中创建一个滑动条控件char TrackbarName[50];sprintf( TrackbarName, "Alpha x %d", alpha_slider_max );createTrackbar( TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar );/// 结果在回调函数中显示on_trackbar( alpha_slider, 0 );/// 按任意键退出waitKey(0);return 0;
}

程序说明

我们只分析关于滑动条的那段代码:

#.首先, 我们加载了两个图像, 目的是把它们混合显示.

src1 = imread("../../images/LinuxLogo.jpg");
src2 = imread("../../images/WindowsLogo.jpg");

#.在创建滑动条之前, 我们要先创建一个窗体,以便把创建的滑动条放置在上面:

namedWindow("Linear Blend", 1);

  1. 现在可以创建滑动条了:

    createTrackbar( TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar );
    

    记住下面的参数介绍:

    • 在标签中显示的文字(提示滑动条的用途) TrackbarName
    • 创建的滑动条要放置窗体的名字 “Linear Blend”
    • 滑动条的取值范围从  到 alpha_slider_max (最小值只能为 zero).
    • 滑动后的值存放在 alpha_slider 变量里
    • 每当滑动条的值改变, 就会调用 on_trackbar 回调函数
  2. 最后, 我们还要定义这个回调函数 on_trackbar 来实现我们想要的结果

    void on_trackbar( int, void* )
    {alpha = (double) alpha_slider/alpha_slider_max ;beta = ( 1.0 - alpha );addWeighted( src1, alpha, src2, beta, 0.0, dst);imshow( "Linear Blend", dst );
    }
    

    注意回调函数中的整型与双精度型的转换:

    • 从滑动条中获取的整型值 alpha_slider (integer) 要转换为双精度类型 alpha.
    • alpha_slider 的值会在滑动条滑动后被修改.
    • 我们所定义的 src1src2distalphaalpha_slider 和 beta 都是全局变量, 因此也可以在回调函数中使用.

结果

  • 下图是程序的运行结果:

  • 也可以使用其它方式验证, 你可以用 改变图像的对比度和亮度 中的例程实现两个滑动条. 一个控制  另一个控制 . 可能的输出会是下面的样子:

OpenCV的视频输入和相似度测量

目标

现在找一个能拍摄视频的设备真是太容易了。结果大家都用视频来代替以前的序列图像。视频可能由两种形式得到,一个是像网络摄像头那样实时视频流,或者由其他设备产生的压缩编码后的视频文件。幸运的是,OpenCV可以使用相同的C++类、用同一种方式处理这些视频信息。在接下来的教程里你将学习如何使用摄像头或者视频文件。

  • 如何打开和读取视频流
  • 两种检查相似度的方法:PSNR和SSIM

源代码

就和其他的OpenCV小例子一样,在这里我也创建了一个小程序来读取两个视频文件然后比较它们的相似度。你可以用这个功能来验证视频压缩后的损失程度,这里有一个 参考视频(原始视频) 和 压缩后的视频. 你可以在OpenCV的示例文件夹samples/cpp/tutorial_code/HighGUI/video-input-psnr-ssim/ 下找到源码和示例视频。

  123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191

#include <iostream> // for standard I/O
#include <string>   // for strings
#include <iomanip>  // for controlling float print precision
#include <sstream>  // string to number conversion#include <opencv2/imgproc/imgproc.hpp>  // Gaussian Blur
#include <opencv2/core/core.hpp>        // Basic OpenCV structures (cv::Mat, Scalar)
#include <opencv2/highgui/highgui.hpp>  // OpenCV window I/Ousing namespace std;
using namespace cv;double getPSNR ( const Mat& I1, const Mat& I2);
Scalar getMSSIM( const Mat& I1, const Mat& I2);
int main(int argc, char *argv[], char *window_name)
{if (argc != 5){cout << "Not enough parameters" << endl;return -1;}stringstream conv;const string sourceReference = argv[1],sourceCompareWith = argv[2];int psnrTriggerValue, delay;conv << argv[3] << endl << argv[4];       // put in the stringsconv >> psnrTriggerValue >> delay;// take out the numberschar c;int frameNum = -1;          // Frame counterVideoCapture captRefrnc(sourceReference),captUndTst(sourceCompareWith);if ( !captRefrnc.isOpened()){cout  << "Could not open reference " << sourceReference << endl;return -1;}if( !captUndTst.isOpened()){cout  << "Could not open case test " << sourceCompareWith << endl;return -1;}Size refS = Size((int) captRefrnc.get(CV_CAP_PROP_FRAME_WIDTH),(int) captRefrnc.get(CV_CAP_PROP_FRAME_HEIGHT)),uTSi = Size((int) captUndTst.get(CV_CAP_PROP_FRAME_WIDTH),(int) captUndTst.get(CV_CAP_PROP_FRAME_HEIGHT));if (refS != uTSi){cout << "Inputs have different size!!! Closing." << endl;return -1;}const char* WIN_UT = "Under Test";const char* WIN_RF = "Reference";// WindowsnamedWindow(WIN_RF, CV_WINDOW_AUTOSIZE );namedWindow(WIN_UT, CV_WINDOW_AUTOSIZE );cvMoveWindow(WIN_RF, 400       ,            0);      //750,  2 (bernat =0)cvMoveWindow(WIN_UT, refS.width,            0);      //1500, 2cout << "Reference frame resolution: Width=" << refS.width << "  Height=" << refS.height<< " of nr#: " << captRefrnc.get(CV_CAP_PROP_FRAME_COUNT) << endl;cout << "PSNR trigger value " <<setiosflags(ios::fixed) << setprecision(3) << psnrTriggerValue << endl;Mat frameReference, frameUnderTest;double psnrV;Scalar mssimV;while( true) //Show the image captured in the window and repeat{captRefrnc >> frameReference;captUndTst >> frameUnderTest;if( frameReference.empty()  || frameUnderTest.empty()){cout << " < < <  Game over!  > > > ";break;}++frameNum;cout <<"Frame:" << frameNum <<"# ";/ PSNR psnrV = getPSNR(frameReference,frameUnderTest);                 //get PSNRcout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB"; MSSIM /if (psnrV < psnrTriggerValue && psnrV){mssimV = getMSSIM(frameReference,frameUnderTest);cout << " MSSIM: "<< " R " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[2] * 100 << "%"<< " G " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[1] * 100 << "%"<< " B " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[0] * 100 << "%";}cout << endl;// Show Image /imshow( WIN_RF, frameReference);imshow( WIN_UT, frameUnderTest);c = cvWaitKey(delay);if (c == 27) break;}return 0;
}double getPSNR(const Mat& I1, const Mat& I2)
{Mat s1;absdiff(I1, I2, s1);       // |I1 - I2|s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bitss1 = s1.mul(s1);           // |I1 - I2|^2Scalar s = sum(s1);         // sum elements per channeldouble sse = s.val[0] + s.val[1] + s.val[2]; // sum channelsif( sse <= 1e-10) // for small values return zeroreturn 0;else{double  mse =sse /(double)(I1.channels() * I1.total());double psnr = 10.0*log10((255*255)/mse);return psnr;}
}Scalar getMSSIM( const Mat& i1, const Mat& i2)
{const double C1 = 6.5025, C2 = 58.5225;/***************************** INITS **********************************/int d     = CV_32F;Mat I1, I2;i1.convertTo(I1, d);           // cannot calculate on one byte large valuesi2.convertTo(I2, d);Mat I2_2   = I2.mul(I2);        // I2^2Mat I1_2   = I1.mul(I1);        // I1^2Mat I1_I2  = I1.mul(I2);        // I1 * I2/*************************** END INITS **********************************/Mat mu1, mu2;   // PRELIMINARY COMPUTINGGaussianBlur(I1, mu1, Size(11, 11), 1.5);GaussianBlur(I2, mu2, Size(11, 11), 1.5);Mat mu1_2   =   mu1.mul(mu1);Mat mu2_2   =   mu2.mul(mu2);Mat mu1_mu2 =   mu1.mul(mu2);Mat sigma1_2, sigma2_2, sigma12;GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);sigma1_2 -= mu1_2;GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);sigma2_2 -= mu2_2;GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);sigma12 -= mu1_mu2;/ FORMULA Mat t1, t2, t3;t1 = 2 * mu1_mu2 + C1;t2 = 2 * sigma12 + C2;t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))t1 = mu1_2 + mu2_2 + C1;t2 = sigma1_2 + sigma2_2 + C2;t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))Mat ssim_map;divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;Scalar mssim = mean( ssim_map ); // mssim = average of ssim mapreturn mssim;
}

如何读取一个视频流(摄像头或者视频文件)?

总的来说,视频捕获需要的所有函数都集成在 VideoCapture C++ 类里面。虽然它底层依赖另一个FFmpeg开源库,但是它已经被集成在OpenCV里所以你不需要额外地关注它的具体实现方法。你只需要知道一个视频由一系列图像构成,我们用一个专业点儿的词汇来称呼这些构成视频的图像:“帧”(frame)。此外在视频文件里还有个参数叫做“帧率”(frame rate)的,用来表示两帧之间的间隔时间,帧率的单位是(帧/秒)。这个参数只和视频的播放速度有关,对于单独的一帧图像来说没有任何用途。

你需要先定义一个 VideoCapture 类的对象来打开和读取视频流。具体可以通过 constructor 或者通过 open 函数来完成。如果使用整型数当参数的话,就可以将这个对象绑定到一个摄像机,将系统指派的ID号当作参数传入即可。例如你可以传入0来打开第一个摄像机,传入1打开第二个摄像机,以此类推。如果使用字符串当参数,就会打开一个由这个字符串(文件名)指定的视频文件。例如在上面的例子里传入如下参数:

video/Megamind.avi video/Megamind_bug.avi  35 10

前两个参数传入了两个文件名,分别代表原始参考视频和测试视频。这里使用了相对地址,这也代表着系统会从软件的工作目录下的video子目录里寻找文件。然后程序将针对这些参数开始进行相似性检查

const string sourceReference = argv[1],sourceCompareWith = argv[2];VideoCapture captRefrnc(sourceReference);
// 或者
VideoCapture captUndTst;
captUndTst.open(sourceCompareWith);

你可以用 isOpened 函数来检查视频是否成功打开与否:

if ( !captRefrnc.isOpened()){cout  << "Could not open reference " << sourceReference << endl;return -1;}

当析构函数调用时,会自动关闭视频。如果你希望提前关闭的话,你可以调用 release 函数. 视频的每一帧都是一幅普通的图像。因为我们仅仅需要从 VideoCapture 对象里释放出每一帧图像并保存成 Mat 格式。因为视频流是连续的,所以你需要在每次调用read 函数后及时保存图像或者直接使用重载的>>操作符。

Mat frameReference, frameUnderTest;
captRefrnc >> frameReference;
captUndTst.open(frameUnderTest);

如果视频帧无法捕获(例如当视频关闭或者完结的时候),上面的操作就会返回一个空的 Mat 对象。我们可以用下面的代码检查是否返回了空的图像:

if( frameReference.empty()  || frameUnderTest.empty())
{// 退出程序
}

读取视频帧的时候也会自动进行解码操作。你可以通过调用 grab 和 retrieve 函数来显示地进行这两项操作。

视频通常拥有很多除了视频帧图像以外的信息,像是帧数之类,有些时候数据较短,有些时候用4个字节的字符串来表示。所以 get函数返回一个double(8个字节)类型的数据来表示这些属性。然后你可以使用位操作符来操作这个返回值从而得到想要的整型数据等。这个函数有一个参数,代表着试图查询的属性ID。在下面的例子里我们会先获得食品的尺寸和帧数。

Size refS = Size((int) captRefrnc.get(CV_CAP_PROP_FRAME_WIDTH),(int) captRefrnc.get(CV_CAP_PROP_FRAME_HEIGHT)),cout << "参考帧的分辨率: 宽度=" << refS.width << "  高度=" << refS.height<< " of nr#: " <<<< endl;

当你需要设置这些值的时候你可以调用 set 函数。函数的第一个参数是需要设置的属性ID,第二个参数是需要设定的值,如果返回true的话就表示成功设定,否则就是false。接下来的这个例子很好地展示了如何设置视频的时间位置或者帧数:

captRefrnc.set(CV_CAP_PROP_POS_MSEC, 1.2);  // 跳转到视频1.2秒的位置
captRefrnc.set(CV_CAP_PROP_POS_FRAMES, 10); // 跳转到视频的第10帧
// 然后重新调用read来得到你刚刚设置的那一帧

你可以参考 get 和 set 函数的文档来得到更多信息。

图像比较 - PSNR and SSIM

当我们想检查压缩视频带来的细微差异的时候,就需要构建一个能够逐帧比较差视频差异的系统。最常用的比较算法是PSNR( Peak signal-to-noise ratio)。这是个使用“局部均值误差”来判断差异的最简单的方法,假设有这两幅图像:I1和I2,它们的行列数分别是i,j,有c个通道。

PSNR公式如下:

每个像素的每个通道的值占用一个字节,值域[0,255]。这里每个像素会有  个有效的最大值 注意当两幅图像的相同的话,MSE的值会变成0。这样会导致PSNR的公式会除以0而变得没有意义。所以我们需要单独的处理这样的特殊情况。此外由于像素的动态范围很广,在处理时会使用对数变换来缩小范围。这些变换的C++代码如下:

double getPSNR(const Mat& I1, const Mat& I2)
{Mat s1;absdiff(I1, I2, s1);       // |I1 - I2|s1.convertTo(s1, CV_32F);  // 不能在8位矩阵上做平方运算s1 = s1.mul(s1);           // |I1 - I2|^2Scalar s = sum(s1);         // 叠加每个通道的元素double sse = s.val[0] + s.val[1] + s.val[2]; // 叠加所有通道if( sse <= 1e-10) // 如果值太小就直接等于0return 0;else{double  mse =sse /(double)(I1.channels() * I1.total());double psnr = 10.0*log10((255*255)/mse);return psnr;}
}

在考察压缩后的视频时,这个值大约在30到50之间,数字越大则表明压缩质量越好。如果图像差异很明显,就可能会得到15甚至更低的值。PSNR算法简单,检查的速度也很快。但是其呈现的差异值有时候和人的主观感受不成比例。所以有另外一种称作 结构相似性的算法做出了这方面的改进。

建议你阅读一些关于SSIM算法的文献来更好的理解算法,然而及时你直接看下面的源代码,应该也能建立一个不错的映像。

See also

请参考下面深度解析SSIM的文章:”Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, “Image quality assessment: From error visibility to structural similarity,” IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004.”

Scalar getMSSIM( const Mat& i1, const Mat& i2)
{const double C1 = 6.5025, C2 = 58.5225;/***************************** INITS **********************************/int d     = CV_32F;Mat I1, I2;i1.convertTo(I1, d);           // 不能在单字节像素上进行计算,范围不够。i2.convertTo(I2, d);Mat I2_2   = I2.mul(I2);        // I2^2Mat I1_2   = I1.mul(I1);        // I1^2Mat I1_I2  = I1.mul(I2);        // I1 * I2/***********************初步计算 ******************************/Mat mu1, mu2;   //GaussianBlur(I1, mu1, Size(11, 11), 1.5);GaussianBlur(I2, mu2, Size(11, 11), 1.5);Mat mu1_2   =   mu1.mul(mu1);Mat mu2_2   =   mu2.mul(mu2);Mat mu1_mu2 =   mu1.mul(mu2);Mat sigma1_2, sigma2_2, sigma12;GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);sigma1_2 -= mu1_2;GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);sigma2_2 -= mu2_2;GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);sigma12 -= mu1_mu2;/ 公式 Mat t1, t2, t3;t1 = 2 * mu1_mu2 + C1;t2 = 2 * sigma12 + C2;t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))t1 = mu1_2 + mu2_2 + C1;t2 = sigma1_2 + sigma2_2 + C2;t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))Mat ssim_map;divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;Scalar mssim = mean( ssim_map ); // mssim = ssim_map的平均值return mssim;
}

这个操作会针对图像的每个通道返回一个相似度,取值范围应该在0到1之间,取值为1时代表完全符合。然而尽管SSIM能产生更优秀的数据,但是由于高斯模糊很花时间,所以在一个实时系统(每秒24帧)中,人们还是更多地采用PSNR算法。

正是这个原因,最开始的源码里,我们用PSNR算法去计算每一帧图像,而仅当PSNR算法计算出的结果低于输入值的时候,用SSIM算法去验证。为了展示数据,我们在例程里用两个窗口显示了原图像和测试图像并且在控制台上输出了PSNR和SSIM数据。就像下面显示的那样:

如果你会翻墙,就可以在天朝河蟹掉的 YouTube 上观看操作视频.

用OpenCV创建视频

目标

你可能已经不满足于读取视频,还想要将你产生的一系列结果保存到一个新建的视频文件中。使用OpenCV中的 VideoWriter 类就可以简单的完成创建视频的工作。在接下来的教程中,我们将告诉你:

  • 如何用OpenCV创建一个视频文件
  • 用OpenCV能创建什么样的视频文件
  • 如何释放视频文件当中的某个颜色通道

为了使例子简单,我就仅仅释放原始视频RGB通道中的一个,并把它放入新视频文件中。你可以使用命令行参数来控制程序的一些行为:

  • 第一个参数指向你需要操作的视频文件。
  • 第二个参数可以是如下的几个字母之一:R G B。用来指定你需要释放哪一个通道。
  • 最后一个参数是Y(Yes)或者N(No). 如果你选择N, 就直接使用视频输入格式来创建输出文件,否则就会弹出一个对话框来让你选择编码器。

举例来说,可以使用下面这样子的命令行:

video-write.exe video/Megamind.avi R Y

源代码

你可以在 samples/cpp/tutorial_code/highgui/video-write/ 文件夹中找到源代码和视频文件。或者从 download it from here这里下载源代码。

 123456789
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

#include <iostream> // for standard I/O
#include <string>   // for strings#include <opencv2/core/core.hpp>        // Basic OpenCV structures (cv::Mat)
#include <opencv2/highgui/highgui.hpp>  // Video writeusing namespace std;
using namespace cv;
int main(int argc, char *argv[], char *window_name)
{if (argc != 4){cout << "Not enough parameters" << endl;return -1;}const string source      = argv[1];            // the source file nameconst bool askOutputType = argv[3][0] =='Y';  // If false it will use the inputs codec typeVideoCapture inputVideo(source);        // Open inputif ( !inputVideo.isOpened()){cout  << "Could not open the input video." << source << endl;return -1;}string::size_type pAt = source.find_last_of('.');   // Find extension pointconst string NAME = source.substr(0, pAt) + argv[2][0] + ".avi";   // Form the new name with containerint ex = static_cast<int>(inputVideo.get(CV_CAP_PROP_FOURCC));     // Get Codec Type- Int form// Transform from int to char via Bitwise operatorschar EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};Size S = Size((int) inputVideo.get(CV_CAP_PROP_FRAME_WIDTH),    //Acquire input size(int) inputVideo.get(CV_CAP_PROP_FRAME_HEIGHT));    VideoWriter outputVideo;                                        // Open the outputif (askOutputType)outputVideo.open(NAME  , ex=-1, inputVideo.get(CV_CAP_PROP_FPS),S, true);  elseoutputVideo.open(NAME , ex, inputVideo.get(CV_CAP_PROP_FPS),S, true);if (!outputVideo.isOpened()){cout  << "Could not open the output video for write: " << source << endl;return -1;}union { int v; char c[5];} uEx ;uEx.v = ex;                              // From Int to char via unionuEx.c[4]='\0';cout << "Input frame resolution: Width=" << S.width << "  Height=" << S.height<< " of nr#: " << inputVideo.get(CV_CAP_PROP_FRAME_COUNT) << endl;cout << "Input codec type: " << EXT << endl;int channel = 2;    // Select the channel to saveswitch(argv[2][0]){case 'R' : {channel = 2; break;}case 'G' : {channel = 1; break;}case 'B' : {channel = 0; break;}}Mat src,res;vector<Mat> spl; while( true) //Show the image captured in the window and repeat{inputVideo >> src;              // readif( src.empty()) break;         // check if at endsplit(src, spl);                 // process - extract only the correct channelfor( int i =0; i < 3; ++i)       if (i != channel)spl[i] = Mat::zeros(S, spl[0].type());merge(spl, res);//outputVideo.write(res); //save oroutputVideo << res;}cout << "Finished writing" << endl;return 0;
}

视频文件的结构

首先,你需要知道一个视频文件是什么样子的。每一个视频文件本质上都是一个容器,文件的扩展名只是表示容器格式(例如 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 类

在看下面的内容之前,你需要先阅读教程: OpenCV的视频输入和相似度测量 并且确保你知道如何读取视频文件。

要创建一个视频文件,你需要创建一个 VideoWriter 类的对象。可以通过构造函数里的参数或者在其他合适时机使用 open 函数来打开,两者的参数都是一样的:

  1. 输出的文件名中包含了容器的类型,当然在现在仅仅支持 avi 格式。在这个例子中我们会使用输入文件名+通道名+avi来创建输出文件名。

    const string source      = argv[1];            // 原视频文件名
    string::size_type pAt = source.find_last_of('.');   // 找到扩展名的位置
    const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi";   // 创建新的视频文件名
    

  2. 然后决定使用的编解码器,现在所有的视频编解码器都使用最多四个字节的名称来标识,例如 XVIDDIVX, 和 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表达式
    

    OpenCV内部使用这个int数来当作第二个参数,这里会使用两种方法来将这个整型数转换为字符串:位操作符和联合体。前者可以用&操作符并进行移位操作,以便从int里面释放出字符:

    char EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};
    

也可以使用 联合体 来做到:

union { int v; char c[5];} uEx ;
uEx.v = ex;                              // 通过联合体来分解字符串
uEx.c[4]='\0';反过来,当你需要修改视频的格式时,你都需要修改FourCC码,而更改ForCC的时候都要做逆转换来指定新类型。如果你已经知道这个FourCC编码的具体字符的话,可以直接使用 *CV_FOURCC* 宏来构建这个int数:

如果传入的参数是一个负数的话,就会在执行时弹出一个对话框,在里面包括了所有已安装的编码器类型,你可以在里面选择一个并使用之。

  1. 输出视频的帧率,也就是每秒需要绘制的图像数,在这里我让输出视频的帧率与输入视频相同,输入视频的帧率可以由 get 函数来获得。
  2. 输出视频的尺寸,在这里我同样保持和输入视频一样的大小,这个大小同样也可以由 get 函数来获得。
  3. 最后一个参数是一个可选参数。默认下它是true,来表示输出的视频是彩色的(所以你需要传给它三通道的图像),如果想创建一个灰度视频就传入false。

具体实现请看下面的例子:

VideoWriter outputVideo;
Size S = Size((int) inputVideo.get(CV_CAP_PROP_FRAME_WIDTH),    //获取输入尺寸(int) inputVideo.get(CV_CAP_PROP_FRAME_HEIGHT));outputVideo.open(NAME , ex, inputVideo.get(CV_CAP_PROP_FPS),S, true);

然后,最好使用 isOpened() 函数来检查是不是成功打开。在成功打开时视频后,就可以用 write 函数向这个对象按照序列发送一些图像帧了,另外使用重载操作符 << 也可以完成这些操作。最后,视频会在 VideoWriter 对象析构时自动关闭。

outputVideo.write(res);  //或者
outputVideo << res;

要“释放”出某个通道,又要保持视频为彩色,实际上也就意味着要把未选择的通道都设置为全0。这个操作既可以通过手工扫描整幅图像来完成,又可以通过分离通道然后再合并来做到,具体操作时先分离三通道图像为三个不同的单通道图像,然后再将选定的通道与另外两张大小和类型都相同的黑色图像合并起来。

split(src, spl);                 // 分离三个通道
for( int i =0; i < 3; ++i)if (i != channel)spl[i] = Mat::zeros(S, spl[0].type());         //创建相同大小的黑色图像
merge(spl, res);             //重新合并

上面的代码完成了这个过程,最终运行结果就像下面所显示的那样。

当然如果你会翻墙的话,又可以在“不存在”的`YouTube <https://www.youtube.com/watch?v=jpBwHxsl1_0>`_ 上观看到动态视频。

from: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/highgui/table_of_content_highgui/table_of_content_highgui.html#table-of-content-highgui

OpenCV之highgui 模块. 高层GUI和媒体I/O: 为程序界面添加滑动条 OpenCV的视频输入和相似度测量 用OpenCV创建视频相关推荐

  1. 【OpenCV 】计算物体的凸包/创建包围轮廓的矩形和圆形边界框/createTrackbar添加滑动条/

    目录 topic 1:模板匹配 topic 2:图像中寻找轮廓 topic 3:计算物体的凸包 topic 4:轮廓创建可倾斜的边界框和椭圆¶ topic 5:轮廓矩¶ topic 6:为程序界面添加 ...

  2. 【OpenCV】视频输入与相似度测量

    视频输入   与之前的博客内容不同,本节处理的是视频流而非图像集.视频流来源可以是摄像头的实时图像反馈.已拍摄存储的视频.OpenCV可以读取这两种类型的视频流并采用RSNP和SSIM检查图像的相似度 ...

  3. OpenCV之core 模块. 核心功能(2)基本绘图 随机数发生器绘制文字 离散傅立叶变换 输入输出XML和YAML文件 与 OpenCV 1 同时使用

    基本绘图 目的 本节你将学到: 如何用 Point 在图像中定义 2D 点 如何以及为何使用 Scalar 用OpenCV的函数 line 绘 直线 用OpenCV的函数 ellipse 绘 椭圆 用 ...

  4. OpenCV:没有GUI的情况下使用OpenCV无缝克隆模块(附完整代码)

    没有GUI的情况下使用OpenCV无缝克隆模块 没有GUI的情况下使用OpenCV无缝克隆模块 没有GUI的情况下使用OpenCV无缝克隆模块 #include "opencv2/photo ...

  5. OpenCV之gpu 模块. 使用GPU加速的计算机视觉:GPU上的相似度检测(PNSR 和 SSIM)

    GPU上的相似度检测(PNSR 和 SSIM) 学习目标 在 OpenCV的视频输入和相似度测量 教程中我们已经学习了检测两幅图像相似度的两种方法:PSNR和SSIM.正如我们所看到的,执行这些算法需 ...

  6. 用opencv的dnn模块做yolov5目标检测

    最近在微信公众号里看到多篇讲解yolov5在openvino部署做目标检测文章,但是没看到过用opencv的dnn模块做yolov5目标检测的.于是,我就想着编写一套用opencv的dnn模块做yol ...

  7. OpenCV无缝克隆不带GUI的模块的实例(附完整代码)

    OpenCV无缝克隆不带GUI的模块的实例 OpenCV无缝克隆不带GUI的模块的实例 OpenCV无缝克隆不带GUI的模块的实例 #include "opencv2/photo.hpp&q ...

  8. OpenCV之imgproc 模块. 图像处理(3)霍夫线变换 霍夫圆变换 Remapping 重映射 仿射变换

    霍夫线变换 目标 在这个部分您将学习到: 使用OpenCV的以下函数 HoughLines 和 HoughLinesP 来检测图像中的直线. 原理 Note 以下原理的说明来自书籍 学习OpenCV  ...

  9. 超详细!使用OpenCV深度学习模块在图像分类下的应用实践

    专注计算机视觉前沿资讯和技术干货 微信公众号:极市平台 官网:https://www.cvmart.net/ 极市导读:本文来自6月份出版的新书<OpenCV深度学习应用与性能优化实践>, ...

最新文章

  1. ASP.NET中如何防范SQL注入式攻击
  2. mysql5.7.安装记录
  3. PostgreSql与sqlserver对比杂记
  4. python关联分析引擎_PowerBI x Python 之关联分析(上)
  5. 介绍几款开源好用的产品
  6. 牛客 - 求函数(线段树+区间合并/线段树+矩阵维护)
  7. 48、多用块枚举,少用for循环
  8. STM32-GPIO篇
  9. javascript错误_您可能会犯的javascript错误
  10. python论文排版格式_论文排版(一):三分钟了解页面设置
  11. exchange 服务器设置自动答复,Exchange自动回复设置配置
  12. react中使用swiper
  13. html textarea 缩放,textarea自动撑开缩放
  14. linux 使用pip安装出现错误
  15. master主节点初始化报错 /proc/sys/net/ipv4/ip_forward contents are not set to 1
  16. 50个直击灵魂的问题_“天台上他问了自己3个问题,放弃了自杀” | 9个直击心灵的灵魂拷问...
  17. Java代码实现简单的画板
  18. 关于--七淅在学Java--SELECT LAST_INSERT_ID() 的使用和注意事项 探究
  19. Map Matching 算法归纳与对比
  20. 重新定义 NFT (音乐、四维、隐私、交互)——下篇

热门文章

  1. 你知道你的模型可以为公司赚多少钱吗
  2. 大数据风控-反欺诈之黑卡与养卡
  3. 日本那个大户又要抛了?
  4. chm 能插入公式吗_批量提取表格名称、插入空列,教你三下五除二秒速完成!...
  5. 那些年,程序员读错的英语
  6. Java Review - SimpleDateFormat线程不安全原因的源码分析及解决办法
  7. 并发编程-21J.U.C组件拓展之Future和FutureTask
  8. Oracle优化03-Latch和等待
  9. gnome mysql client_解决MySQLWorkbenchgnome-keyring-daemon错误的方法分享
  10. php和派森,安装多版本Python,一个神器足矣