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

OpenCV入门学习笔记的更多相关文章

  1. Android NDK开发及OpenCV初步学习笔记

    https://www.jianshu.com/p/c29bb20908da Android NDK开发及OpenCV初步学习笔记 Super_圣代 关注 2017.08.19 00:55* 字数 6 ...

  2. OpenCV图像处理学习笔记-Day1

    OpenCV图像处理学习笔记-Day1 目录 OpenCV图像处理学习笔记-Day1 第1课:图像读入.显示和保存 1. 读入图像 2. 显示图像 3. 保存图像 第2课:图像处理入门基础 1. 基本 ...

  3. Hadoop入门学习笔记---part4

    紧接着<Hadoop入门学习笔记---part3>中的继续了解如何用java在程序中操作HDFS. 众所周知,对文件的操作无非是创建,查看,下载,删除.下面我们就开始应用java程序进行操 ...

  4. Hadoop入门学习笔记---part3

    2015年元旦,好好学习,天天向上.良好的开端是成功的一半,任何学习都不能中断,只有坚持才会出结果.继续学习Hadoop.冰冻三尺,非一日之寒! 经过Hadoop的伪分布集群环境的搭建,基本对Hado ...

  5. PyQt4入门学习笔记(三)

    # PyQt4入门学习笔记(三) PyQt4内的布局 布局方式是我们控制我们的GUI页面内各个控件的排放位置的.我们可以通过两种基本方式来控制: 1.绝对位置 2.layout类 绝对位置 这种方式要 ...

  6. PyQt4入门学习笔记(一)

    PyQt4入门学习笔记(一) 一直没有找到什么好的pyqt4的教程,偶然在google上搜到一篇不错的入门文档,翻译过来,留以后再复习. 原始链接如下: http://zetcode.com/gui/ ...

  7. Hadoop入门学习笔记---part2

    在<Hadoop入门学习笔记---part1>中感觉自己虽然总结的比较详细,但是始终感觉有点凌乱.不够系统化,不够简洁.经过自己的推敲和总结,现在在此处概括性的总结一下,认为在准备搭建ha ...

  8. Hadoop入门学习笔记---part1

    随着毕业设计的进行,大学四年正式进入尾声.任你玩四年的大学的最后一次作业最后在激烈的选题中尘埃落定.无论选择了怎样的选题,无论最后的结果是怎样的,对于大学里面的这最后一份作业,也希望自己能够尽心尽力, ...

  9. Scala入门学习笔记三--数组使用

    前言 本篇主要讲Scala的Array.BufferArray.List,更多教程请参考:Scala教程 本篇知识点概括 若长度固定则使用Array,若长度可能有 变化则使用ArrayBuffer 提 ...

随机推荐

  1. 191. Number of 1 Bits

    题目: Write a function that takes an unsigned integer and returns the number of ’1' bits it has (also ...

  2. CVE爬虫抓取漏洞URL

    String url1="http://www.cnnvd.org.cn/vulnerability/index/vulcode2/tomcat/vulcode/tomcat/cnnvdid ...

  3. Django用户认证系统(二)Web请求中的认证

    在每个Web请求中都提供一个 request.user 属性来表示当前用户.如果当前用户未登录,则该属性为AnonymousUser的一个实例,反之,则是一个User实例. 你可以通过is_authe ...

  4. matlab 怎么保存plot的图 到指定文件夹

    %%使用print函数,第一个参数一定是figure的句柄,第二个参数设置格式,第三个参数是指定文件夹 %代码如下 h=figure; plot(1:10); print(h,'-djpeg','F: ...

  5. Hibernate征途(五)之继承映射和组件映射

    之所以把这两种映射放到一起说,是因为二者都是以复用为目的,减少了代码和配置量,这是相同点:二者之间的不同点类似继承和实现的区别:继承的类是一个事物的抽象,而实现的接口仅仅是功能的抽象. 继承映射 如上 ...

  6. poj 3414 Pots ( bfs )

    题目:http://poj.org/problem?id=3414 题意:给出了两个瓶子的容量A,B, 以及一个目标水量C, 对A.B可以有如下操作: FILL(i)        fill the ...

  7. CodeForces Round #287 Div.2

    A. Amr and Music (贪心) 水题,没能秒切,略尴尬. #include <cstdio> #include <algorithm> using namespac ...

  8. 初次接触Object对象变量

    Object 变量存储为 32 位(4 个字节)的地址形式,其为对象的引用.利用 Set 语句,声明为 Object 的变量可以赋值为任何对象的引用. 第一次注意到还有个数据类型,帮助文件里只有上面这 ...

  9. 【原】Storm 消息处理保障机制

    Storm入门教程 1. Storm基础 Storm Storm主要特点 Storm基本概念 Storm调度器 Storm配置 Guaranteeing Message Processing(消息处理 ...

  10. setup.s

    INITSEG = 0x9000 ! we move boot here - out of the way ! 原来 bootsect 所处的段. ! ok, the read went well s ...