OpenCV入门学习笔记

时间:2023-06-24 13:56:08

OpenCV入门学习笔记

一.简介

  • OpenCV(Open Source Computer Vision),开源计算机视觉库
  • 提供了很多函数,实现了很多计算机视觉算法,算法从最基本的滤波到高级的物体检测皆有涵盖
  • 学习OpenCV所需要的基本知识
    • C/C++编程基础(编程能力)
    • 了解算法原理(理论基础知识)
  • 提升理论基础知识,所要了解的课程
    • 数字图像处理
    • 计算机视觉
    • 模式识别
  • OpenCV知识一个算法库,我们并不需要完全精通算法原理之后才去使用,只需要了解它的功能,就可以动手操作了

二.预备知识

1.编程的流程

  • 编辑(edit)
    • 编辑即编写代码,是编程的第一步
    • 可以使用任意编辑器编写代码,但是为了方便,推荐使用功能丰富的编辑器
  • 编译(compile)
    • 编译是将某种编程语言写成的源代码,转换成目标文件
    • 目标文件包含
      • 机器代码(可以直接被计算机CPU执行)
      • 代码在运行时使用的数据
    • 编译器(compiler)是实现这一目的的软件
      • Windows下的有cl.exe
      • Linux下有gcc或g++
  • 连接(link)
    • 连接是将多个目标文件,以及库文件生成可执行文件(或静态库/动态库)的过程
    • 连接器(linker)是实现这一目的的软件
    • 常用的连接器有
      • Windows下的link.exe
      • Linux下的ld等
  • 运行(run)
  • Visual C++(IDE)
    • 集成开发环境(Integrated Development Environment)可以帮助开发者对项目进行管理
  • 头文件
    • 在存在多个源文件的情况下,一个源文件中的函数要想调用另外一个源文件中的函数
    • 在编译阶段,由于编译器是对单个文件进行编译,编译器也不知道是否存在那个函数可以调用,以及调用的方式是否正确,因此就需要借助头文件中的函数声明来判断
  • 库文件
    • 库文件中包含一系列的子程序,库文件是二进制的,在库文件中是看不到原始的源代码的
    • 库文件和可执行文件的区别是,库不是独立程序,是向其他程序提供服务的代码
    • 使用库文件的好处
      • 对源代码进行保密
      • 减少重复编译的时间,增强程序的模块化
    • 将库文件连接到程序中的两种方式
      • 静态链接库
      • 动态链接库

2.OpenCV是什么

  • OpenCV其实就是一堆C和C++语言的源代码文件,这些源代码文件中实现了许多常用的计算机视觉算法

3.其他知识

  • 命令行参数
    • int main(int argc, char** argv)
    • int main(int argc, char* argv[])
      • argc表示命令行输入参数的个数(以空白符分隔)
      • argv存储了所有的命令行参数
    • 例如:hello.exe Jiaqi Wang
      • argc的值是3
      • argv[0]是"hello.exe"
      • argv[1]是"Jiaqi"
      • argv[2]是"Wang"
  • 常见编译错误
    • 出现编译错误后,需要做的第一件事就是阅读出错信息
    • 出错信息虽然看似凌乱,但是能够提供很多有价值的信息,帮助开发者解决问题
  • 常见编译错误1:找不到头文件
    • 找不到头文件一般有两个原因
      • 头文件的文件名拼写错误
      • 未将头文件所在的路径添加到开发环境中
    • 如果文件名拼写正确,编译器还是找不到头文件,则需要将头文件所在路径添加到相应的变量中
  • 常见编译错误2:拼写错误
    • 在编程中,拼写错误也是一类常见错误
    • 如果检查后发现不是拼写错误,可能的原因是声明函数的头文件未使用include语句包含到源文件中
    • 如果源代码不符合语法规则,也会造成编译错误
  • 常见链接错误
    • 如果代码符合语法规则,则会通过编译过程
    • 编译完所有源代码之后,下一步是连接目标文件,以形成可执行文件
    • 编译通过以后,在连接时出错,可能是没有导入依赖的库文件,故找不到对应的方法实现
    • 需要将依赖的库文件添加到项目设置中
  • 运行时错误
    • 经过编译和连接的过程,生成了可执行文件,在运行这个可执行文件所产生的错误是运行时错误
    • 比较常见的运行时错误是内存错误
    • 在程序编写中,对于数组和指针等,要特别的小心
    • 因为对于空指针以及数组越界等问题,编译器无法在编译时给出错误提示
    • 这类错误一旦在运行时发生,排除起来非常困难

