【小白】openCV入门杂记(2)---认识openCV
2018.09.03
【小白】openCV入门杂记(2)---认识openCV
-
什么 是 openCV
-
OpenCV于1999年由Intel建立,如今由Willow Garage提供支持。 OpenCV于1999年由Intel建立,如今由Willow Garage提供支持。
-
最新版本: 2017年8月3日,发布OpenCV 3.3版(最重要的更新是把DNN模块从contrib里面提到主仓库)
-
2018年7月4日,OpenCV3.4.2版本发布。
PS:BSD许可
BSD许可证原先是用在加州大学柏克利分校发表的各个4.4BSD/4.4BSD-Lite版本上面(BSD是Berkly Software Distribution的简写)的,后来也就逐渐沿用下来。1979年加州大学伯克利分校发布了BSD Unix,被称为开放源代码的先驱,BSD许可证就是随着BSD Unix发展起来的。BSD许可证现在被Apache和BSD操作系统等开源软件所采纳。
2018.09.03
【以下内容从https://www.cnblogs.com/coderwjq/p/6264607.html OpenCV入门学习笔记》中提取,侵删】
2.图像的基本操作
-
图像的表示
-
对于图像显示来说,目前大部分设备都是用无符号8位整数(CV_8U)表示像素亮度
-
-
在OpenCV中,RGB图像的通道顺序为BGR
-
Mat类
-
旧版本使用IpLImage和CvMat数据结构来表示图像
-
新加入的Mat类能够自动管理内存
PS:IPLImage
3.创建Mat对象
可以用来创建和操作多维矩阵,有多种方法创建一个Mat对象
-
构造函数方法
-
Mat M(3,2, CV_8UC3, Scalar(0,0,255));
-
创建一个行数(高度)为3,列数(宽度)为2的图像
-
图像元素是8位无符号整数类型,且有三个通道
-
图像的所有像素值被初始化为(0,0,255)
-
由于OpenCV中默认的颜色顺序为BGR,因此这是一个全红色的图像
-
Mat重新定义了<<操作符,使用这个操作符,可以方便地输出所有像素值,而不需要使用for循环逐个像素输出
-
-
这些构造函数中,很多都涉及到类型type,type可以是
-
CV_8UC1
-
8U表示8位无符号整数
-
-
CV_16SC1
-
16S表示16位有符号整数
-
-
CV_64FC4
-
64F表示64位浮点数(即double类型)
-
-
C后面表示通道数
-
例如C1表示一个通道的图像,C4表示4个通道的图像,以此类推
-
如果需要更多的通道数,需要使用宏CV_8UC(n)
-
-
-
4.像素值的读写
-
很多时候,我们需要读取某个像素值,或者设置某个像素值
-
在更多的时候,我们需要对整个图像里的所有像素进行遍历
-
OpenCV提供了多种方法来实现图像的遍历
-
at()函数
-
函数at()来实现读取矩阵中的某个像素,或者对某个像素进行赋值操作
-
uchar value = grayim.at
-
grayim.at
-
注意:如果要遍历图像,并不推荐使用at()函数
-
使用这个函数的有优点是代码的可读性高,但是效率并不是很高
-
-
使用迭代器
-
如果你熟悉C++的STL库,那一定了解迭代器(iterator)的使用
-
迭代器可以方便地遍历所有元素
-
Mat也增加了迭代器的支持,一般与矩阵元素的遍历
-
但是由于使用了迭代器,而不是使用行数和列数来遍历,所以就没有了i和j变量
-
-
使用数据指针
-
使用IpLImage结构的时候,我们会经常使用数据指针来直接操作像素
-
通过指针操作来访问像素是非常高效的,但是务必要十分小心
-
C/C++中的指针操作是不进行类型以及越界检查的,如果指针访问出错,程序运行时有时候可能看上去一切正常,有时候却突然弹出段错误(segment fault)
-
对于不熟悉指针的编程者,不推荐使用指针操作
-
如果你非常注重程序的运行速度,那么遍历像素时,建议使用指针
-
-
5、选取图像局部区域
-
Mat类提供了很多方便的方法来选择图像的局部区域
-
使用这些方法时需要注意,这些方法并不进行内存的复制操作
-
如果将局部区域赋值给新的Mat对象,新对象与原始对象共用相同的数据区域,不重新申请内存,因此这些方法的执行速度都比较快
-
单行或单列的选择
-
提取矩阵的一行或者一列可以使用函数row()或col()
-
函数的声明如下
-
Mat Mat::row(int i) const
-
Mat Mat::col(int j) const
-
-
-
用Range选择多行或多列
-
Range是OpenCV中新增的类,该类有两个关键变量start和end
-
Range对象可以用来表示矩阵的多个连续的行或者多个连续的列
-
其表示范围从start到end,包含start,但不包含end
-
Range类还提供了一个静态方法all(),这个方法的作用如同Matlab中的":",表示所有的行或者所有的列
-
// 创建一个单位阵
-
Mat A = Mat::eye(10, 10, CV_32S);
-
// 提取第 1 到 3 列(不包括 3)
-
Mat B = A(Range::all(), Range(1, 3));
-
// 提取 B 的第 5 至 9 行(不包括 9)
-
// 其实等价于 C = A(Range(5, 9), Range(1, 3)) Mat
-
C = B(Range(5, 9), Range::all());
-
-
感兴趣区域
-
从图像中提取感兴趣区域(Region of interest)有两种方法
-
使用构造函数
-
// 创建宽度为 320,高度为 240 的 3 通道图像
-
Mat img(Size(320,240),CV_8UC3);
-
// roi 是表示 img 中 Rect(10,10,100,100)区域的对象
-
Mat roi(img, Rect(10,10,100,100));
-
-
使用括号运算符
-
Mat roi2 = img(Rect(10,10,100,100));
-
-
也可以使用Range对象来定义感兴趣区域
-
// 使用括号运算符
-
Mat roi3 = img(Range(10,100),Range(10,100));
-
// 使用构造函数
-
Mat roi4(img, Range(10,100),Range(10,100));
-
-
-
取对角线元素
-
矩阵的对角线元素可以使用Mat类的diag()函数获取
-
该函数的定义如下
-
Mat Mat::diag(int d) const
-
当参数d=0时,表示取主对角线
-
当参数d>0时,表示取主对角线下方的次对角线
-
当参数d=1时,表示取主对角线下方,且紧贴主对角线的元素
-
当参数d<0时,表示取主对角线上方的次对角线
-
PS:
-
-
-
-
如同row()和col()函数,diag()函数也不进行内存复制操作,其复杂度也是O(1)
-
-
6.Mat表达式(相关标量)
-
利用C++中的运算符重载,OpenCV2中引入了Mat运算表达式
-
下面给出Mat表达式所支持的运算(下面列表中使用A和B表示Mat类型的对象,使用s表示Scalar对象即标量对象,alpha表示double值
-
加法,减法,取负:A+B,A-B,A+s,A-s,s+A,s-A,-A
-
缩放取值范围:A*alpha
-
矩阵对应元素的乘法和除法: A.mul(B),A/B,alpha/A
-
矩阵乘法:A*B (注意此处是矩阵乘法,而不是矩阵对应元素相乘)
-
矩阵转置:A.t()
-
矩阵求逆和求伪逆:A.inv()
-
矩阵比较运算:A cmpop B,A cmpop alpha,alpha cmpop A。此处 cmpop 可以是>,>=,==,!=,<=,<。如果条件成立,则结果矩阵(8U 类型矩 阵)的对应元素被置为 255;否则置 0。
-
矩阵位逻辑运算:A logicop B,A logicop s,s logicop A,~A,此处 logicop 可以是&,|和^。
-
矩阵对应元素的最大值和最小值:min(A, B),min(A, alpha),max(A, B), max(A, alpha)。
-
矩阵中元素的绝对值:abs(A)
-
叉积和点积:A.cross(B),A.dot(B)
-
7.Mat类的内存管理
-
Mat是一个类,由两个数据部分组成
-
矩阵头(包含矩阵尺寸/存储方法/存储地址等信息)
-
一个指向存储所有像素值的矩阵的指针
-
-
为了解决矩阵数据的传递,OpenCV使用了引用计数
-
思路:让每个Mat对象有自己的矩阵头信息,但多个Mat对象可以共享同一个矩阵数据
-
让矩阵指针指向同一地址而实现这一目的(应该是元素相同的矩阵,比如矩阵的复制传递)
-
很多函数以及很多操作(如函数参数传值)只复制矩阵头信息,而不复制矩阵数据
-
有很多种方法创建Mat类,如果Mat类自己申请数据空间,那么该类会多申请4个字节(int类型),多出的4个字节存储数据被引用的次数
-
引用次数存储于数据空间的后面,refcount指向这个位置
-
当计数等于0时,则释放该空间
-
8.Mat与ImlImage和CvMat的转换
-
如果要与以前的代码兼容,将会涉及到Mat与ImlImage和CvMat的转换
-
Mat转为ImlImage和CvMat格式(qiangzhi)
-
IplImage iplimg = img; //转为IplImage结构
-
CvMat cvimg = img; //转为CvMat结构
-
注意:类型转换后,ImlImage和CvMat与Mat公用同一矩阵数据,而ImlImage和CvMat没有引用计数功能,如果img中数据被释放,ImlImage和CvMat也就失去了数据,因此要牢记不可将Mat对象提前释放
-
-
ImlImage和CvMat格式转为Mat
-
Mat 类有两个构造函数,可以实现 IplImage 和 CvMat 到 Mat 的转换。
-
这两 个函数都有一个参数 copyData。
-
如果 copyData 的值是 false,那么 Mat 将与 IplImage 或 CvMat 共用同一矩阵数据;
-
如果值是 true,Mat 会新申请内存,然后将 IplImage 或 CvMat 的数据复制到 Mat 的数据区。
-
-
如果共用数据,Mat 也将不会使用引用计数来管理内存,需要开发者自己来管理。
-
建议做此转换是将参数置为 true,这样内存管理变得简单。
-
Mat::Mat(const CvMat* m, bool copyData=false)
-
Mat::Mat(const IplImage* img, bool copyData=false)
-
-
-----------------------------------------------------图片及视频的读写-----------------------------------------------------------
9.读写图像文件
-
将图像文件读入内存,可以使用imread()函数
-
将Mat对象以图像文件格式写入内存,可以使用imwrite()函数
-
读图像文件
-
imread()函数返回的是Mat对象
-
如果读取文件失败,则会返回一个空矩阵,即Mat::data的值是NULL
-
执行imread()之后,需要检查文件是否成功读入,可以使用Mat::empty()函数进行检查
-
-
imread()函数的声明如下
-
Mat imread(const string& filename, int flags=1 )
-
filename是被读取或保存的图像文件名
-
在imread()函数中,flag参数值有三种情况
-
flag>0,该函数返回3通道图像,如果磁盘上的图像文件是单通道的灰度图像,则会被强制转为3通道
-
flag=0,该函数返回单通道图像,如果磁盘的图像文件是多通道图像,则会被强制转为单通道
-
flag<0,该函数不会对图像进行通道转换
-
-
-
imread()函数支持多种文件格式,且该函数是根据图像文件的内容来确定文件格式,而不是根据文件的扩展名来确定
-
所支持的格式文件如下:
-
Windows 位图文件 - BMP, DIB;
-
JPEG 文件 - JPEG, JPG, JPE;
-
便携式网络图片 - PNG;
-
便携式图像格式 - PBM,PGM,PPM;
-
Sun rasters - SR,RAS;
-
TIFF 文件 - TIFF,TIF;
-
TIFF 文件 - TIFF,TIF;
-
JPEG 2000 图片- jp2。
-
-
所安装的OpenCV并不一定能支持上述所有格式,文件格式的支持需要特定的库,只有在编译OpenCV添加了响应的文件格式库,才可支持其格式
-
-
写图像文件
-
将图像写入文件,可以使用imwrite()函数,该函数的声明如下:
-
bool imwrite(const string& filename, InputArray image, const vector
-
-
文件的格式由filename参数指定的文件扩展名确定
-
推荐使用PNG文件格式
-
BMP格式是无损格式,但是一般不进行压缩,文件尺寸非常大
-
JPEG格式的文件较小,但是JPEG是有损压缩,会丢失一些信息
-
PNG是无损压缩格式,推荐使用
-
-
imwrite()函数的第三个参数params可以指定文件格式的一些细节信息,这个参数里面的数值是跟文件格式相关的:
-
JPEG:表示图像的质量,取值范围从0到100,数值越大表示图像质量越高,当然文件也越大,默认值是95
-
PNG:表示压缩级别,取值范围是从0到9,数值越大表示文件越小,但是压缩花费的时间也越长,默认值是3
-
PPM/PGM/PBM:表示文件是以二进制还是纯文本方式存储,取值为0或1,如果取值为1,则表示以二进制方式存储,默认值是1
-
-
并不是所有Mat对象都可以村委图像文件,目前支持的格式只有8U类型的单通道和3通道(颜色顺序为BGR)矩阵
-
如果需要保存16U格式图像,只能使用PNG/JPEG2000/TIFF格式
-
如果希望将其他格式的矩阵保存为图像文件,可以现用Mat::convertTo()函数或者cvtColor()函数将矩阵转为可以保存的格式
-
另外需要注意的是,在保存文件时,如果文件已经存在,imwrite()函数不会进行提醒,将直接覆盖掉以前的文件
-
10.读写视频
-
在介绍OpenCV读写视频之前,先介绍一下编解码器(codec)
-
视频的格式主要由压缩算法决定,压缩算法称之为编码器(coder),解压缩算法称之为解码器(decoder),编解码算法可以统称为编解码器(codec)
-
视频文件能读或写,关键看是否有相应的编解码器,编解码器的种类非常多,常用的有MJPG/XVID/DIVX等,完整列表请参考FOURCC网站,因此视频文件的扩展名往往只能表示这是一个视频文件
-
OpenCV2中提供了两个类来实现视频的读写
-
读视频的类是VideoCapture
-
写视频的类是VideoWriter
-
-
-
读视频
-
VideoCapture既可以从视频文件读取图像,也可以从摄像头读取图像
-
可以使用该类的构造函数打开视频文件或者摄像头
-
如果VideoCapture对象已经创建,也可以使用VideoCapture::open()打开,VideoCapture::open()函数会自动调用VideoCapture::release()函数,先释放已经打开的视频,然后再打开新视频
-
如果要读一帧,可以使用VideoCapture::read()函数
-
VideoCapture类重载了>>操作符,实现了读视频帧的功能
-
-
-
写视频
-
使用OpenCV创建视频也非常简单,与读视频不同的是,需要在创建视频时设置一系列参数,包括:
-
文件名
-
编解码器:编解码器使用四个字符表示
-
CV_FOURCC('M','J','P','G')
-
CV_FOURCC('X','V','I','D')
-
CV_FOURCC('D','I','V','X')
-
如果使用某种编解码器无法创建视频文件,请尝试其他的编解码器
-
-
帧率
-
宽度
-
高度
-
-
将图像写入视频可以使用VideoWriter:write()函数,VideoWrite类中也重载了<<操作符,使用起来非常方便
-
-----------------------------------------------------图片及视频的读写-----------------------------------------------------------