目录
一、opencv的图像缓存表达(cv::mat)
二、图片读写
2.1 图片读写API
2.2 图片读写案例
2.3 案例编译与测试
三、opencv的视频读写:
3.1 视频读写接口
3.2 视频读写案例
3.3 编译与测试
一、opencv的图像缓存表达(cv::mat)
OpenCV定义了各式的大型数组类型来表达视觉数据,其中cv::mat是这些类型中最重要的一个,它是opencv的c++实现最重要的核心类型,无数关于图像、视频的处理函数都是围绕cv::mat类型展开的,该类型或作为函数参数、或作为类成员、或作为返回值等。
cv::mat可以作为任意维度的数组来使用,任意的图像数据在opencv中就是按顺序存储在n维的cv::mat 数组中。假设现在有一个3*3个像素的图片,该图片是一个灰度图,即没个像素仅有一个通道表达的数值,数值范围就是【0~255】。从图像表达的角度来看,该图片就是一个3*3的矩阵,只是每个元素的取值规定了【0~255】的数据范围,例如:
灰度img表达:
12 25 17
22 37 21
56 38 26
如果从cv::mat 数组存储的角度来说,就是一个顺序存储的一维数组,只是,该数组还会给出类矩阵的行与列:
cv::mat表达:
dims:1
rows:3
cols:3
data:12 25 17 22 37 21 56 38 26
且看看cv::mat 数组类型的数据存储相关成员变量定义:
灰度如果看做是一维表达,那么彩色图像就可以看做三维(RGB)或四维(RGBA)表达,还是以3*3大小为例表达一个图像,假设一个像素的颜色排列就是RGB顺序,每个颜色通道的取值同样是【0~255】,假设该图像每个像素的各颜色通道值一样,则:
彩色img表达:
22 22 22 33 33 33 44 44 44
21 21 21 32 32 32 43 43 43
20 20 20 31 31 31 42 42 42
而对于从cv::mat 数组存储的角度来就是那顺序存储的,只是同时给出了通道数(维度)、行、列表达:
彩色图cv::mat表达:
dims:1
rows:3
cols:3
data:22 22 22 33 33 33 44 44 44 21 21 21 32 32 32 43 43 43 20 20 20 31 31 31 42 42 42
同样针对更多维度的数据矩阵表达都是类似的采用顺序存储方式,将矩阵数据按通道顺序、行、列顺序地排列存储。数据获取就是通过存储这些数据的指针地址,并通过维度数、行数、列数的偏移计算来取的对应偏移地址,从而获得具体数据值的。
二、图片读写
2.1 图片读写API
OpenCV读取和写入图片数据的两个重要函数集就是cv::imread和cv::imwrite,定义在imgcodecs.hpp:
namespace cv
{
//读取
CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR );
CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int flags = IMREAD_ANYCOLOR);
CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int start, int count, int flags = IMREAD_ANYCOLOR);
//...
//写入
CV_EXPORTS_W bool imwrite( const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>());
CV_WRAP static inline
bool imwritemulti(const String& filename, InputArrayOfArrays img,const std::vector<int>& params = std::vector<int>())
//...
//其他
};
目前OpenCV支持读写和操作的图像格式有(cv::imread的API函数描述信息):
Currently, the following file formats are supported:
- Windows bitmaps - \*.bmp, \*.dib (always supported)
- JPEG files - \*.jpeg, \*.jpg, \*.jpe (see the *Note* section)
- JPEG 2000 files - \*.jp2 (see the *Note* section)
- Portable Network Graphics - \*.png (see the *Note* section)
- WebP - \*.webp (see the *Note* section)
- Portable image format - \*.pbm, \*.pgm, \*.ppm \*.pxm, \*.pnm (always supported)
- PFM files - \*.pfm (see the *Note* section)
- Sun rasters - \*.sr, \*.ras (always supported)
- TIFF files - \*.tiff, \*.tif (see the *Note* section)
- OpenEXR Image files - \*.exr (see the *Note* section)
- Radiance HDR - \*.hdr, \*.pic (always supported)
- Raster and Vector geospatial data supported by GDAL (see the *Note* section)
2.2 图片读写案例
下来看看这两个函数集的用法,下列代码中,先读取一张图片,存储在前面讲述的cv::mat类型中,然后对图像数据做了一些转换(这不是重点),在将数据另存储为新的一张图片:
#include "opencv2/opencv.hpp"
int main( int argc, char** argv )
{
cv::Mat img1,img2;
cv::namedWindow("Example1",cv::WINDOW_AUTOSIZE );
cv::namedWindow("Example2",cv::WINDOW_AUTOSIZE);
img1=cv::imread( argv[1]);//读取图片
cv::imshow("Example1",img1);
cv::pyrDown( img1, img2);//高斯模糊及降采样
cv::imshow("Example2",img2);
cv::imwrite("2.png",img2);//保存转换后的图片
cv::waitKey(0);
return 0;
}
代码中为了程序演示效果,采用了highgui模块的创建可视化窗口cv::namedWindow和显示图片cv::imshow的函数接口,程序最后通过在显示窗口中捕获用户输入(cv::waitKey)而结束程序应用。
2.3 案例编译与测试
本文编译这段程序采用的是win10下搭建的mingw编译环境(可请查看本专栏的C/C++开发,win下OpenCV+MinGW编译环境搭建博文),Makefile文件配置如下:
#/bin/sh
CX= g++
BIN := ./
TARGET := show_img3.exe
FLAGS := -std=c++11 -static
SRCDIR := ./
#INCLUDES
INCLUDEDIR := -I"../../opencv_MinGW/include"
staticDir := ../../opencv_MinGW/x64/mingw/staticlib/
#opencv_world放在前,然后是opencv依赖的第三方库,后面的库是MinGW编译工具的库
LIBDIR := -L $(staticDir) -lopencv_world460 -lade -lIlmImf -lquirc -lzlib \
-llibjpeg-turbo -llibopenjp2 -llibpng -llibprotobuf -llibtiff -llibwebp \
-lgdi32 -lComDlg32 -lOleAut32 -lOle32 -luuid
source := $(wildcard $(SRCDIR)/*.cpp)
$(TARGET) :
$(CX) $(FLAGS) $(INCLUDEDIR) $(source) -o $(BIN)/$(TARGET) $(LIBDIR)
clean:
rm $(BIN)/$(TARGET)
编译命令如下:
然后运行程序来看看如何:
程序顺利打开图片1.PNG,然后将采样后,另存储为2.png图片。可以通过在两个显示窗口激活情况下输入任意信息结束程序运行,就可以在运行目录下可以顺利看到存储好的图片。
三、opencv的视频读写:
3.1 视频读写接口
opencv读取视频有两种主要方式,一就是从视频中逐帧读取图片,然后不断刷新显示,就是一段视频流,即所谓的视频数据就是一组连续的图片集。
另一种方式就是从已经存储的视频文件逐帧读取图片,同样不断刷新显示,形成视频。
从视频获取图片通过一个关键类cv::VideoCapture来实现,该类定义在videoio.hpp文件中,支持打开视频、获取视频信息及图片数据等操作:
class CV_EXPORTS_W VideoCapture
{
public:
CV_WRAP virtual bool open(const String& filename, int apiPreference = CAP_ANY);
CV_WRAP virtual bool open(const String& filename, int apiPreference, const std::vector<int>& params);
CV_WRAP virtual bool open(int index, int apiPreference = CAP_ANY);
//...
virtual VideoCapture& operator >> (CV_OUT Mat& image);
virtual VideoCapture& operator >> (CV_OUT UMat& image);
//...
CV_WRAP virtual bool read(OutputArray image);
//..
//其他
};
而存储视频是通过cv::VideoWriter类来实现的,cv::VideoWriter可以新建打开一个视频文件,将图片数据逐帧输出到视频文件中。
class CV_EXPORTS_W VideoWriter
{
public:
CV_WRAP virtual bool open(const String& filename, int fourcc, double fps,
Size frameSize, bool isColor = true);
CV_WRAP bool open(const String& filename, int apiPreference, int fourcc, double fps,
Size frameSize, bool isColor = true);
//...
virtual VideoWriter& operator << (const Mat& image);
virtual VideoWriter& operator << (const UMat& image);
//...
CV_WRAP virtual void write(InputArray image);
//..
//其他
};
3.2 视频读写案例
下来看看这段代码,这是一个从摄像头读取视频数据或从视频文件中读取数据的例子:
#include <opencv2/opencv.hpp>
#include <iostream>
int main( int argc,char* argv[])
{
const char* win_in_name = "Example_in";
cv::namedWindow(win_in_name,cv::WINDOW_AUTOSIZE );
cv::VideoCapture capture;
double fps = 25; //摄像头频率
cv::Size size = {640,480};//摄像头像素
if(argc==1){
capture.open(0);// open the first camera
}else{
capture.open(argv[1]);
fps = capture.get( cv::CAP_PROP_FPS ); //视频频率
size = { //视频显示像素大小
(int)capture.get(cv::CAP_PROP_FRAME_WIDTH ), (int)capture.get( cv::CAP_PROP_FRAME_HEIGHT )
};
}
if(!capture.isOpened())
{
// check if we succeeded
std::cerr << "Couldn't open capture."<< std::endl;
return-1;
}
cv::VideoWriter writer;
const char* out_video = "cpy.avi";
std::cout << "fps="<<fps <<","<< "size="<<size<<"\n";
writer.open( out_video,cv::VideoWriter::fourcc('M','J','P','G'),fps,size);
cv::Mat bgr_frame;
for(;;){
capture >> bgr_frame;
if(bgr_frame.empty()) break; // end if done
cv::imshow(win_in_name,bgr_frame );
writer << bgr_frame; //原视频直接写入测试
char c = cv::waitKey(10);
if( c == 27 )break;// allow the user to break out
}
capture.release();
}
上述代码中,如果成行输出参数1是一个视频文件,则cv::VideoCapture对象读取视频文件,并读取源视频文件的频率和像素大小作为存储格式参数传递给新存储,如果是空则cv::VideoCapture对象读取摄像头视频,采用的是摄像头的频率和像素大小(本文采用的笔记本摄像头是25fps/640*480),然后cv::VideoWriter打开一个视频文件,将将采样后的视频输出存储。按键esc可以退出程序(if( c == 27 )break;)。
3.3 编译与测试
工程配置文件Makefile和前面图片处理工程类似:
#/bin/sh
CX= g++
BIN := ./
TARGET := show_video4.exe
FLAGS := -std=c++11 -static
SRCDIR := ./
#INCLUDES
INCLUDEDIR := -I"../../opencv_MinGW/include"
staticDir := ../../opencv_MinGW/x64/mingw/staticlib/
#opencv_world放在前,然后是opencv依赖的第三方库,后面的库是MinGW编译工具的库
LIBDIR := -L $(staticDir) -lopencv_world460 -lade -lIlmImf -lquirc -lzlib \
-llibjpeg-turbo -llibopenjp2 -llibpng -llibprotobuf -llibtiff -llibwebp \
-lgdi32 -lComDlg32 -lOleAut32 -lOle32 -luuid
source := $(wildcard $(SRCDIR)/*.cpp)
$(TARGET) :
$(CX) $(FLAGS) $(INCLUDEDIR) $(source) -o $(BIN)/$(TARGET) $(LIBDIR)
clean:
rm $(BIN)/$(TARGET)
编译命令如下(由于引用全包头文件opencv.hpp因此编译会慢些,这也是前面博文建议大家在实际项目中精准引用对于头文件的原因):
运行程序,打开一个本地存储的视频(mp4格式):
视频播放完成后自动结束,输出一个新的视频文件cpy.avi,用播放器可以打开该新视频文件:
直接运行程序(参数1为空),本文是打开了笔记本摄像头:
同样可以在程序运行目录下(esc按键退出录像)存储摄像头录取的视频cpy.avi,覆盖了前面视频文件的另存内容;
将程序输出存储文件判定一下大小,然后再次打开一个新文件,不间断地录像就是我们行车记录仪类似的录像功能了,大家感兴趣的可以尝试修改下。