三.OpenCV介绍

  • OpenCV的全称是Open Source Computer Vision Library,是一个开放源代码的计算机视觉库
  • OpenCV最初由英特尔公司发起并开发,以BSD许可证授权发行,可以在商业和研究领域中免费使用,现在美国Willow Garage为OpenCV提供主要的支持
  • OpenCV可用于开发实时的图像处理,计算机视觉以及模式识别程序,目前在工业界以及科研领域广泛采用

1.OpenCV的来源

  • 诞生于Intel
    • 初衷:提供一个计算机视觉库,使之充分发掘CPU的计算能力,促进Intel产品的销售
    • 最初开发是由Intel在俄罗斯的团队实现
  • 2008年Willow Garage开始大力支持OpenCV
    • Willow Garage是一家机器人公司
    • 致力于为个人机器人开发开放的硬件平台和软件
    • 现已开发PR2机器人,并支持ROS/OpenCV/PCL等软件
    • ROS(Robot Operating System)是用于机器人的操作系统,是一个开发源代码的软件,OpenCV作为ROS的视觉模块嵌入

2.OpenCV的协议

  • OpenCV采用BSD协议,这是一个非常宽松的协议
    • 用户可以修改OpenCV的源代码
    • 可以将OpenCV嵌入到自己的软件中
    • 可以将包含OpenCV的软件销售,可以用于商业产品
    • 也可以用于科研领域
    • BSD协议并不具有"传染性"
      • 如果在软件中使用OpenCV,你不需要公开代码
      • 你可以对OpenCV做任何操作
    • 协议对用户的唯一约束就是
      • 要在软件的文档或者说明中注明使用OpenCV,并附上OpenCV的协议
  • OpenCV的协议保证了计算机视觉技术快速的传播,让更多的人从OpenCV受益

四.图像的基本操作

1.图像的表示

  • 基本知识
    • 人眼看到的图像,在计算机看来,只是一堆亮度各异的点
    • 一副尺寸为M × N的图像,可以用一个M × N的矩阵来表示
    • 矩阵元素的值表示这个位置上的像素的亮度
    • 一般来说,像素值越大表示该点越亮
  • 灰度图和彩色图像
    • 一般来说,灰度图用2维矩阵表示(M × N)
    • 彩色(多通道)图像用3维矩阵表示(M × N × 3)
    • 对于图像显示来说,目前大部分设备都是用无符号8位整数(CV_8U)表示像素亮度
    • 图像数据在计算机内存中的存储顺序为以图像最左上点(也可能最左下点)开始
    • 如果是多通道图像,比如RGB图像,则每个像素用三个字节表示
    • 在OpenCV中,RGB图像的通道顺序为BGR

2.Mat类

  • 早期的OpenCV
    • 使用IpLImage和CvMat数据结构来表示图像
    • IpLImage和CvMat都是C语言的结构
    • 使用这两个结构的问题是内训需要手动管理
      • 开发者必须清楚何时需要申请内存,何时需要释放内存
      • 为开发者带来了一定的负担,开发者应该将更多精力用于算法设计
      • 因此,新版本的OpenCV中引入了Mat类
  • 新版的OpenCV
    • 新加入的Mat类能够自动管理内存
    • 优点:
      • 不需要花费大量精力在内存管理上
      • 代码会变得很简洁,代码行数会变少
    • 缺点:
      • 使用C++接口在一些嵌入式开发系统中可能只支持C语言
      • 如果开发平台支持C++,完全没必要再用IpLImage和CvMat
    • 在新版本的OpenCV中,开发者依然可以使用IpLImage和CvMat
    • 但是一些新增的函数只提供了Mat接口
class CV_EXPORTS Mat
{
public:
//一系列函数 ...
/* flag参数中包含许多关于矩阵的信息,如:
-Mat 的标识
-数据是否连续
-深度
-通道数目
*/
int flags;
//矩阵的维数,取值应该大于或等于 2
int dims;
//矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
int rows, cols;
//指向数据的指针
uchar* data;
//指向引用计数的指针
//如果数据是由用户分配的,则为 NULL
int* refcount;
//其他成员变量和成员函数
...
};

