学习OpenCV系列博客
上一章: 《学习OpenCV3》第2章 OpenCV初探
下一章: 《学习OpenCV3》第4章 图像和大型数组类型(持续更新)
《学习OpenCV3》第3章 了解OpenCV的数据类型
1. OpenCV数据类型分类
1.1 基础数据类型分类:
- 第一类是直接从C++继承的基础数据类型,比如 int,float 等。这些类型包括简单的数组和矩阵,同时也代表一些简单的几何概念,比如 点, 矩形,大小 等;
- 第二类是辅助对象。这些对象代表更抽象的概念,比如 垃圾收集指针类,用于数据切片的范围对象 range objects, 以及 抽象的终止条件类等
- 第三类可以称为大型数组类型。这些对象原本目的是涵盖数组或一些其他的原语、程序集或更常见的基础数据类型。这一类的典型代表是 Mat 类,该类用来表示任意维度的包含任意基础元素的数组。存储图像是 Mat 类的特殊用途,这个类还涵盖其他类型,比如稀疏矩阵 SparseMat ,它更适用于直方图这样的稀疏数据。
- 除了这些类型,OpenCV还使用了很多标准模板库(STL)。OpenCV相当依赖 vector 类, 许多OpenCV库函数在参数列表中有vector模板类。
1.2 基础类型概述
- Vec<> 是固定向量类,它是用来处理小型响亮的。
- Matx<> 是固定矩阵类(注意不是 Mat) ,它并不是用于大型数组的,而是用于一些特定的小型矩阵操作。固定矩阵类的维度在编译之前必须已知,这使得它的运算效率高,同时消除了许多动态内存分配操作。
2. 深入了解基础类型
2.1 Point类
- Point类的实现基于一个基础模板结构,所以它可以成为任何类型的点,如整型和浮点型。这里有两个模板,一个是二维点,一个是三维点。Point类的巨大优势就是简单并且开销小。它也可以转换成更广义的类型,比如在需要的时候可以转换为固定向量类或者是固定矩阵类。
- Point 类通过 Point2i , Point3f 这类别名来实例化。 最后一个字母的含义是:b是无符号字符,s是短整型,i是32位整型,f是32位浮点数,d是64位浮点数。
- 直接受Point类支持的操作
操作 | 示例 |
---|---|
默认构造函数 | Point2i p |
复制构造函数 | Point3f p2(p1) |
值构造函数 | Point2i(x0,x1) |
构造成固定向量类 | (Vec3f) p |
成员访问 | p.x, p.y |
点乘 | float x = p1.dot(p2) |
双精度点乘 | double x = p1.ddot(p2) |
叉乘 | p1.cross(p2) 针对三维点 |
判断一个点p是否在矩形r内 | p.inside( r ) 针对二维点 |
2.2 Scalar类
- Scalar是四维点类。
操作 | 示例 |
---|---|
默认构造函数 | Scalar s |
复制构造函数 | Scalar s2(s1) |
值构造函数 | Scalar s(x0) ; Scalar s(x0,x1,x2,x3) |
构造成固定向量类 | (Vec3f) p |
元素相乘 | s1.mul(s2) |
(四元数)共轭 | s.conj(); // return Scalar(s0, -s1, -s2, -s3) |
(四元数)真值测试 | s.isReal(); // return true, if s0 == s1== s2 ==s3) |
2.3 size类
- size类在实际操作时与Point类似,而且可以与其相互转换。这两者之间主要的区别在于Point类的数据成员是x和y,而size类的成员是width和height。
- size类的三个别名是 Size, Size2i, Size2f,前面两个是等价的。
- size类不支持转换为固定向量类,这一点与Point类不同。但是Point类和固定向量类都可以转成size类
操作 | 示例 |
---|---|
默认构造函数 | Size sz, Size2i sz, Size2f sz |
复制构造函数 | Size sz2(sz1) |
值构造函数 | Size sz(w,h) |
成员访问 | sz.width, sz.height |
计算面积 | sz.area() |
2.4 Rect类
- Rect类包含Point类的成员x和y(矩形左上角)和size类的成员width和height(代表了矩形的大小)。然而矩形类并不是从Point类或size类继承过来的,所以它并没有从它们那里继承操作。
操作 | 示例 |
---|---|
默认构造函数 | Rect r |
复制构造函数 | Rect r2(r1)) |
值构造函数 | Rect(x,y,w,h) |
由左上角点和大小 | Rect(p, sz) |
由两个对角构造 | Rect(p1, p2) |
成员访问 | r.x; r.y; r.width; r.height |
计算面积 | r.area() |
提取左上角 | r.tl() |
提取右下角 | r.br() |
判断点是否在矩形内 | r.contains( p) |
- Rect对象的覆写操作符
操作 | 示例 |
---|---|
矩形r1和矩形r2的交集 | Rect r3 = r1 & r2 |
同时能包含矩形r1和矩形r2的最小矩形 | Rect r3 = r1丨r2 ; r1丨= r2 |
矩形平移 | Rect r = r + p |
矩形扩大 | Rect r = r + sz |
矩形是否相等 | bool eq = (r1 == r2); bool ne = (r1 != r2) |
2.5 RotatedRect类
- RotatedRect类包含一个中心点Point2f、一个尺寸Size2f和一个额外的角度float。角度代表图形绕中心旋转的角度,需要注意的是RotatedRect是以中心为原点的。
操作 | 示例 |
---|---|
默认构造函数 | RotatedRect rr |
复制构造函数 | RotatedRect rr2(rr1)) |
三个点构造(按顺序,如果无法构成矩形会报错) | RotatedRect(p1, p2, p3)) |
值构造函数,需要一个点、一个尺寸、一个角度 | RotatedRect rr(p, sz, theta) |
成员访问 | rr.center, rr.size, rr.angle |
返回四个角的列表 | rr.points(pts[4]) |
2.6 固定矩阵类
- 固定矩阵类在编译时就已知维度,它内部的所有数据都是在堆栈上分配的,分配和清除都很快。
操作 | 示例 |
---|---|
默认构造函数 | Matx33f m33f; Matx43d m43d |
复制构造函数 | Matx22d m2dd(n22d) |
值构造函数 | Matx21f m(x0, x1); Matx4d m(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 ) |
含相同元素的矩阵 | m33f = Matx33f::all(x) ;所有元素都是x |
全0矩阵 | m23d = Matx23d::zeros() |
全1矩阵 | m16f = Matx16f ::ones() |
单位矩阵 | m33f = Matx33f.:eye() |
创建一个可以容纳另一个矩阵对角线的矩阵 | m31f = Matx33f::diag() |
创建一个均匀分布的矩阵 | m33f = Matx33f::randu(min, max) |
创建一个正态分布的矩阵 | m33f = Matx33f::nrandn(mean, variance) |
成员访问 | m(i, j), m(i) |
矩阵代数运算 | m1 = m0; mo*m1; m0+m1; m0-m1 |
标量与矩阵运算 | m * a; a * m; m/a |
比较 | m1==m2; m1!=m2 |
点积 | m1.dot(m2) |
点积 | m1.ddot(m2) ; 双精度 |
改变矩阵形状 | m91f = m33f.reshape<9,1>() |
变换操作符 | m44f=(Matx44f)m44d |
提取(i,j)处的2*2子矩阵 | m44f.get_minor<2,2>(i, j) |
提取第i行 | m14f=m44f.row(i) |
提取第j列 | m41f=m44f.col(j) |
提取矩阵对角线 | m41f=m44f.diag() |
转置矩阵 | n44f = m44f.t() |
逆矩阵 | n44f = m44f.inv(method) |
解线性系统 | m31f = m33f.solve( rhs31f, method) |
m31f = m33f.solve<2>( rhs32f, method) | |
每个元素的乘法 | m1.mul(m2) |
2.7 固定向量类
- 固定向量类是从固定矩阵类派生出来的,只是为了更方便的使用Matx<>,可以说 Vec是列为1的Matx<>
操作 | 示例 |
---|---|
默认构造函数 | Vec2s v2s; Vec6f v6f |
复制构造函数 | Vec3f u3f(v3f) |
值构造函数 | Vec2f v2f(x0,x1) |
成员访问 | v4f[i]; v3w(j) |
向量叉乘 | v3f.cross(u3f) |
2.8 复数类
- OpenCV的复数类与STL复数类模板complex<>不一样,但是与之兼容,可以相互转换。它们最大的区别在于成员获取,在STL类中,虚部和实部是通过成员函数real()和imag()获取的,而OpenCV中,直接通过成员变量re和im获取。
操作 | 示例 |
---|---|
默认构造函数 | Complexf z1; Complexd z2 |
复制构造函数 | Complexf z2(z1) |
Complexf u2f(v2f) | |
值构造函数 | Complexd z1(re0); Complexd (re0, im1) |
成员访问 | z1.re; z1.im |
共轭复数 | z2 = z1.conj() |
3. 辅助对象
3.1 TermCriteria类
-
很多算法需要一个终止条件以确定何时退出。通常,终止条件的形式要么是达到允许的有限迭代次数(称为COUNT 或 MAX_ITER),要么是某种形式的误差参数(如果接近于如此程度,就可以退出,称为 EPS,即 epsilon 的简称)。
-
它有三个公有成员变量(type, maxCount, epsilon)。
-
构造函数TermCriteria(int type, int maxCount, double epsilon).
-
type设置为 TermCriteria::COUNT 或 TermCriteria::EPS ,也可以把这两个条件并在一起:TermCriteria::COUNT | TermCriteria::EPS.
-
COUNT 与 MAX_ITER是等价的,使用哪个看你心情。
-
需要注意的是 type 参数必须进行相应的设置才可以继续使用 maxCount 或 EPS 的。
TermCriteria criteria(TermCriteria::MAX_ITER | TermCriteria::EPS, 10, 0.01); meanShift(backprojection, rect, criteria);
3.2 Range类
- Range 类用于确定一个连续的整数序列。它有两个元素 start 和 end .
- 在构造函数中设定: Range( int start, int end ).
- Range范围包含初始值,但不包含终止值。例如:Range rng(0, 4) 包含 0, 1, 2, 3.
- 使用 size() 可以得到 Range 类的元素数量。
- 使用 empty() 测试 range 是否为空。
- 使用 all() 获得 range 的可用范围。
Range rng = Range(0, 4);
cout << rng.size() << endl;
cout << rng.empty() << endl;
cout << rng.all() << endl;
3.3 Ptr模板和垃圾收集
3.3.1 智能指针如何工作
-
智能指针(Smart Pointer)是C++中一个非常有用的类型。这个指针允许我们创建一个对象的引用,然后把它传递到各处。你可以创建更多的对该对象的引用,然后所有这些引用都会被计数。当引用超出范围,智能指针的引用计数就会减少。一旦所有的引用(指针的实例)小时,这个对象将自动清理(释放)。
-
首先为要封装的对象定义一个指针模板的实例:可以调用cv::Ptr< Matx33f> p(new Matx33f) 或者 cv::Ptr< Matx33f> p = makePtr< Matx33f>() 来实现
-
这个模板的构造函数就拥有指向对应对象的指针。进行这样的操作之后,就得到了一个只能指针 p ,这是一个类似指针的对象,你可以随意传递,并且像使用标准指针一样使用它(支持**operator*()和operator->()**等操作)。
-
一旦得到p就可以创建其他相同类型的对象,而不需要把一个指向新的对象的指针传递给他们。例如可以创建 Ptr< Matx33f> q,当你把p的值传递给q时,起始仍然只有一个实际上的Matx33f实例,而p和q同时指向它。
-
p和q都知道它们只是两个指针中的一个。当p被释放,q就知道自己是原始矩阵仅存的引用。如果q也要被释放,这时它的析构函数将被调用(隐式),由于q发现自己是最后一个引用,所以它会释放原始矩阵。
-
可以把这个过程想象成办公室最后一个离开的人有责任关灯。
3.3.2 引用计数功能相关附加函数
- **Ptr()<>**模板类支持多个接口中的与智能指针的引用计数功能相关的附加函数;
- **addref()和release()**增加或减少指针内部的引用计数,这些都是相对危险的函数,但需要自己管理引用计数的时候还是可以使用的。
3.3.3 empty()函数的两个应用
- 应用1:empty() 函数可以确定一个智能指针是否指向一个已经被释放掉的对象。举个例子:当你对一个对象调用release() 的时候,你仍然有一个智能指针,但它指向的对象已被销毁。
- 应用2:确定智能指针对象内部对象指针是否出于其他原因而为NULL。举个例子:你给智能指针赋值时是用了一个刚好首先返回NULL的函数(cvLoadImage(), fopen() 等)。
3.3.4 delete_obj()
- 这是一个引用计数为零时自动调用的函数。
3.4 cv::Exception类和异常处理
- Exception类型有成员code,err,func,file,line.分别由一个数字的错误代码表示,对应表示产生异常的错误的字符串,错误发生的函数名,错误发生的文件,错误出现在文件中的行数。
- CV_Error(errorcode, description) 会生成并抛出一个有固定文字描述的异常;
- CV_Error_(errorcode, printf_fmt_str, [printf-args]) 以同样的方式工作,但允许你使用类似printf格式字符串和参数代替固定的描述;
- CV_Assert(Condition) 和 CV_DbgAssert(Condition) 都会测试程序中所设定的条件(condition),如果条件不符合,则抛出异常。
- 在较新的OpenCV版本中,这些功能只能在调试(debug)版本使用。
- 这些宏是抛出异常的首选方法,因为他们会自动为你处理函数、文件和行。
3.5 DataType<>模板
4. 工具函数
4.1 函数列表
- 工具函数和系统函数
函数名称 | 描述 |
---|---|
alignPtr() | 对齐指针给定字节数 |
alignSize() | 将缓冲区大小与给定的字节数对齐 |
allocate() | 分配一个C风格的数组对象 |
Ceil() | 近似一个浮点数x到不小于x的最近的整数(向上取整) |
cubeRoot | 计算一个数的立方根 |
CV_Assert() | 如果给定的条件不为真,则抛出异常 |
CV_Error() | 构造cv::Exception (从固定的字符串) 并抛出异常的一个宏 |
CV_Error_() | 构造cv::Exception (从格式化的字符串) 并抛出异常的一个宏 |
deallocate() | 释放一个C风格的数组对象 |
error() | 指示错误并抛出异常 |
fastAtan2() | 向量的二维角度的计算 |
fastFree() | 释放一个内存缓冲区 |
fastMalloc() | 分配一个对齐的内存缓冲区 |
cvFloor() | 近似一个浮点数x到不大于x的最近的整数(向下取整) |
format() | 以sprintf类似格式创建一个STL字符串 |
getCPUTickCount() | 从内部CPU计时器获得tick技术 |
getNumThreads() | 获得当前OpenCV使用的线程 |
getOptimalDFTSize() | 计算要传递给DFT的数组的最适宜大小 |
getThreadNum() | 获得当前线程的索引 |
getTickCount() | 获得系统的tick计数 |
getTickFrequency | 获得每秒的tick计数 |
cvIsInf() | 判断一个浮点数x是否无穷 |
cvIsNan() | 判断一个浮点数x是否不是一个数 |
cvRound() | 近似一个浮点数x到最近的整数 |
setNumThreads() | 设定OpenCV使用的线程 |
setUseOptimized() | 开启或关闭优化代码 |
useOptimized | 指示代码优化的启用 |
5. 模板结构
- OpenCV 2.1以及后续的版本都建立在类似于STL、boost及其他类似库的模板元编程风格之上。这些库的设计非常强劲,不管是在质量上还是在速度上都非常领先,并且对开发者保持了最大限度的自由。从惯例上来说,OpenCV中所使用的模板结构允许算法以一种抽象的形式表达并且不需要依赖于C++的基本结构,甚至不依赖于OpenCV的基本结构。
- 通用的固定长度模板
函数名称 | 描述 |
---|---|
cv::Point_< Type T> | 包含两个类型T对象的点 |
cv::Rect_< Type T> | 位置、宽度和高度, 均为类型T |
Vec< Type T, int H> | 一个类型T的集合 |
Matx< Type T, int H, int W> | 一个H* W的类型T |
Scalar_< Type T> | 一个包含四个类型T对象的集合(和cv::Vec< T, 4>) |