更多更详细的文章请关注微信公众号:SLAM之路
概要
HighGUI:即high-level graphical user interface,,高级图像用户接口,使我们可以读取和写入与图像相关的文件(图像和视频),打开和管理窗口,展示图片,处理简单的鼠标、指针、键盘活动。
HighGUI库可以分为三部分:硬件部分、文件系统部分、GUI部分。
硬件部分:主要涉及相机操作;
文件系统部分:图像加载和保存;典型特征之一是,读取视频和读取相机采用相同的方法;
GUI部分:使我们能打开窗口,并将图像置于窗口中;使我们注册和响应鼠标、键盘事件等;
针对图像
加载图像
最普遍的工作是,加载(cv::imread())和保存文件(cv::imwrite()),这两个函数完成解压缩、压缩和与文件系统交互的全部工作。
首要学习的是,如何从文件系统将图像导入程序中:cv::imread().
cv::Mat cv::imread(
const string& filename, //文件名
int flags = cv::IMREAD_COLOR //设置如何解释的标志位
)
该函数并不关心文件扩展名,它会根据文件头几个字节决定合适的编码。
标志位flags默认是cv::IMREAD_COLOR,也可采用下表中参数:
此外,当cv::imread()无法加载图像时,并不会报运行错误,它仅是返回一个空cv::Mat。cv::Mat::empty()==true.
保存图像
显然,与cv::imread()相辅相成的函数是,cv::imwrite()。
bool cv::imwrite(
const string& filename, //文件名
cv::InputArray image, //要保存的图像
const vector<int>& params = vector<int>()
)
第一个参数是,文件名,扩展名文件保存的格式,支持的文件格式:
第三个参数说明:params,表示为特定格式保存的参数编码,它有默认值std::vector(),所以一般情况下不需要填写。如果更改的话,对于不同的图片格式,其对应的值不同功能不同,如下:
-
对于JPEG格式的图片,这个参数表示从0-100的图片质量(cv::IMWRITE_JPEG_QUALITY),默认值是95.
-
对于PNG格式的图片,这个参数表示压缩级别(cv::IMWRITE_PNG_COMPRESSION)从0-9.较高的值意味着更小的尺寸和更长的压缩时间而默认值是3.
-
对于PPM,PGM或PBM格式的图片,这个参数表示一个二进制格式标志(cv::IMWRITE_PXM_BINARY),取值为0或1,而默认值为1.
std::vector<int> compression_params;
compression_params.push_back(CV_IMWRITE_JPEG_QUALITY); //选择jpeg
compression_params.push_back(0); //在这个填入你要的图片质量
cv::Mat img1=cv::imread("aa.jpg");
std::cout<<cv::imwrite("b.png",img1,compression_params); //写入成功返回1反之0
cv::imshow("addWeight",img1);
压缩和解压缩
根据Opencv数组类型直接压缩图像,这种情况下, 压缩结果不是数组类型而是一个简单的字符缓冲,此时与原图像大小不同。
void cv:imencode(
const string& ext, //指定的编码
cv::InputArray img, //被编码图像
vector<uchar>& buf, //编码文件字节数
const vector<int>& params = vector<int>()
)
cv::imdecode()实现从字符缓冲到数组的解压缩。
void cv:imdecode(
cv::InputArray buf, //编码文件字节
int flags = cv::IMREAD_COLOR //
)
第一个参数缓冲,通常类型是std::vector<uchar>。
获取基本属性
cv::Mat img=cv::imread("a.jpg");
std::cout<<"rows:"<<img.rows<<std::endl
<<"cols:"<<img.cols<<std::endl
<<"chanls:"<<img.channels()<<std::endl;
cv::imshow("addWeight",img);
cv::waitKey(0);
针对视频
如何将视频从硬盘中读取和写入硬盘。
读取视频:对象cv::VideoCapture
该对象可以从相机或视频文件读取图像帧相关信息,我们可以使用三种不同调用方法创建对象cv::VideoCapture,
cv::VideoCapture::VideoCapture(const string& filename);
cv::VideoCapture::VideoCapture(int device); //相机编号
cv::VideoCapture::VideoCapture();
第一种,给定视频文件名字,可以打开文件并准备读取,如果打开成功,我们将能开始读取图像帧,cv::VideoCapture::isOpened()返回true打开成功。
第二种方法,数字代表相应相机的域,0表示只有一个相机,当同一个系统有多个相机时,数字逐步增加;
第三种方法,创建一个对象但未打开任何视频或相机,
cv::VideoCapture cap;
cap.open("my_video.avi");
读取图像帧(1):cv::VideoCapture::read()
在得到对象cv::VideoCapture后,通过调用cv::VideoCapture::read()读取图像帧,并将自动将对象cv::VideoCapture向前移动,以满足下次调用read时可以读取下一帧,循环往复。如果读取失败,那么函数将返回false。
读取图像帧(2):cv::VideoCapture::operator>>()
此外,也可以使用重加载符号读取视频对象的下一帧,其表现与read相同,但由于其是流操作符,无论读取是否成功,它都将返回原始对象cv::VideoCapture的引用,这种情况下,必须核对返回数组是否为空。
读取图像帧(3):cv::VideoCapture::grab()、retrieve()
将视频读取分为,grab阶段(将可使用图像复制到内部缓冲中,对用户不可见),retrieve阶段(处理grabbed数据的解码)。暂不细讲。
获取/设置相机属性cv::VideoCapture::get()/set()
视频文件不仅包含视频图像帧,也包括其他重要信息;当视频打开后,信息被复制到对象cv::VideoCapture的内部数据区域,读取这部分信息很常见,将这些数据写入文件也很有用,通过set()和get()函数使我们实现这种操作:
double cv::VideoCapture::get(
int propid //属性对应号
);
bool cv::VideoCapture::set(
int propid, //属性对应号
double value //所需设定属性值
);
下表是属性对应号:
保存视频:对象cv::VideoWriter
本质上,它与读取视频相同,但有额外几点需要注意:
正如我们使用cv::VideoCapture读取视频,我们保存视频前需要创建对象cv::VideoWriter,它有两种构建方法:一种是简单的默认构造器,仅创建一个没有初始化的视频对象,需要随后打开它;另一种则是包含所有必要的参数以创建视频写入对象:
第一种,默认构造器:
cv::VideoWriter out;
out.open(
"my.mpg",
CV_FOURCC("D","I","V","X"),
30.0,
cv::Size(630,480),
true
);
编码方式:CV_FOURCC(c0,c1,c2,c3);
第二种,完整构造器:
cv::VideoWriter::VideoWriter(
const string& filename, //文件名
int fourcc, //编码方式
double fps, //帧率,每秒多少帧
cv::Size frame_size, //每张图像大小
bool is_color = true //如果是false,灰度图像
);
再开始使用前,应该通过cv::VideoWriter::isOpened()方法确认是否准备好,如果返回true,可以执行;如果返回false,那表明你没有权限访问文件目录或者指定的编码方式无法使用。
写入图像帧(1):cv::VideoWriter::write()
再确认写入图像对象成功打开后,把数组传递给writer即可记录下图像帧;
cv::VideoWriter::write(
const Mat& image //记录到下一帧的图像
);
图像大小必须与writer构建的大小一致;如果writer是彩色的,那么图像必须是三通道,反之则是灰度图像单通道。
写入图像帧(2):cv::VideoWriter::operator<<()
writer也重载了输出流符号<<,当有了writer时,可以将图像写入视频流的方式,就像ofstream对象:
my_video_writer<<my_frame;
数据持久性
除了标准标准的视频压缩,Opencv也提供一种机制,用YAML或XML格式实现将各种类型数据序列化到硬盘或者从硬盘反序列化。使用这些方法可以再单一文件中加载和存储任何数量的Opencv数据对象(包括基本类型int/float等等)。这一部分中,我们将专注于一般对象的持久化:读取写入矩阵、Opencv结构、配置结构、logfiles。
对于读取写入文件,使用对象cv::FileStorage,这种对象本质上表示硬盘上一个文件。
写入文件:对象cv::FileStorage
FileStorage::FileStorage();
FileStorage::FileStorage(string fileName, int flag);
写入文件对象cv::FileStorage是XML和YAML数据文件的一种表示形式,创建该对象并将文件名赋予构造器,或者使用默认构造器建立空对象然后使用cv::FileStorage::open打开文件,参数flag是cv::FileStorage::WRITE或cv::FileStorage::APPEND,
FileStorage::open(string fileName, int flag);
在打开需要写的文件后,使用加载符cv::FileStorage::operator<<()写入文件。cv::FileStorage内文件采用两种形式中一种存储,分别是“映射”(也就是键值),或者是“序列”(一些列未命名的条目);
为了创建序列条目,可以先创建条目字符名称,然后是条目本身值。
cv::FileStorage::release()关闭文件对象;
键值类型存储:
cv::FileStorage fs = cv::FileStorage("NAME.YAML",cv::FileStorage::WRITE);
fs<<"hello"<<12;
fs<<"anArray"<<cv::Mat::eye(5,5,CV_8UC3);
序列类型存储
注意中括号与大括号的区别,在YAML中,大括号映射,中括号序列
cv::FileStorage fs = cv::FileStorage("NAME.YAML",cv::FileStorage::WRITE);
fs<<"theCat"<<"{";
fs<<"fur"<<"gray"<<"eyes"<<"green"<<"weightlbs"<<16;
fs<<"}";
输出结果:
%YAML:1.0
---
theCat:
fur: gray
eyes: green
weightlbs: 16
cv::FileStorage fs = cv::FileStorage("NAME.YAML",cv::FileStorage::APPEND);
fs<<"theTeam"<<"[";
fs<<"eddie"<<"tom"<<"scott";
fs<<"]";
输出结果:
%YAML:1.0
---
theCat:
fur: gray
eyes: green
weightlbs: 16
...
---
theTeam:
- eddie
- tom
- scott
cv::FileStorage fs("test.ml",cv::FileStorage::WRITE);
fs<<"frameCount"<<5;
time_t rawtime;time(&rawtime);
fs<<"calibrationDate"<<asctime(localtime(&rawtime));
cv::Mat cameraMatrix =(
cv::Mat_<double>(3,3)
<<1000,0,320,0,1000,230,0,0,1
);
cv::Mat distCoeff =(
cv::Mat_<double>(5,1)
<<0.1,0.01,-0.001,0,0
);
fs<<"cameraMatrix"<<cameraMatrix<<"distCoeff"<<distCoeff;
fs<<"feature"<<"[";
for (int i=0;i<3;i++){
int x=rand()%640;
int y=rand()%480;
uchar lbp=rand()%256;
fs<<"{:"<<"x"<<x<<"y"<<y<<"lbp"<<"[:";
for (int j=0;j<8;j++)
fs<<((lbp>>j)&1);
fs<<"]"<<"}";
}
fs<<"]";
fs.release();
%YAML:1.0 --- frameCount: 5 calibrationDate: "Tue Sep 08 16:42:36 2020\n" cameraMatrix: !!opencv-matrix rows: 3 cols: 3 dt: d data: [ 1000., 0., 320., 0., 1000., 230., 0., 0., 1. ] distCoeff: !!opencv-matrix rows: 5 cols: 1 dt: d data: [ 1.0000000000000001e-01, 1.0000000000000000e-02, -1.0000000000000000e-03, 0., 0. ] feature: - { x:41, y:227, lbp:[ 0, 1, 1, 1, 1, 1, 0, 1 ] } - { x:260, y:449, lbp:[ 0, 0, 1, 1, 0, 1, 1, 0 ] } - { x:598, y:78, lbp:[ 0, 1, 0, 0, 1, 0, 1, 0 ] }
读文件:对象cv::FileStorage
FileStorage::FileStorage(string fileName, int flag);
读文可以采用与写文件相同的方法,只是参数flag,应设定为cv::FileStorage::READ。如同写操作一样,也可以先用默认构造器创建空对象,随后使用cv::FileStorage::open()打开文件。
FileStorage::open(string fileName, int flag);
文件打开后,可以使用重加载数组符号cv::FileStorage::operator[]()或者迭代器cv::FileNodeIterator进行读操作。读取完毕后,使用cv::FileStorage::release()关闭文件。
读取映射类型时,cv::FileStorage::operator[]()通过字符键值关联到目标对象;读取序列类型时,需要使用整型参数调用该操作符。但是,该操作符的返回值并不是目标对象;它是一个cv::FileNode对象,它以抽象的形式表示值,具体参考cv::FileNode。
读文件:对象cv::FileNode
若干种合适的重加载符号使用该对象,cv::FileNode::operator>>()
cv::FileStorage fs("test.ml",cv::FileStorage::READ);
cv::Mat anArray;
fs["cameraMatrix"]>>anArray;
std::cout<<"anArray is :"<<std::endl<<anArray<<std::endl;
//支持强制转换
float aNumber;
fs["frameCount"]>>aNumber;
std::cout<<"aNumber:"<<std::endl<<aNumber<<std::endl;
fs.release();
若给定cv::FileNode对象,则成员函数cv::FileNode::begin()和end(),对于键值类型和序列类型都会提供first和“after last”迭代。
//打开文件
cv::FileStorage fs("test.ml",cv::FileStorage::READ);
//使用FileNode中的类型操作符
int frameCount = (int)fs["frameCount"];
//使用重加载符>>
std::string date;
fs["calibrationDate"]>>date;
cv::Mat cameraMatrix2,distCoeffs2;
fs["cameraMatrix"]>>cameraMatrix2;
fs["distCoeff"]>>distCoeffs2;
std::cout<<"frameCount: "<<frameCount<<std::endl;
std::cout<<"calibration date:"<<date<<std::endl;
std::cout<<"camera matirx: "<<cameraMatrix2<<std::endl;
std::cout<<"distortion coeffs: "<<distCoeffs2<<std::endl;
cv::FileNode features = fs["features"];
cv::FileNodeIterator it = features.begin(),it_end = features.end();
int idx = 0;
std::vector<uchar> lbpval;
for(;it!=it_end;++it,idx++){
std::cout<<"features #"<<idx<<":";
std::cout<<"x="<<(int)(*it)["x"]<<", y="<<(int)(*it)["y"]<<",lbp:(";
(*it)["lbp"]>>lbpval;
for (int i = 0;i<(int)lbpval.size();i++)
std::cout<<" "<<(int)lbpval[i];
std::cout<<")"<<std::endl;
}
fs.release();