3.创建Mat对象

  • Mat是一个非常优秀的图像类,它同时也是一个通用的矩阵类
  • 可以用来创建和操作多维矩阵,有多种方法创建一个Mat对象
    • 构造函数方法
      • Mat M(3,2, CV_8UC3, Scalar(0,0,255));
      • 创建一个行数(高度)为3,列数(宽度)为2的图像
      • 图像元素是8位无符号整数类型,且有三个通道
      • 图像的所有像素值被初始化为(0,0,255)
      • 由于OpenCV中默认的颜色顺序为BGR,因此这是一个全红色的图像
      • Mat重新定义了<<操作符,使用这个操作符,可以方便地输出所有像素值,而不需要使用for循环逐个像素输出
    • 常用的构造方法有("::"表示作用域和所属关系)
      • Mat::Mat()
        • 无参数构造方法
      • Mat::Mat(int rows, int cols, int type)
        • 创建行数为rows,列数为cols,类型为type的图像
      • Mat::Mat(Size size, int type)
        • 创建大小为size,类型为type的图像
      • Mat::Mat(int rows, int cols, int type, const Scalar& s)
        • 创建行数为rows,列数为cols,类型为type的图像,并将所有元素初始化为值s
      • Mat::Mat(Size size, int type, const Scalar& s)
        • 创建大小为size,类型为type的图像,并将所有元素初始化为值s
      • Mat::Mat(const Mat& m)
        • 将m赋值给新创建的对象,此处不会对图像数据进行复制,m和新对象公用图像数据
      • Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
        • 创建行数为rows,列数为cols,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由step指定
      • Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
        • 创建大小为size,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由step指定
      • Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
        • 创建新图像为m的一部分,具体的范围由rowRange和colRange指定,此构造函数也不进行图像数据的复制操作,新图像与m公用图像数据
      • Mat::Mat(const Mat& m, const Rect& roi)
        • 创建的新图像为m的一部分,具体的范围由roi指定,此构造函数也不进行图像数据的复制操作,新图像与m公用图像数据
    • 这些构造函数中,很多都涉及到类型type,type可以是
      • CV_8UC1
        • 8U表示8位无符号整数
      • CV_16SC1
        • 16S表示16位有符号整数
      • CV_64FC4
        • 64F表示64位浮点数(即double类型)
      • C后面表示通道数
        • 例如C1表示一个通道的图像,C4表示4个通道的图像,以此类推
        • 如果需要更多的通道数,需要使用宏CV_8UC(n)

4.使用create()函数创建对象

  • 除了在构造函数中可以创建图像,也可以使用Mat类的create()函数创建图像
  • 如果create()函数指定的参数与图像之前的参数相同,则不进行是指的内存申请操作
  • 如果参数不同,则减少原始数据内存的索引,并重新申请内存
  • 需要注意的是,使用create()函数无法设置图像像素的初始值

5.Matlab风格的创建对象方法

  • OpenCV2中提供了Matlab风格的函数
  • 如zeros(),ones(),eyes()
  • 这种方法使得代码非常简洁,使用起来也非常方便
  • 使用这些函数需要指定图像的大小和类型
    • Mat Z = Mat::zeros(2,3, CV_8UC1);
    • Mat O = Mat::ones(2, 3, CV_32F);
    • Mat E = Mat::eye(2, 3, CV_64F);
    • 该代码中,有些type参数如CV_32F未注明通道数目,这种情况下它表示单通道

6.矩阵的基本元素表达

  • 单通道图像
    • 对于单通道图像,其元素类型一般为8U(即8位无符号整数)
    • 当然也可以是16S/32F等,这些类型可以直接用uchar/short/float等C/C++语言中的基本数据类型表达
  • 多通道图像
    • 对于多通道图像,如RGB彩色图像,需要用三个通道来表示
    • 在这种情况下,如果依然将图像视作一个二维矩阵,那么矩阵的元素不再是基本的数据类型
  • OpenCV中有模板类Vec,可以表示一个向量
  • OpenCV中使用Vec类预定义了一些小向量,可以将之用于矩阵元素的表达
    • typedef Vec<uchar, 2> Vec2b;
    • typedef Vec<short, 2> Vec2s;
    • typedef Vec<int, 2> Vec2i;
    • typedef Vec<float, 2> Vec2f;
    • typedef Vec<double, 2> Vec2d;
  • 例如8U类型的RGB彩色图像可以使用Vec3b,三通道float类型的矩阵可以使用Vec3f
  • 对于Vec对象,可以使用[]符号如操作数组般读写其元素,如:
    • Vec3b color; // color变量描述一种RGB颜色
    • color[0] = 255; // B分量
    • color[1] = 0; // G分量
    • color[2] = 0; //R分量

