11.视频读取与保存
1、视频数据读取
虽然视频文件是由多张图片组成的,但是imread()函数并不能直接读取视频文件,需要由专门的视频读取函数进行视频读取,并将每一帧图像保存到Mat类矩阵中,代码清单2-27中给出了VideoCapture类在读取视频文件时的构造方式。
代码清单2-27 读取视频文件VideoCapture类构造函数 cv :: VideoCapture :: VideoCapture(); //默认构造函数 cv :: VideoCapture :: VideoCapture(const String& filename, int apiPreference =CAP_ANY )
-
filename:读取的视频文件或者图像序列名称
-
apiPreference:读取数据时设置的属性,例如编码格式、是否调用OpenNI等,详细参数及含义在表2-5给出。
该函数是构造一个能够读取与处理视频文件的视频流,在代码清单2-27中的第一行是VideoCapture类的默认构造函数,只是声明了一个能够读取视频数据的类,具体读取什么视频文件,需要在使用时通过open()函数指出,例如cap.open(“1.avi”)是VideoCapture类变量cap读取1.avi视频文件。
第二种构造函数在给出声明变量的同时也将视频数据赋值给变量。可以读取的文件种类包括视频文件(例如video.avi)、图像序列或者视频流的URL。其中读取图像序列需要将多个图像的名称统一为“前缀+数字”的形式,通过“前缀+%02d”的形式调用,例如在某个文件夹中有图片img_00.jpg、img_01.jpg、img_02.jpg……加载时文件名用img_%02d.jpg表示。函数中的读取视频设置属性标签默认的是自动搜索合适的标志,所以在平时使用中,可以将其缺省,只需要输入视频名称即可。与imread()函数一样,构造函数同样有可能读取文件失败,因此需要通过isOpened()函数进行判断,如果读取成功则返回值为true,如果读取失败,则返回值为false。
通过构造函数只是将视频文件加载到了VideoCapture类变量中,当我们需要使用视频中的图像时,还需要将图像由VideoCapture类变量里导出到Mat类变量里,用于后期数据处理,该操作可以通过“>>”运算符将图像按照视频顺序由VideoCapture类变量复制给Mat类变量。当VideoCapture类变量中所有的图像都赋值给Mat类变量后,再次赋值的时候Mat类变量会变为空矩阵,因此可以通过empty()判断VideoCapture类变量中是否所有图像都已经读取完毕。
VideoCapture类变量同时提供了可以查看视频属性的get()函数,通过输入指定的标志来获取视频属性,例如视频的像素尺寸、帧数、帧率等,常用标志和含义在表2-5中给出。
表2-5 VideoCapture类中get方法中的标志参数
标志参数 |
简记 |
作用 |
CAP_PROP_POS_MSEC |
0 |
视频文件的当前位置(以毫秒为单位) |
CAP_PROP_FRAME_WIDTH |
3 |
视频流中图像的宽度 |
CAP_PROP_FRAME_HEIGHT |
4 |
视频流中图像的高度 |
CAP_PROP_FPS |
5 |
视频流中图像的帧率(每秒帧数) |
CAP_PROP_FOURCC |
6 |
编解码器的4字符代码 |
CAP_PROP_FRAME_COUNT |
7 |
视频流中图像的帧数 |
CAP_PROP_FORMAT |
8 |
返回的Mat对象的格式 |
CAP_PROP_BRIGHTNESS |
10 |
图像的亮度(仅适用于支持的相机) |
CAP_PROP_CONTRAST |
11 |
图像对比度(仅适用于相机) |
CAP_PROP_SATURATION |
12 |
图像饱和度(仅适用于相机) |
CAP_PROP_HUE |
13 |
图像的色调(仅适用于相机) |
CAP_PROP_GAIN |
14 |
图像的增益(仅适用于支持的相机) |
为了更加熟悉VideoCapture类,在代码清单2-28中给出了读取视频,输出视频属性,并按照原帧率显示视频的程序,运行结果在图2-6给出。
代码清单2-28 VideoCapture.cpp读取视频文件 #include <opencv2\opencv.hpp> #include <iostream> using namespace std; using namespace cv; int main() { system("color F0"); //更改输出界面颜色 VideoCapture video("cup.mp4"); if (video.isOpened()) { cout << "视频中图像的宽度=" << video.get(CAP_PROP_FRAME_WIDTH) << endl; cout << "视频中图像的高度=" << video.get(CAP_PROP_FRAME_HEIGHT) << endl; cout << "视频帧率=" << video.get(CAP_PROP_FPS) << endl; cout << "视频的总帧数=" << video.get(CAP_PROP_FRAME_COUNT); } else { cout << "请确认视频文件名称是否正确" << endl; return -1; } while (1) { Mat frame; video >> frame; if (frame.empty()) { break; } imshow("video", frame); waitKey(1000 / video.get(CAP_PROP_FPS)); } waitKey(); return 0; }
2、摄像头的调用
代码清单2-29 VideoCapture类调用摄像头构造函数 cv :: VideoCapture :: VideoCapture(int index,int apiPreference = CAP_ANY)
通过与代码清单2-27中对比,调用摄像头与读取视频文件相比,只有第一个参数不同。调用摄像头时,第一个参数为要打开的摄像头设备的ID,ID的命名方式从0开始。从摄像头中读取图像数据的方式与从视频中读取图像数据的方式相同,通过“>>”符号读取当前时刻相机拍摄到的图像。并且读取视频时VideoCapture类具有的属性同样可以使用。我们将代码清单2-28中的视频文件改成摄像头ID(0),再次运行代码清单2-28的程序,运行结果如图2-7所示。
3、图像保存
代码清单2-30 imwrite()函数原型 bool cv :: imwrite(const String& filename, InputArray img, Const std::vector<int>& params = std::vector<int>() )
-
filename:保存图像的地址和文件名,包含图像格式
-
img:将要保存的Mat类矩阵变量
-
params:保存图片格式属性设置标志
该函数用于将Mat类矩阵保存成图像文件,如果成功保存,则返回true,否则返回false。可以保存的图像格式参考imread()函数能够读取的图像文件格式,通常使用该函数只能保存8位单通道图像和3通道BGR彩色图像,但是可以通过更改第三个参数保存成不同格式的图像。不同图像格式能够保存的图像位数如下:
-
16位无符号(CV_16U)图像可以保存成PNG、JPEG、TIFF格式文件;
-
32位浮点(CV_32F)图像可以保存成PFM、TIFF、OpenEXR和Radiance HDR格式文件;
-
4通道(Alpha通道)图像可以保存成PNG格式文件。
函数第三个参数在一般情况下不需要填写,保存成指定的文件格式只需要直接在第一个参数后面更改文件后缀即可,但是当需要保存的Mat类矩阵中数据比较特殊时(如16位深度数据),则需要设置第三个参数。第三个参数的设置方式如代码清单2-31中所示,常见的可选择设置标志在表2-6中给出。
代码清单2-31 imwrite()函数中第三个参数设置方式 vector <int> compression_params; compression_params.push_back(IMWRITE_PNG_COMPRESSION); compression_params.push_back(9); imwrite(filename, img, compression_params)
表2-6 imwrite()函数第三个参数可选择的标志及作用
标志参数 |
简记 |
作用 |
IMWRITE_JPEG_QUALITY |
1 |
保存成JPEG格式的文件的图像质量,分成0-100等级,默认95 |
IMWRITE_JPEG_PROGRESSIVE |
2 |
增强JPEG格式,启用为1,默认值为0(False) |
IMWRITE_JPEG_OPTIMIZE |
3 |
对JPEG格式进行优化,启用为1,默认参数为0(False) |
IMWRITE_JPEG_LUMA_QUALITY |
5 |
JPEG格式文件单独的亮度质量等级,分成0-100,默认为0 |
IMWRITE_JPEG_CHROMA_QUALITY |
6 |
JPEG格式文件单独的色度质量等级,分成0-100,默认为0 |
IMWRITE_PNG_COMPRESSION |
16 |
保存成PNG格式文件压缩级别,从0-9,只越高意味着更小尺寸和更长的压缩时间,默认值为1(最佳速度设置) |
IMWRITE_TIFF_COMPRESSION |
259 |
保存成TIFF格式文件压缩方案 |
为了更好的理解imwrite()函数的使用方式,在代码清单2-32中给出了生成带有Alpha通道的矩阵,并保存成PNG格式图像的程序。程序运行后会生成一个保存了4通道的png格式图像,为了更直观的看到图像结果,我们在图2-8中给出了Image Watch插件中看到的图像和保存成png格式的图像。
代码清单2-32 imgWriter.cpp保存图像 1. #include <opencv2\opencv.hpp> 2. #include <iostream> 3. 4. using namespace std; 5. using namespace cv; 6. 7. void AlphaMat(Mat &mat) 8. { 9. CV_Assert(mat.channels() == 4); 10. for (int i = 0; i < mat.rows; ++i) 11. { 12. for (int j = 0; j < mat.cols; ++j) 13. { 14. Vec4b& bgra = mat.at<Vec4b>(i, j); 15. bgra[0] = UCHAR_MAX; // 蓝色通道 16. bgra[1] = saturate_cast<uchar>((float(mat.cols - j)) / ((float)mat.cols) 17. * UCHAR_MAX); // 绿色通道 18. bgra[2] = saturate_cast<uchar>((float(mat.rows - i)) / ((float)mat.rows) 19. * UCHAR_MAX); // 红色通道 20. bgra[3] = saturate_cast<uchar>(0.5 * (bgra[1] + bgra[2])); // Alpha通道 21. } 22. } 23. } 24. int main(int agrc, char** agrv) 25. { 26. // Create mat with alpha channel 27. Mat mat(480, 640, CV_8UC4); 28. AlphaMat(mat); 29. vector<int> compression_params; 30. compression_params.push_back(IMWRITE_PNG_COMPRESSION); //PNG格式图像压缩标志 31. compression_params.push_back(9); //设置最高压缩质量 32. bool result = imwrite("alpha.png", mat, compression_params); 33. if (!result) 34. { 35. cout<< "保存成PNG格式图像失败" << endl; 36. return -1; 37. } 38. cout << "保存成功" << endl; 39. return 0; 40. }
4、视频的保存
代码清单2-33 读取视频文件VideoCapture类构造函数 cv :: VideoWriter :: VideoWriter(); //默认构造函数 cv :: VideoWriter :: VideoWriter(const String& filename, int fourcc, double fps, Size frameSize, bool isColor=true )
-
filename:保存视频的地址和文件名,包含视频格式
-
int:压缩帧的4字符编解码器代码,详细参数在表2-7给出。
-
fps:保存视频的帧率,即视频中每秒图像的张数。
-
framSize:视频帧的尺寸
-
isColor:保存视频是否为彩色视频
代码清单2-33中的第1行默认构造函数的使用方法与VideoCapture()相同,都是创建一个用于保存视频的数据流,后续通过open()函数设置保存文件名称、编解码器、帧数等一系列参数。第二种构造函数需要输入的第一个参数是需要保存的视频文件名称,第二个函数是编解码器的代码,可以设置的编解码器选项在表中给出,如果赋值“-1”则会自动搜索合适的编解码器,需要注意的是其在OpenCV 4.0版本和OpenCV 4.1版本中的输入方式有一些差别,具体差别在表2-7给出。第三个参数为保存视频的帧率,可以根据需求自由设置,例如实现原视频二倍速播放、原视频慢动作播放等。第四个参数是设置保存的视频文件的尺寸,这里需要注意的时,在设置时一定要与图像的尺寸相同,不然无法保存视频。最后一个参数是设置保存的视频是否是彩色的,程序中,默认的是保存为彩色视频。
该函数与VideoCapture()有很大的相似之处,都可以通过isOpened()函数判断是否成功创建一个视频流,可以通过get()查看视频流中的各种属性。在保存视频时,我们只需要将生成视频的图像一帧一帧通过“<<”操作符(或者write()函数)赋值给视频流即可,最后使用release()关闭视频流。
表2-7 视频编码格式
OpenCV 4.1版本标志 |
OpenCV 4.0版本标志 |
作用 |
VideoWriter::fourcc('D','I','V','X') |
CV_FOURCC('D','I','V','X') |
MPEG-4编码 |
VideoWriter::fourcc('P','I','M','1') |
CV_FOURCC('P','I','M','1') |
MPEG-1编码 |
VideoWriter::fourcc('M','J','P','G') |
CV_FOURCC('M','J','P','G') |
JPEG编码(运行效果一般) |
VideoWriter::fourcc('M', 'P', '4', '2') |
CV_FOURCC('M', 'P', '4', '2') |
MPEG-4.2编码 |
VideoWriter::fourcc('D', 'I', 'V', '3') |
CV_FOURCC('D', 'I', 'V', '3') |
MPEG-4.3编码 |
VideoWriter::fourcc('U', '2', '6', '3') |
CV_FOURCC('U', '2', '6', '3') |
H263编码 |
VideoWriter::fourcc('I', '2', '6', '3') |
CV_FOURCC('I', '2', '6', '3') |
H263I编码 |
VideoWriter::fourcc('F', 'L', 'V', '1') |
CV_FOURCC('F', 'L', 'V', '1') |
FLV1编码 |
为了更好的理解VideoWrite()类的使用方式,代码清单2-34中给出了利用已有视频文件数据或者直接通过摄像头生成新的视频文件的例程。读者需要重点体会VideoWrite()类和VideoCapture()类的相似之处和使用时的注意事项。
代码清单2-34 VideoWriter.cpp保存视频文件 1. #include <opencv2\opencv.hpp> 2. #include <iostream> 3. 4. using namespace cv; 5. using namespace std; 6. 7. int main() 8. { 9. Mat img; 10. VideoCapture video(0); //使用某个摄像头 11. 12. //读取视频 13. //VideoCapture video; 14. //video.open("cup.mp4"); 15. 16. if (!video.isOpened()) // 判断是否调用成功 17. { 18. cout << "打开摄像头失败,请确实摄像头是否安装成功"; 19. return -1; 20. } 21. 22. video >> img; //获取图像 23. //检测是否成功获取图像 24. if (img.empty()) //判断有没有读取图像成功 25. { 26. cout << "没有获取到图像" << endl; 27. return -1; 28. } 29. bool isColor = (img.type() == CV_8UC3); //判断相机(视频)类型是否为彩色 30. 31. VideoWriter writer; 32. int codec = VideoWriter::fourcc('M', 'J', 'P', 'G'); // 选择编码格式 33. //OpenCV 4.0版本设置编码格式 34. //int codec = CV_FOURCC('M', 'J', 'P', 'G'); 35. 36. double fps = 25.0; //设置视频帧率 37. string filename = "live.avi"; //保存的视频文件名称 38. writer.open(filename, codec, fps, img.size(), isColor); //创建保存视频文件的视频流 39. 40. if (!writer.isOpened()) //判断视频流是否创建成功 41. { 42. cout << "打开视频文件失败,请确实是否为合法输入" << endl; 43. return -1; 44. } 45. 46. while (1) 47. { 48. //检测是否执行完毕 49. if (!video.read(img)) //判断能都继续从摄像头或者视频文件中读出一帧图像 50. { 51. cout << "摄像头断开连接或者视频读取完成" << endl; 52. break; 53. } 54. writer.write(img); //把图像写入视频流 55. //writer << img; 56. imshow("Live", img); //显示图像 57. char c = waitKey(50); 58. if (c == 27) //按ESC案件退出视频保存 59. { 60. break; 61. } 62. } 63. // 退出程序时刻自动关闭视频流 64. //video.release(); 65. //writer.release(); 66. return 0; 67. }