以前看 OpenCV 的书或资料,遇到 Mat 类的介绍,一般都是匆匆带过,自以为已经很熟悉了,从来没有深入研究过。
结果前段时间面试了一家公司,被问到两个 Mat 的问题:一是,谈谈对 Mat 类的理解;二是,如果 Mat B = A 的话会有什么结果
虽然也勉强回答了些,但是感觉自己对 OpenCV 中的一些基础知识,并没有想象中那么熟悉。于是,特意写下该文记录对 Mat 类的理解。
数字图像可看作一个数值矩阵, 其中的每个元素代表一个像素点,如下图所示:
上图中的数值矩阵可用 Mat 来表示,它是很关键的一种数据结构,因为 OpenCV 中的大部分函数都和 Mat 有关:或是 Mat 的成员函数;或把 Mat 作为参数;或将 Mat 作返回值。
Mat,在 OpenCV 中表示的是 N 维稠密矩阵,与稠密矩阵相对的是稀疏矩阵(只存储非零的像素值),后者常用于直方图处理中,OpenCV 中对应为 cv::SparseMat
如下所示:第一个为稠密矩阵的存储方式,存储所有的像素数值;第二个为稀疏矩阵的存储方式,只存储非零的像素值
$\quad \begin{bmatrix} 0 & 2 & 0 \\ 1 & 0 & 1 \\ 0 & 2 & 0 \end{bmatrix} $ $\quad \begin{bmatrix} & 2 & \\ 1 & & 1 \\ & 2 & \end{bmatrix} $
当 N=1 时,所有像素存储为一行;当 N=2 时,所有像素按照一行行的顺序排列;当 N=3 时,所有像素按照一面面的顺序排列,其中一行行的像素构成一个平面。
下图左,为灰度图的存储方式;图右,为 RGB 图像的存储方式,注意其存储顺序为 BGR (Blue->Green->Red)
Mat 类包含两部分,一是 矩阵头 (matrix header),二是 矩阵指针 (pointer to matrix),部分矩阵头如下:
- int flags; // signaling the contents of the matrix
- int dims; // dimensions
- int rows, cols; // rows and columns
- MatSize size; //
- MatStep step; //
矩阵指针如下,指向包含所有像素值的矩阵
- uchar* data; // pointer to the data
Mat 类中的赋值算子 "=" 和 拷贝构造函数,涉及的是浅拷贝,因此,当执行这两个操作时,仅仅是复制了矩阵头。
如果想要深拷贝,达到复制图像矩阵的目的,应使用 clone() 或 copyTo() 函数,如下图所示 (摘自参考资料 -- 4):
写到此处,面试的第二个问题也便有了答案。
下面是简单的验证,将矩阵 m3 通过 copyTo() 函数复制给 m1,而 m2 是通过 m1 直接赋值的,二者指向的是同样的数据。因此,如果改变了 m1,则 m2 对应的矩阵数值,也会进行相应的改变。
- Mat m1(3, 3, CV_32FC1, Scalar(1.1f));
- cout << "m1 = " << endl << " " << m1 << endl << endl;
- // using assign operator
- Mat m2 = m1;
- cout << "m2 = " << endl << " " << m2 << endl << endl;
- Mat m3(3, 3, CV_32FC1, Scalar(3.3f));
- m3.copyTo(m1);
- cout << "m1 = " << endl << " " << m1 << endl << endl;
- cout << "m2 = " << endl << " " << m2 << endl << endl;
在创建 Mat 之前,首先了解 Mat 中元素的数据类型,其格式为 CV_{8U, 16S, 16U, 32S, 32F, 64F}C{1, 2, 3} 或 CV_{8U, 16S, 16U, 32S, 32F, 64F}C(n)
第一个 {} 内数据表示的意义如下:
- CV_8U - 8-bit 无符号整数 ( 0..255 )
- CV_8S - 8-bit 有符号整数 ( -128..127 )
- CV_16U - 16-bit ( 0..65535 )
- CV_16S - 16-bit ( -32768..32767 )
- CV_32S - 32-bit ( -2147483648..2147483647 )
- CV_32F - 32-bit 浮点数 ( -FLT_MAX..FLT_MAX, INF, NAN )
- CV_64F - 64-bit 浮点数 ( -DBL_MAX..DBL_MAX, INF, NAN )
第二个 {} 内的数据 或 (n),表示的是图像矩阵的通道数,CV_8UC3 则等价于 CV_8UC(3),表示的数据类型为:3 通道 8 位无符号整数
创建一个 3 行 5 列,3 通道 32 位,浮点型的矩阵,通道 1, 2, 3 的值分别为 1.1f,2.2f,3.3f
- Mat m(3, 5, CV_32FC3, Scalar(1.1f, 2.2f, 3.3f));
- cout << "m = " << endl << " " << m << endl << endl;
输出的矩阵如下:
使用 Mat() + create() + setTo(),也可以构建如上的数值矩阵
- Mat m;
- // Create data area for 3 rows and 10 columns of 3-channel 32-bit floats
- m.create(3, 5, CV_32FC3);
- // Set the values in the 1st channel to 1.0, the 2nd to 0.0, and the 3rd to 1.0
- m.setTo(Scalar(1.1f, 2.2f, 3.3f));
- cout << "m = " << endl << " " << m << endl << endl;
单位矩阵 (ones),对角矩阵 (eye),零矩阵 (zeros),如下所示:
- // 单位矩阵
- Mat O = Mat: :ones(3, 3, CV_32F);
- cout << "O = " << endl << " " << O << endl << endl;
- // 零矩阵
- Mat Z = Mat: :zeros(3, 3, CV_8UC1);
- cout << "Z = " << endl << " " << Z << endl << endl;
- // 对角矩阵
- Mat E = Mat: :eye(3, 3, CV_64F);
- cout << "E = " << endl << " " << E << endl << endl;
常用来遍历 Mat 元素的基本函数为 at<>(),其中 <> 内的数据类型,取决于 Mat 中元素的数据类型,二者的对应关系如下:
- CV_8U -- Mat.at<uchar>(y,x)
- CV_8S -- Mat.at<schar>(y,x)
- CV_16U -- Mat.at<ushort>(y,x)
- CV_16S -- Mat.at<short>(y,x)
- CV_32S -- Mat.at<int>(y,x)
- CV_32F -- Mat.at<float>(y,x)
- CV_64F -- Mat.at<double>(y,x)
简单的遍历如下,使用了 Qt 的 qDebug() 来显示输出
- Mat m1 = Mat: :eye(10, 10, CV_32FC1);
- // use qDebug()
- qDebug() << "Element (3,3) is : " << m1.at < float > (3, 3);
- Mat m2 = Mat: :eye(10, 10, CV_32FC2);
- // use qDebug()
- qDebug() << "Element (3,3) is " << m2.at < cv: :Vec2f > (3, 3)[0] << "," << m2.at < cv: :Vec2f > (3, 3)[1];
注意:at<>() 函数中 () 内,行索引号在前,列索引号在后,也即 (y, x)
- Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
- {
- // accept only char type matrices
- CV_Assert(I.depth() == CV_8U);
- int channels = I.channels();
- int nRows = I.rows;
- int nCols = I.cols * channels;
- if (I.isContinuous())
- {
- nCols *= nRows;
- nRows = 1;
- }
- int i,j;
- uchar* p;
- for(i=0; i<nRows; ++i)
- {
- p = I.ptr<uchar>(i);
- for (j = 0; j<nCols; ++j)
- {
- p[j] = table[p[j]];
- }
- }
- return I;
- }
- Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
- {
- // accept only char type matrices
- CV_Assert(I.depth() == CV_8U);
- const int channels = I.channels();
- switch(channels)
- {
- case 1:
- {
- MatIterator_<uchar> it, end;
- for(it=I.begin<uchar>(), end=I.end<uchar>(); it!=end; ++it)
- *it = table[*it];
- break;
- }
- case 3:
- {
- MatIterator_<Vec3b> it, end;
- for(it=I.begin<Vec3b>(), end=I.end<Vec3b>(); it!=end; ++it)
- {
- (*it)[0] = table[(*it)[0]];
- (*it)[1] = table[(*it)[1]];
- (*it)[2] = table[(*it)[2]];
- }
- }
- }
- return I;
- }
比较上面两种方法的耗时,可使用如下代码来进行计算:
- double t = (double)getTickCount();
- // do something ...
- t = ((double)getTickCount() - t)/getTickFrequency();
- qDebug() << "Times passed in seconds: " << t << endl; // using qDebug()
1. <Learning OpenCV3> chapter 4
2. OpenCV Tutorials / The Core Functionality (core module) / Mat - The Basic Image Container
3. OpenCV Tutorials / The Core Functionality (core module) / How to scan images, lookup tables and time measurement with OpenCV
4. OpenCV 基础篇之 Mat 数据结构
来源: http://www.cnblogs.com/xinxue/p/7298683.html