7.像素值的读写

  • 很多时候,我们需要读取某个像素值,或者设置某个像素值
  • 在更多的时候,我们需要对整个图像里的所有像素进行遍历
  • OpenCV提供了多种方法来实现图像的遍历
    • at()函数
      • 函数at()来实现读取矩阵中的某个像素,或者对某个像素进行赋值操作
      • uchar value = grayim.at(i,j);//读出第i行第j列像素值
      • grayim.at(i,j)=128; //将第i行第j列像素值设置为128
      • 注意:如果要遍历图像,并不推荐使用at()函数
      • 使用这个函数的有优点是代码的可读性高,但是效率并不是很高
    • 使用迭代器
      • 如果你熟悉C++的STL库,那一定了解迭代器(iterator)的使用
      • 迭代器可以方便地遍历所有元素
      • Mat也增加了迭代器的支持,一般与矩阵元素的遍历
      • 但是由于使用了迭代器,而不是使用行数和列数来遍历,所以就没有了i和j变量
    • 使用数据指针
      • 使用IpLImage结构的时候,我们会经常使用数据指针来直接操作像素
      • 通过指针操作来访问像素是非常高效的,但是务必要十分小心
      • C/C++中的指针操作是不进行类型以及越界检查的,如果指针访问出错,程序运行时有时候可能看上去一切正常,有时候却突然弹出段错误(segment fault)
      • 对于不熟悉指针的编程者,不推荐使用指针操作
      • 如果你非常注重程序的运行速度,那么遍历像素时,建议使用指针

8.选取图像局部区域

  • 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时,表示取主对角线上方的次对角线
        • 如同row()和col()函数,diag()函数也不进行内存复制操作,其复杂度也是O(1)

9.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)

10.Mat_类

  • Mat_类是对Mat类的一个包装
    • 在包装中,只定义了几个方法,没有定义新的属性
    • 如果使用Mat_类,可以在变量声明时确定元素的类型,访问元素时不再需要指定元素类型,既能使代码简洁,又能减少出错的可能性

11.Mat类的内存管理

  • 虽然使用Mat类时,内存管理变得简单,但是如果清楚了解Mat类的内存管理,会更清楚一些函数到底操作了哪些数据
  • Mat是一个类,由两个数据部分组成
    • 矩阵头(包含矩阵尺寸/存储方法/存储地址等信息)
    • 一个指向存储所有像素值的矩阵的指针
  • 矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级
  • 复制矩阵数据往往花费较多时间,因此除非有必要,不要复制大的矩阵
  • 为了解决矩阵数据的传递,OpenCV使用了引用计数
    • 思路:让每个Mat对象有自己的矩阵头信息,但多个Mat对象可以共享同一个矩阵数据
    • 让矩阵指针指向同一地址而实现这一目的
    • 很多函数以及很多操作(如函数参数传值)只复制矩阵头信息,而不复制矩阵数据
    • 有很多种方法创建Mat类,如果Mat类自己申请数据空间,那么该类会多申请4个字节(int类型),多出的4个字节存储数据被引用的次数
    • 引用次数存储于数据空间的后面,refcount指向这个位置
    • 当计数等于0时,则释放该空间

12.输出

  • Mat类重载了<<操作符,可以方便的使用流操作来输出矩阵的内容
  • 默认情况下,输出的格式是类似Matlab中矩阵的输出格式
  • 除了默认格式,Mat也支持其他的输出格式
    • Python格式
    • csv格式(以逗号分隔)
    • numpy格式
    • C语言格式
  • 除了Mat对象可以使用<<符号输出,其他的很多类型也支持<<输出
    • 二维点
    • 三维点

13.Mat与ImlImage和CvMat的转换

  • 虽然OpenCV2引入了方便的Mat类,出于兼容性考虑,OpenCV依然是支持C语言接口的ImlImage和CvMat结构
  • 如果要与以前的代码兼容,将会涉及到Mat与ImlImage和CvMat的转换
    • Mat转为ImlImage和CvMat格式
      • 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)

五.数据获取与存储

1.读写图像文件

  • 将图像文件读入内存,可以使用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& params=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()函数不会进行提醒,将直接覆盖掉以前的文件

2.读写视频

  • 在介绍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类中也重载了<<操作符,使用起来非常方便
    • 另外需要注意:待写入的图像尺寸必须与创建视频时指定的尺寸一致