OpenCV把用于操作系统、文件系统以及摄像机等硬件设备交换的函数纳入了HighGUI(High-level Graphical User Interface)模块中。有了HighGUI模块,我们可以方便地打开窗口、显示图像、读出或写入图像相关的文件、鼠标事件和键盘事件。下面将对三部分分别进行介绍。
部分 | 作用 |
---|---|
硬件部分 | 最主要是对摄像机的操作 |
文件系统部分 | 主要工作是完成图片的载入和保存 |
窗口系统GUI | 创建窗口并将图片放入窗口显示,同时添加响应鼠标和键盘事件功能 |
图像文件的处理
图像的载入与保存
使用cv::imread()读取图片
cv::Mat cv::imread(const string& filename, int flags=cv::IMREAD_COLOR)
作用:载入图像,若失败不会抛出异常,返回的是空的cv::Mat(可以用cv::Mat::empty()==true来判断)。
标志 | 含义 | 默认值 |
---|---|---|
cv::IMREAD_COLOR | 读取三通道图像,即使输入是灰度图像,也会有三通道,只是每个通道拥有相同的数据 | 是 |
cv::IMREAD_GRAYSCALE | 读取单通道图像 | 否 |
cv::IMREAD_ANYCOLOR | 通道数由文件实际通道数(不超过3) | 否 |
cv::IMREAD_ANYDEPTH | 允许加载超过8bit深度 | 否 |
cv::IMREAD_UNCHANGED | 相当于cv::IMREAD_ANYCOLOR和cv::IMREAD_ANYDEPTH组合使用,可保留alpha通道。 | 否 |
使用cv::imwrite()保存图像
bool cv::imwrite(const string& filename, InputArray image, const vector<int>& params=vector<int>())
作用:保存图像,成功返回true,否则返回false。
扩展名 | 格式 | 大小 | 通道 |
---|---|---|---|
.jpg或.jpeg | baseline JPEG | 8位 | 单通道或三通道 |
.jp2 | JPEG2000 | 8位或16位 | 单通道或三通道 |
.tif或.tiff | TIFF | 8位或16位 | 单通道、三通道或四通道 |
.png | PNG | 8位或16位 | 单通道、三通道或四通道 |
.bmp | BMP | 8位 | 单通道、三通道或四通道 |
.ppm或.pgm | NetPBM | 8位 | 单通道(PGM)和三通道(PPM) |
标志 | 含义 | 取值范围 | 默认值 |
---|---|---|---|
cv::IMWRITE_JPG_QUALITY | JPEG的质量 | 0~100 | 95 |
cv::IMWRITE_PNG_COMPRESSION | PNG压缩值 | 0~9 | 3 |
cv::IMWRITE_PXM_BINARY | 对PPM、PGM或PBM文件是否使用二值格式 | 0或1 | 1 |
关于codecs的一些注释
用于图像处理的库通常被叫做codecs(co-mpression and dec-compress libraries-s)。OpenCV对一些图片格式自带编码解码库,对于这些有一下三中选择:
① 不使用这些编码解码库;
② 使用OpenCV所提供的编码解码库;
③ 使用相应的拓展库(如libjpeg、libpng等)。
在Windows系统中默认选②选项,而在OS X或Linux系统上,默认选③选项。
图片的编码与解码
使用cv::imencode()压缩图像
void cv::imencode(const string& ext, InputArray img, vector<uchar>& buf, const vector<int>& params=vector<int>())
作用:对图像进行编码。
参数 | 含义 |
---|---|
ext | 文件扩展名,用于函数与对应的编码压缩方式相关联。指明格式并帮助系统索引对应编码库 |
img | 要被压缩的图像数据 |
buf | 用来存储编码压缩后的图像数据的字符数组 |
params | 指明要使用编码器的输入参数 |
使用cv::imdecode()解码文件
cv::Mat cv::imdecode(InputArray buf, int flags=cv::IMREAD_COLOR)
作用:将字节流数据解码成数组形式的图像。
视频处理
使用cv::VideoCapture对象读取视频流
cv::VideoCapture::VideoCapture(const string& filename)
cv::VideoCapture::VideoCapture(int device)
cv::VideoCapture::VideoCapture()
作用:打开视频文件或摄像机。打开成功则cv::VideoCapture::isOpen()会返回true,否则返回false。
构造设备参数时,传入的标识符是相机的域索引和相机索引之和。相机索引是指系统中摄像机设备的索引,从0开始计数。相机域表明我们用何种相机,如下表所示。
相机捕捉常数 | 数值 | 相机捕捉常数 | 数值 | 相机捕捉常数 | 数值 |
---|---|---|---|---|---|
cv::CAP_ANY | 0 | cv::CAP_DSHOW | 700 | cv::CAP_OPENNI2 | 1600 |
cv::CAP_MIL | 100 | cv::CAP_PVAPI | 800 | cv::CAP_OPENNI2_ASUS | 1610 |
cv::CAP_VFW | 200 | cv::CAP_OPENNI | 900 | cv::CAP_GPHOTO2 | 1700 |
cv::CAP_V4L | 200 | cv::CAP_OPENNI_ASUS | 910 | cv::CAP_GSTREAMER | 1800 |
cv::CAP_V4L2 | 200 | cv::CAP_ANDROID | 1000 | cv::CAP_FFMPEG | 1900 |
cv::CAP_FIREWIRE | 300 | cv::CAP_XIAPI | 1100 | cv::CAP_IMAGES | 2000 |
cv::CAP_FIREWARE | 300 | cv::CAP_AVFOUNDATION | 1200 | cv::CAP_ARAVIS | 2100 |
cv::CAP_IEEE11394 | 300 | cv::CAP_GIGANETIX | 1300 | cv::CAP_OPENCV_MJPEG | 2200 |
cv::CAP_DC1394 | 300 | cv::CAP_MSMF | 1400 | cv::CAP_INTEL_MFX | 2300 |
cv::CAP_QT | 500 | cv::CAP_WINRT | 1410 | cv::CAP_XINE | 2400 |
cv::CAP_UNICAP | 600 | cv::CAP_INTELPERC | 1500 |
当传入的标识符为-1时,则OpenCV会弹出一个选择窗口,可手动选择希望使用的相机。
最后一种形式只是直接创建一个VideoCapture对象,无法直接使用,需要调用cv::VideoCapture::open()函数来指定文件名或设备号,和前两种形式一样。
使用cv::VideoCapture::read()从视频流中读取图像
bool cv::VideoCapture::read(OutputArray img)
作用:从文件中读取下一帧数据,并将结果存入img中,并会自动更新。读取成功返回true,读取失败返回false。
使用cv::VideoCapture::operator>>()从视频流读取图像数据
cv::VideoCapture& cv::VideoCapture::operator>>(Mat& img)
作用:重载输入流操作符,从文件中读取下一帧数据,并将结果存入img中。
使用cv::VideoCapture::grab()和cv::VideoCapture::retrieve()从视频流中读取图像数据
bool cv::VideoCapture::grab()
作用:将当前可使用的图像数据拷贝至用户看不到的一个内存缓存区。抓取的图像帧是未处理的,该函数只是设计用于将原始数据尽快获取到电脑。
bool cv::VideoCapture::retrieve(OutputArray image, int channel)
作用:对内部缓存数据进行必要的图像解码、内存分配与拷贝,最后以Mat形式的序列将图像帧返回。
相机属性cv::VideoCapture::get()和cv::VideoCapture::set()
double cv::VideoCapture::get(int propid)
bool cv::VideoCapture::set(int propid, double value)
作用:获取或设置元数据(meta data)。
ID | 是否只在摄像头模式下可以使用 | 含义 |
---|---|---|
cv::CAP_PROP_POS_MSEC | 视频文件中当前位置(毫秒)或视频捕获时间戳 | |
cv::CAP_PROP_POS_FRAMES | 从0开始下一帧的索引 | |
cv::CAP_PROP_POS_AVI_RATIO | 视频的相对位置 | |
cv::CAP_PROP_FRAME_WIDTH | 视频帧的像素宽度 | |
cv::CAP_PROP_FRAME_HEIGHT | 视频帧的像素高度 | |
cv::CAP_PROP_FPS | 录制视频的帧速率 | |
cv::CAP_PROP_FOURCC | 四个字符代码指示编解码 | |
cv::CAP_PROP_FRAME_COUNT | 视频文件的总帧数 | |
cv::CAP_PROP_FORMAT | 返回Mat对象的格式(如CV_8UC3) | |
cv::CAP_PROP_MODE | 表示捕捉模式,值是特定于正在使用的视频后端(如DC1394) | |
cv::CAP_PROP_BRIGHTNESS | √ | 相机的亮度设置 |
cv::CAP_PROP_CONCAT | √ | 相机的对比度设置 |
cv::CAP_PROP_SATURATION | √ | 相机的饱和度设置 |
cv::CAP_PROP_HUE | √ | 相机的色调设置 |
cv::CAP_PROP_GAIN | √ | 相机的增益设置 |
cv::CAP_PROP_EXPOSURE | √ | 相机的曝光设置 |
cv::CAP_PROP_CONVERT_RGB | √ | 如果非0,获取的图像将被转换为具有三个通道 |
cv::CAP_PROP_WHITE_BALANCE | √ | 相机的白平衡设置 |
cv::CAP_PROP_RECTIFICATION | √ | 立体相机整流标志(仅适用于DC1934-2.x) |
使用cv::VideoWriter对象写入视频
cv::VideoWriter::VideoWriter(const string& filename, int fourcc, double fps, Size frame_size, bool is_color=true)
cv::VideoWriter::VideoWriter()
作用:创建一个VideoWriter对象,is_color为true则传入彩色图,反之可以传入灰度图。同样第二种形式不能直接使用,需要调用cv::VideoWriter::open()函数进行配置。
判断是否初始化成功,可通过cv::VideoWriter::isOpen()的返回值进行判断。
使用cv::VideoWriter::write()函数完成图像帧的写入
cv::VideoWriter::write(const Mat& img)
作用:完成图像帧的写入,图像必须和cv::VideoWriter具有相同的图像尺寸,如果是彩色必是三通道,反之使用单通道。
使用cv::VideoWriter::operator<<()将图像写入视频
cv::VideoWriter& cv::VideoWriter::operator<<(Mat& img)
作用:重载输出流操作符,将img存入视频文件。
数据存储
作为对标准视频压缩方式的补充,OpenCV提供了一种序列化与反序列化的机制,用于将不同数据类型的数据已YAML或XML格式写入磁盘或者从磁盘读取。这些方法可以用来加载或者保存任何OpenCV的数值变量到一个文件中。
cv::FileStorage的写入
cv::FileStorage::FileStorage(string filename, int flag)
cv::FileStorage::FileStorage()
作用:创建FileStorage对象(代表XML或YAML格式的数据文件),flag指定功能,写入时可为cv::FileStorage::WRITE和cv::FileStorage::APPEND。同样第二种形式不能直接使用,需要调用cv::FileStorage::open()函数进行配置。
写入时可以使用重载输出操作符函数cv::FileStorage::operator<<()进行写入。写完时调用cv::FileStorage::release()关闭该文件。
cv::FileStorage内部数据的存储形式主要有mapping和sequence,在最顶层是一个mapping,其中可以嵌套mapping和sequence。新建mapping或sequence分别使用{}和[]。
#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <time.h>// 使用cv::FileStorage创建一个test.yml参数文件
int main()
{// 创建FileStorage对象,实现其写入功能cv::FileStorage fs("test.yml", 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, 240, 0, 0, 1);cv::Mat distCoeffs = (cv::Mat_<double>(5, 1) << 0.1, 0.01, -0.001, 0, 0);fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;// 随机生成数及数组并写入fs << "features" << "[";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();return 0;
}
用记事本打开test.yml文件,内容如下:
cv::FileStorage读取文件
cv::FileStorage::FileStorage(string filename, int flag)
cv::FileStorage::FileStorage()
作用:创建FileStorage对象(代表XML或YAML格式的数据文件),flag指定功能,读取时可为cv::FileStorage::READ。同样第二种形式不能直接使用,需要调用cv::FileStorage::open()函数进行配置。
读取时可以使用重载输出操作符函数cv::FileStorage::operator>>()进行读取,得到的不是直接的数据,而是FileNode对象。若该值是基本数据类型,则可以通过强制类型转换直接获取;若该值是mapping,则通过重载操作符[]并给定所需关键字访问;若该值是sequence,则通过重载操作符[]并给定整型参数访问,也可使用cv::FileStorageIterator迭代器进行访问。
读完时调用cv::FileStorage::release()关闭该文件。
cv::FileNode
FileNode的基本使用在上述内容中已做过介绍,下面将主要针对cv::FileStorageIterator进行介绍。
给定一个FileNode对象,通过cv::FileNode::begin()和cv::FileNode::end()可以分别返回该mapping或者sequence的首元素的迭代器和末尾首元素的迭代器。该迭代器对象可以通过cv::FileStorageIterator::operator*()来进行解引用操作并返回迭代器对应的位置的FileNode对象,并通过自增和自减来控制迭代器。
若其对一个mapping进行遍历,返回的FileNode对象将会有名字属性,可通过cv::FileNode::name()得到。同样,通过cv::FileNode::type()可获得cv::FileNode中定义的枚举类型。
示例 | 说明 |
---|---|
cv::FileNode fn | 文件节点对象的默认构造函数 |
cv::FileNode fn1(fn0) | 文件节点对象的复制构造函数,从节点fn0创建节点fn1 |
cv::FileNode fn1(fs,node) | 文件节点对象构造函数,从C风格的CvFileStorage*指针和C风格的CvFileNode*指针节点创建FileNode对象 |
fn[ (string) key ] fn[ (const char*) key ] | 命名子串(映射节点)的STL字符串或C字符串存取器,将秘钥转换为适当的子节点。 |
fn[ (int) id ] | 子编号(序列节点)的访问;将ID转换为相应的子节点 |
fn.type() | 返回节点类型enum |
fn.empty() | 确定节点是否为空 |
fn.isNone() | 确定节点是否为None |
fn.isSeq() | 确定节点是否为序列 |
fn.isMap() | 确定节点是否为映射 |
fn.isInt() fn.isReal() fn.isString() | 确定节点是否为整数 确定节点是否为浮点数 确定节点是否为字符串 |
fn.name() | 如果节点是映射的子节点,返回节点名称 |
size_t sz= fn.size() | 如果是序列或映射,返回节点中的元素数量,否则返回1。 |
(int) fn (float) fn (double) fn (string) fn | 从包含整数的节点中提取 从包含32位浮点数的节点中提取 从包含64位浮点数的节点中提取 从包含字符串的节点中提取 |
示例 | 说明 |
---|---|
cv::FileNode::NONE = 0 | 节点类型为None |
cv::FileNode::INT = 1 | 节点包含一个整数 |
cv::FileNode::REAL = 2 cv::FileNode::FLOAT = 2 | 节点包含一个浮点数(不分精度) |
cv::FileNode::STR = 3 cv::FileNode::STRING = 3 | 节点包含一个字符串 |
cv::FileNode::REF = 4 | 节点包含参考(即复合对象) |
cv::FileNode::SEQ = 5 | 节点本身是其他节点的序列 |
cv::FileNode::MAP = 6 | 节点本身是其他节点的映射 |
cv::FileNode::FLOW = 8 | 节点是序列或映射的紧凑表示 |
cv::FileNode::USER = 16 | 节点是注册对象(如矩阵) |
cv::FileNode::EMPTY = 32 | 节点没有复制给它 |
cv::FileNode::NAMED = 64 | 节点是映射的子节点(即它有一个名字) |
注:一个FileNode除了拥有前8种属性之外还可以同时拥有最后四个中的一个或全部属性。
#include "stdafx.h"
#include <opencv2/opencv.hpp>// 使用FileStorage来读取test.yml文件
int main()
{// 创建FileStorage对象,实现读取功能cv::FileStorage fs("D:\\personal-data\\opencv\\example8-2\\example8-2\\test.yml", cv::FileStorage::READ);// 对FileNode进行强制类型转换,得到数据int frameCount = (int)fs["frameCount"];// 使用重载>>操作符std::string date;fs["calibrationDate"] >> date;cv::Mat cameraMatrix, distCoeffs;fs["cameraMatrix"] >> cameraMatrix;fs["distCoeffs"] >> distCoeffs;std::cout << "frameCount" << frameCount << std::endl;std::cout << "calibrationDate" << date << std::endl;std::cout << "cameraMatrix" << cameraMatrix << std::endl;std::cout << "distCoeffs" << distCoeffs << 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();return 0;
}
运行结果:
frameCount5
calibrationDateMon Aug 20 20:20:03 2018cameraMatrix[1000, 0, 320;0, 1000, 240;0, 0, 1]
distCoeffs[0.1;0.01;-0.001;0;0]
features #0: x=41, y=227, lbp: ( 0 1 1 1 1 1 0 1)
features #1: x=260, y=449, lbp: ( 0 0 1 1 0 1 1 0)
features #2: x=598, y=78, lbp: ( 0 1 0 0 1 0 1 0)