抱着学习的态度,阅读OpenCV源码。我阅读的版本是OpenCV2.4.8,其实2.X在思路上是很相似的。OpenCV自带了详尽的文档。其中opencv_tutorials.pdf是OpenCV入门文档,该文档中有大量的示例;opencv2refman.pdf是函数手册。这两个文档也是重要的参考资料。
第一章 Mat
本章主要内容:Mat类的结构、内存管理、运算、_InputArray类
主要涉及文件有:
include | opencv2 | core | core.hpp 其中有Mat类定义
| mat.hpp 其中有Mat类实现
| types_c.h C语言类型定义和部分函数
modules | core | src | arithm.cpp 算法实现
| matop.cpp Mat的运算
Mat是OpenCV中最关键的类之一,破解Mat类对把握OpenCV有重要作用,同时通过这部分阅读可以了解OpenCV的设计理念。
Mat是Matrix的简称,中文含义是矩阵,OpenCV2.x摒弃了1.x中区别对待IplImage、CvMat等结构的思路,将所有的问题划归到数学概念中去,图像在线性空间中的表达就是矩阵。
另外,OpenCV中定义了Matx(小矩阵)的结构,Vec(向量)是Matx的子类。他们通过_InputArray(输入数组)类实现相互转换。在矩阵运算、图像处理等函数中,通过以_InputArray类为代理,访问Mat或Vec。Mat_<T>是Mat的子类,是Mat的模板化。
1.1 Mat的结构
【core.hpp 1475-2010行】【opencv2refman.pdf 25-44页】
1.1.1 Mat中成员变量
/*! includes several bit-fields:
- the magic signature
- continuity flag
- depth
- number of channels
*/
int flags;
//! the matrix dimensionality, >= 2
int dims;
//! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;
//! pointer to the reference counter;
// when matrix points to user-allocated data, the pointer is NULL
int* refcount;
//! helper fields used in locateROI and adjustROI
uchar* datastart;
uchar* dataend;
uchar* datalimit;
//! custom allocator
MatAllocator* allocator;
MSize size;
MStep step;
(1) flag是标记,他由五部分组成:
1、0-2位:深度,在types_c.h中深度值如下:
CV_8U | 0 | 0x000 |
CV_8S | 1 | 0x001 |
CV_16U | 2 | 0x010 |
CV_16S | 3 | 0x011 |
CV_32S | 4 | 0x100 |
CV_32F | 5 | 0x101 |
CV_64F | 6 | 0x110 |
CV_USRTYPE1 | 7 | 0x111 |
2、3-11位:通道数减1,因为即灰度图像(1通道)时3-11位是0x000000000,彩色图像(3通道)时3-11位是0x000000010
3、14位:MAT_CONT_FLAG,矩阵的数据存储是否连续的标记,1表示连续,0表示不连续
4、15位:SUBMAT_FLAG,子矩阵标记,Mat支持从大矩阵中取出子矩阵,数据没有复制。
例如,假设Mat A是3×3的矩阵
| 1 2 3 |
A = | 4 5 6 |
| 7 8 9 |
Mat B = A.rowRange(1,2);//B取A的第2到3行,行号以0开始
则 B = | 4 5 6 |
| 7 8 9 |,
B的数据并没有新增加,而是直接指向了A的数据,B的datastart和dataend表明了起止位置,rows和cols表明了尺寸。具体将在Mat的内存管理中说明。
5、16-31位:默认的标记MAGIC_VAL,由于矩阵有很多种类型,_InputArray代理所有的数据输入时,需要依赖这个量来判断数据类型,这个值就表达了该类的类型。
查看源码可以知道
类型 | MAGIC_VAL值 |
SparseMat(系数矩阵) | 0x42FD0000 |
Mat | 0x42FF0000 |
cvMat | 0x42420000 |
CvMatND | 0x42430000 |
CvSparseMat | 0x42440000 |
CvHistogram | 0x42450000 |
CvMemStorage | 0x42890000 |
CvSeq | 0x42990000 |
CvSet | 0x42980000 |
IPLImage | 0x00000070 |
他们都是类或结构体的前两个字节(int)。IPLImage来自以往的库,他的前两个字节是int nSize,是结构体的大小,也就是说他的MAGIC_VAL是0x00000070。
(2)dims是维度(一般是2);rows是行数;cols是列数。
(3)data指针指向了数据;refcount指针指向了一个计数器,这个计数器记录着多少个Mat指向了同一个data,当计数为0时,释放data;allocator是动态内存申请器。
(4)step保存了一行的宽度,完整连续的图像step.p[0]与cols相等,子矩阵的step.p[0]与原始图像的cols相等。
代码注释非常给力,以下几行注释清楚地表现了Mat的结构:
1.1.2 Mat的构造函数、赋值运算符、数据类型转换运算符
构造函数、赋值运算、类型转换都表现了数据转换能力:
(1)构造函数
1、空构造函数【1690行】
2、指定尺寸和类型【1692行】,或由Scalar结构定义尺寸和类型【1696行】
3、高维【1700行】
4、从其他Mat拷贝【1704行】
5、从数据块拷贝并指定尺寸和类型【1706行】
6、只定义一个头,数据指向其他Mat中一部分(行列范围、ROI)【1711行】
7、从老式的CvMat、CvMatND、IplImage转换成Mat【1715-1719行】
8、从新式的std::vector、cv::Vec、cv::Matx、2D point、3D point、comma initializer转换成Mat【1722-1731行】
(2)赋值运算
9、const Mat& m从另一个Mat转换【1740行】
10、const MatExpr& expr从矩阵表达式转换成Mat【1741行】
矩阵表达式MatExpr,构造了一个运算结构,具体在Mat的计算中解释
11、const Scalar& s从Scalar转换成Mat,是用一种颜色将整个图像填充【1774行】
(3)数据转换运算
11、括号运算符:截取子矩阵【1842-1844行】
12、数据转换:由Mat转换成老式结构CvMat、CvMatND、IplImage【1846-1851行】
13、数据转换:由Mat转换成vector<_Tp>、Vec<_Tp, n>、Matx<_Tp, m, n>【1853-1855行】