第二节 图像与视频读取与保存
在前面,详细描述了OpenCV库的imgcodecs模块的图像读取、保存、编码、解码及highgui模块的基本操作。接下来将详细描述highgui模块对图像、视频的显示操作。
1、图像显示
cv::imshow:在指定的窗口中显示图像。
void cv::imshow (const String & winname,InputArray mat)
imshow函数在指定的窗口中显示图像。 如果窗口是使用cv :: WINDOW_AUTOSIZE标志创建的,则图像以其原始大小显示,但是它仍然受屏幕分辨率的限制。 否则,将缩放图像以适合窗口。 该功能可能会缩放图像,具体取决于其深度:
- 如果图像是8位无符号的,则按原样显示。
- 如果图像是16位无符号或32位整数,则将像素除以256。即,值范围[0,255 * 256]映射到[0,255]。
- 如果图像是32位或64位浮点,则将像素值乘以255。即,值范围[0,1]映射为[0,255]。
如果使用OpenGL支持创建了window,则cv :: imshow还支持ogl :: Buffer,ogl :: Texture2D和cuda :: GpuMat作为输入。
如果在创建窗口之前调用此函数,则假定使用cv :: WINDOW_AUTOSIZE创建一个新窗口。
如果需要显示大于屏幕分辨率的图像,则需要在imshow之前调用namedWindow(“”,WINDOW_NORMAL)。
注意:
此函数后应跟cv :: waitKey函数,该函数显示指定毫秒的图像。 否则,它将不会显示图像。 例如,waitKey(0)将无限期显示窗口,直到任何按键为止(适用于图像显示)。 waitKey(25)将显示25毫秒的帧,此后将自动关闭显示。 (如果将其放在循环中以阅读视频,它将逐帧显示视频)
在windows系统中,OpenCV还支持如下操作:
- 按Ctrl + C会将图像复制到剪贴板。
- 按Ctrl + S将显示一个对话框来保存图像。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 创建窗口
cv::namedWindow("image",cv::WINDOW_NORMAL);
cv::Mat src = cv::imread("d:/develop/machine-vision-workbench/resources/images/f1.jpg");
// 调整窗口大小
cv::resizeWindow("image",640,480);
cv::imshow("image",src);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
2、视频读取、显示与保存
1)、cv::VideoCapture:从视频文件(本地或网络流)、图像序列、摄像机捕获视频的类。
-
cv::VideoCapture()
-
cv::VideoCapture::VideoCapture(const String & filename,int apiPreference = CAP_ANY)
-
cv::VideoCapture::VideoCapture(int index,int apiPreference = CAP_ANY)
经常使用的为VideoCapture第二、第三个构造函数。当filename为本地视频文件路径或网络视频流(如rtsp、rtmp、hls等协议);参数index为本地摄像头设备的序号,一般是从0开始。
VideoCapture提个了几个简单而高效的函数:
-
cv::VideoCapture::get:查询视频的属性。具体可以参考cv::VideoCaptureProperties
virtual double cv::VideoCapture::get(int propId)const
-
cv::VideoCapture::getBackendName:返回使用的后端API名称(一般情况下默认为FFMPG)
String cv::VideoCapture::getBackendName ()const
-
cv::VideoCapture::grab:抓取视频文件或摄像头设备下一帧图像。如果成功,则返回true。
virtual bool cv::VideoCapture::grab()
该功能的主要用途是在多相机环境中,尤其是在相机没有硬件同步的情况下。 也就是说,您为每个摄像机调用VideoCapture :: grab(),然后调用较慢的方法VideoCapture :: retrieve()解码并从每个摄像机获取帧。 这样,消除了去马赛克或运动jpeg解压缩等方面的开销,并且从不同摄像机检索到的帧将在时间上更近。
另外,当连接的摄像机是多头摄像机(例如,立体摄像机或Kinect设备)时,从中检索数据的正确方法是先调用VideoCapture :: grab(),然后再调用VideoCapture :: retrieve() 使用不同的channel参数值一次或多次。
-
cv::VideoCapture::isOpened:判断文件是否准备好或摄像设备是否初始化成功。如果成功,则返回true。
-
cv::VideoCapture::open:打开视频文件或摄像设备,效果等同VideoCapture构造函数传递相关参数初始化
-
**操作符>>:**抓取并返回下一帧图像。
-
cv::VideoCapture::read:抓取、解码并返回下一帧图像。
virtual bool cv::VideoCapture::read (OutputArray image)
-
cv::VideoCapture::release:关闭打开文件或摄像设备。
-
cv::VideoCapture::retrive:解码并返回抓取的视频帧。
virtual bool cv::VideoCapture::retrieve (OutputArray image,int flag = 0)
-
cv::VideoCapture::set:VideoCapture中属性设置。
virtual bool cv::VideoCapture::set(int propId,double value)
propId请参考:cv::VideoCaptureProperties
-
cv::VideoCapture::waitAny:静态函数,等待来自VideoCapture的就绪帧。
static bool cv::VideoCapture::waitAny(const std::vector< VideoCapture > & streams,std::vector< int > & readyIndex,int64 timeoutNs = 0)
参数streams为输入流列表;readyIndex,为就绪的设备列表序号;timeoutNs超时时间。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 打开一个网络视频流
const std::string webstream = "http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8";
cv::VideoCapture cap(webstream);
if(!cap.isOpened()){
cerr << "cannot open rtsp stream.\n";
exit(0);
}
// 查询后端支持的API
cout << "backend name:" << cap.getBackendName();
// 获取视频流的帧宽度和高度
int width = cap.get(cv::CAP_PROP_FRAME_WIDTH);
int height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
cout << "width = " << width << ",height = " << height << endl;
cv::Mat frame;
int key = -1;
while(true){
cap >> frame;
if(frame.empty()){
cerr << "cannot grab frame from video stream\n";
break;
}
cv::imshow("video",frame);
key = cv::waitKey(10);
if(key == 27){
break;
}
}
cv::destroyAllWindows();
cap.release();
return 0;
}
2)cv::VideoWriter:将视频或图像序列写到文件。
- VideoWriter()
- VideoWriter (const String &filename, int fourcc, double fps, Size frameSize, bool isColor=true)
- VideoWriter (const String &filename, int apiPreference, int fourcc, double fps, Size frameSize, bool isColor=true)
- VideoWriter (const String &filename, int fourcc, double fps, const Size &frameSize, const std::vector< int > ¶ms)
- VideoWriter (const String &filename, int apiPreference, int fourcc, double fps, const Size &frameSize, const std::vector< int > ¶ms)
其中,参数fourcc为视频或图像序列的编码格式,比如VideoWriter :: fourcc(‘P’,‘I’,‘M’,‘1’)是MPEG-1编解码器,VideoWriter :: fourcc(‘M’,‘J’,‘P’,‘G’)是 运动jpeg编解码器等;参数fps为帧率;参数frameSize为帧大小;参数isColor为是否按彩色图像编码;经常使用的为第二个构造函数。
cv::VideoCapture::write函数实现了将视频帧或图像序列写到文件。
virtual void cv::VideoWriter::write(InputArray image)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 从本地摄像头创建VideoCapture
cv::VideoCapture cap(0);
if(!cap.isOpened()){
cerr << "cannot open camera.\n";
exit(0);
}
// 获取视频流的帧宽度、高度和帧率
int width = cap.get(cv::CAP_PROP_FRAME_WIDTH);
int height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
int rate = cap.get(cv::CAP_PROP_FPS);
cout << "video info:width = " << width << ",height = " << height << ",frame rate = " << rate << endl;
// 创建一个MPEG-4编码 视频Writer
cv::VideoWriter writer("e:/temp/output.mp4",
cv::VideoWriter::fourcc('D','I','V','X'),rate,
cv::Size(width,height));
cv::Mat frame;
int key = -1;
while(cap.isOpened()){
cap >> frame;
if(frame.empty()){
cout << "cannot grab frame from camera.\n";
break;
}
// TODO:对图像进行各种处理
// 对图像进行取反,实现底片效果
cv::imshow("video",frame);
frame -= 255;
// 保存视频
writer.write(frame);
cv::imshow("video:invert",frame);
key = cv::waitKey(10);
if(key == 27){
break;
}
}
cv::destroyAllWindows();
cap.release();
writer.release();
return 0;
}
常见的视频格式编码如下:
-
CV_FOURCC(‘P’, ‘I’, ‘M’, ‘1’) = MPEG-1 code
-
CV_FOURCC(‘M’, ‘J’, ‘P’, ‘G’) = motion-jpeg codec
-
CV_FOURCC(‘M’, ‘P’, ‘4’, ‘2’) = MPEG-4.2 codec
-
CV_FOURCC(‘D’, ‘I’, ‘V’, ‘3’) = MPEG-4.3 codec
-
CV_FOURCC(‘D’, ‘I’, ‘V’, ‘X’) = MPEG-4 codec
-
CV_FOURCC(‘U’, ‘2’, ‘6’, ‘3’) = H263 codec
-
CV_FOURCC(‘I’, ‘2’, ‘6’, ‘3’) = H263I codec
CV_FOURCC(‘F’, ‘L’, ‘V’, ‘1’) = FLV1 codec MPEG-1是为CD光盘介质定制的视频和音频压缩格式; Motion JPEG是一种视频压缩格式,其中每一帧图像都分别使用JPEG编码; MPEG-4利用很窄的带宽,通过帧重建技术,压缩和传输数据,以求以最少的数据获得最佳的图像质量;
具体可以参考fourcc