Mat类
Mat 是一个类 , 由两个数据部分组成 :
- 矩阵头(包含矩阵尺寸 、存储方法 、 存储地址等信息)
- 一个指向存储所有像素值的矩阵(根据所选存储方法的不同,矩阵可以是不 同的维数)的指针 。
矩阵头的尺寸是常数值 , 但矩阵本身的尺寸会依图像的不同而不同 , 通常比矩阵头的尺寸大数个数量级 。 因此,
当在程序中传递图像并创建副本时,大的开销是由矩阵造成的,而不是信息头 。
当进行大图像的复制时 , 会降低程序的运行速度。
为了解决此问题 , OpenCV使用了引用计数机制 。 其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵 。 这通过让矩阵指针指向同一地址而实现 。而拷贝构造函数则只复制信息头和矩阵指针 , 而不复制矩阵 。
如下面的代码:
Mat A , C ; // 仅创建信息头部分
A = imread("l.jpg" , CV_ LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存
Mat B(A) ; //使用拷贝构造函数
C = A; //赋值运算符
以上代码中的所有 Mat 对象最终都指向同一个也是唯一一个数据矩阵 。 虽然它们的信息头不同,但通过任何一个对象所做的改变也会影响其他对象 。 实际上 ,不同的对象只是访问相同数据的不同途径而已 。
这里还要提及一个比较棒的功能 :
我们可以创建只引用部分数据的信息头。 比如想要创建一个感兴趣区域 ( ROL ) ,只需要创建包含边界信息的信息头:
Mat D(A, Rect(lO , 10 , 100 , 100) ) ; //使用矩形界定
Mat E = A(Range:all() , Range(l,3)) ; //用行和列来界定
如果矩阵属于多个 Mat 对象,那么 当不再需要它时,谁来负责清理呢?
最后一个使用它的对象 。 通过引用计数机制来实现。我们无论什么时候复制一个 Mat 对象的信息头,都会增加矩阵的引用次数 。 反之,当一个头被释放之后 , 这个计数被减一 ; 当计数值为零 , 矩阵会被消灭。但某些时候你仍会想复制矩阵本身(不只是信息头和矩阵指针 ) , 这时可以使用函数 clone() 或者 copyTo() 。
Mat F = A.clone();
Mat G;
A.copyTo(G);
现在改变 F 或者 G 就不会影响 Mat 信息头所指向的矩阵。
小结:
• OpenCV 函数中输出图像的内存分配是自动完成的 (如果不特别指定的话) 。
• 使用 OpenCV 的 C++接口时不需要考虑内存释放问题 。
• 赋值运算符和拷贝构造函数 ( 构造函数)只复制信息头。
• 使用函数 clone()或者 copyTo()来复制一幅图像的矩阵 。
像素值的存储方法
颜色系统有很多,它们各有优势,具体如下 。
- RGB 是最常见的 ,这是因为人哏采用相似的工作机制 , 它也被显示设备所采用
- HSY 和 HLS 把颜色分解成色调、饱和度和亮度/明度 。 这是描述颜色更自然的方式 ,比如可以通过抛弃最后一个元素,使算法对输入图像的光照条件不敏感
- YCrCb 在 JPEG 图像格式中 广泛使用
- ClE L*a*b*是种在感知上均匀的颜色空间 , 它适合用来度量两个颜色之间的距离
显式创建 Mat 对象的五种方法
1 使用 Mat()构造函数
Mat M( 3 , 3 , CV_8UC3, Scalar(0 , 0 , 255 ));
cout << "M = " << endl << " " << M << endl << endl;
运行结果:
M =
[ 0, 0, 255, 0, 0, 255, 0, 0, 255;
0, 0, 255, 0, 0, 255, 0, 0, 255;
0, 0, 255, 0, 0, 255, 0, 0, 255]
对于二维多通道图像,首先要定义其尺寸,即行数和列数 。 然后 , 需要指定存储元素的数据类型以及每个矩阵点的通道数 。 为此,依据下面的规则有多种定义:
CV_[The number of bits per item] [Signed or Unsigned ) [Type Prefix ]C[The channel number)
即 :
CV_[位数][带符号与否][类型前缀] C [通道数]如:CV_8UC3
2 利用 Create()函数
Mat M;
M.create (4,4, CV_8UC( 2 ));
cout << "M = " << endl << " " << M << endl << endl;
运行结果:
M =
[ 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0]
需要注意的是 ,此创建方法不能为矩阵设初值,只 是在改变尺寸时重新为矩阵数据开辟内存而已。
3 采用 Matlab 式的初始化方式
采用 Matlab 形式的初始化方式 : zeros(), ones(), eyes() 。 使用以下方式指定尺寸和数据类型 :
Mat E = Mat::eye(4 , 4, CV_64F);
cout << " E = " << endl << " " << E << endl << endl ;
Mat O = Mat::ones(2 , 2 , CV_32F);
cout << "O = " << endl << " " << O << endl << endl ;
Mat Z = Mat::zeros (3 , 3 , CV_8UC1);
cout << " Z = " << endl << " " << Z << endl << endl;
运行结果:
E =
[1, 0, 0, 0;
0, 1, 0, 0;
0, 0, 1, 0;
0, 0, 0, 1]
O =
[1, 1;
1, 1]
Z =
[ 0, 0, 0;
0, 0, 0;
0, 0, 0]
4 对小矩阵使用逗号分隔式初始化函数
Mat C = (Mat_<double> (3 , 3) << 0 , -1 , 0 , -1, 5 , -1, 0 , -1 , 0) ;
cout << " C = " << endl << " " << C << endl << endl ;
运行结果:
C =
[0, -1, 0;
-1, 5, -1;
0, -1, 0]
5 为已存在的对象创建新信息头
使用成员函数 clone()或者 copyTo()为一个已存在的 Mat 对象创建一个新的信息头,示范代码如下 。
Mat C = (Mat_<double> (3 , 3) << 0 , -1 , 0 , -1, 5 , -1, 0 , -1 , 0) ;
cout << " C = " << endl << " " << C << endl << endl ;
Mat RowClone = C.row(1).clone();
cout << "RowClone = " << endl << " " << RowClone << endl << endl ;
运行结果:
C =
[0, -1, 0;
-1, 5, -1;
0, -1, 0]
RowClone =
[-1, 5, -1]
OpenCV 中的格式化输出方法
1. 【风格一 】 OpenCV 默认风格
风格一为 OpenCV 默认风格的输出 方法 , 如下。
Mat M = Mat (10, 3, CV_8UC3) ;
randu(M , Scalar::all (0) , Scalar::all(255));//生成随机数
cout << "M = " << endl << " " << M << endl << endl;
运行结果:
M =
[ 91, 2, 79, 179, 52, 205, 236, 8, 181;
239, 26, 248, 207, 218, 45, 183, 158, 101;
102, 18, 118, 68, 210, 139, 198, 207, 211;
181, 162, 197, 191, 196, 40, 7, 243, 230;
45, 6, 48, 173, 242, 125, 175, 90, 63;
90, 22, 112, 221, 167, 224, 113, 208, 123;
214, 35, 229, 6, 143, 138, 98, 81, 118;
187, 167, 140, 218, 178, 23, 43, 133, 154;
150, 76, 101, 8, 38, 238, 84, 47, 7;
117, 246, 163, 237, 69, 129, 60, 101, 41]
2 . 【风格二 】 Python 风格
Mat M = Mat (10, 3, CV_8UC3) ;
randu(M , Scalar::all (0) , Scalar::all(255));//生成随机数
cout << "M = " << format(M , Formatter::FMT_PYTHON) << ";" << endl << endl ;
运行结果:
M = [[[ 91, 2, 79], [179, 52, 205], [236, 8, 181]],
[[239, 26, 248], [207, 218, 45], [183, 158, 101]],
[[102, 18, 118], [ 68, 210, 139], [198, 207, 211]],
[[181, 162, 197], [191, 196, 40], [ 7, 243, 230]],
[[ 45, 6, 48], [173, 242, 125], [175, 90, 63]],
[[ 90, 22, 112], [221, 167, 224], [113, 208, 123]],
[[214, 35, 229], [ 6, 143, 138], [ 98, 81, 118]],
[[187, 167, 140], [218, 178, 23], [ 43, 133, 154]],
[[150, 76, 101], [ 8, 38, 238], [ 84, 47, 7]],
[[117, 246, 163], [237, 69, 129], [ 60, 101, 41]]];
3. 【 风格三 】 C 语言风格
Mat M = Mat (10, 3, CV_8UC3) ;
randu(M , Scalar::all (0) , Scalar::all(255));
cout << "M = " << format(M , Formatter::FMT_C) << ";" << endl << endl ;
运行结果:
M = { 91, 2, 79, 179, 52, 205, 236, 8, 181,
239, 26, 248, 207, 218, 45, 183, 158, 101,
102, 18, 118, 68, 210, 139, 198, 207, 211,
181, 162, 197, 191, 196, 40, 7, 243, 230,
45, 6, 48, 173, 242, 125, 175, 90, 63,
90, 22, 112, 221, 167, 224, 113, 208, 123,
214, 35, 229, 6, 143, 138, 98, 81, 118,
187, 167, 140, 218, 178, 23, 43, 133, 154,
150, 76, 101, 8, 38, 238, 84, 47, 7,
117, 246, 163, 237, 69, 129, 60, 101, 41};
输出其他常用数据结构
之前我们讲解了 如何输出 Mat 类型,其实, OpenCV 同样支持使用运算符" << "来打印其他常用的 OpenCV 数据结构。
1. 定义和输出二维点
首先看看二维点的定义和输出方法 :
Point2f p(6 , 2 );
cout << "[二维点] p = " << p << ";" << endl ;
运行结果:
[二维点] p = [6, 2];
2. 定义和输出三维点
Point3f p3f (8 , 2 , 0);
cout << " [ 三维点 ] p = " << p3f << ";" << endl ;
运行结果:
[三维点] p = [8, 2, 0] ;
3 . 定义和输出基千 Mat 的 std : :vector
vector<float> v ;
v . push_back (3 );
v . push_back (5 );
v . push_back (7 );
cout << "[基于Mat的vector] shortvec = " << Mat(v ) << ";" <<endl ;
运行结果:
[基于Mat的ector] shortvec = [3;
5;
7];
4. 定义和输出 std : :vector 点
最后看看如何定义和输出存放着 点的 vector 容器,以存放二维点 Point2f为例 :
vector<Point2f> points (20 );
for (size_t i = 0; i < points . size (); ++i )
points[i] = Point2f((float) (i * 5) , (float) (i % 7)) ;
cout << " [ 二维点向量 ] points = " <<points<< ";";
运行结果:
[ 二维点向量 ] points = [0, 0;
5, 1;
10, 2;
15, 3;
20, 4;
25, 5;
30, 6;
35, 0;
40, 1;
45, 2;
50, 3;
55, 4;
60, 5;
65, 6;
70, 0;
75, 1;
80, 2;
85, 3;
90, 4;
95, 5];