opencv引入了Mat类用于管理图像(从数字图像处理的角度看,图像其实就是矩阵),Mat类采用了很巧妙的内存管理机制,可以让开发人员不必把注意力放在内存的管理上,这大大降低了opencv的学习门槛。
cv::Mat可以作为任意维度的数组使用,在一维数组中元素是按照顺序存储的,二维数组是按照行排列的方式排列,每一行元素同样按照顺序存储,对于三维数组,每个通道被一个二维数组填充,每个通道的存储方式与二维数组是一样的。
cv::Mat最重要的几个信息:元素数据类型、通道数、每个通道的尺寸(rows和cols),指明数据存储位置的data指针。
尺寸是常数,但是矩阵本身的尺寸是可变的。创建和复制图像的开支,主要来源于图像矩阵的操作,可想而知复制一个图像是一笔很大的开销。cv::Mat类处理这个问题的机制是:在保留对象自己的信息头的情况下共用矩阵。
m和n是两个cv::Mat对象,当用其中一个对象赋值给另一个时m=n,m内部的数据指针指向的数据实体将被释放,m和n将会共享同一块内存,这块内存的引用指针会增加一个引用计数,m变成了新的数据实体的头,其行、列、数据类型信息等会依据新的数据实体进行更新!
例子
cv::Mat A, C; //仅创建信息头部分
A = cv::imread(file_path);
C = A; //赋值运算符,A和C的数据指针指向同一个数据实体,对一个对象的操作会影响到另一个
cv::Mat B(C); //拷贝构造
cv::Mat D = A.clone(); //复制操作
cv::rectangle(C, cv::Point(100, 200), cv::Point(300, 400), cv::Scalar(0, 0, 255), 1, 1, 0); //对C对象进行操作
cv::rectangle(B, cv::Point(200, 300), cv::Point(400, 500), cv::Scalar(255, 0, 255), 1, 1, 0); //对B对象进行操作
cv::rectangle(D, cv::Point(250, 350), cv::Point(450, 550), cv::Scalar(0, 255, 0), 1, 1, 0); //对D对象进行操作
cv::imshow("A", A); //显示A对象图像
cv::imshow("B", B); //显示B对象图像
cv::imshow("C", C); //显示C对象图像
cv::imshow("D", D); //显示D对象图像
cout << &A << "," << &C;
结果表明对A、B、C对象的操作是在操作同一个对象,或者严格的说操作的是同一个矩阵(各个对象有自己的信息头),说明C和B对象创建时发生了浅拷贝,矩阵部分没有进行复制。对D的操作不再是同一个矩阵,说明clone()接口被调用时发生了深拷贝,矩阵的内容也被复制了一份到D对象自己的地址空间。
二维三通道的32位浮点型数组,数组元素就是占用12字节的32位浮点型数据(4字节*3通道)。n维单通道和(n-1)维多通道数组有非常接近的存储结构,区别在于,后者数组元素中的通道是按照顺序进行存储的。
通过cv::Mat的默认构造可以创建一个没有大小和数据类型的数组,之后可以通过create成员函数来进行内存空间的申请—通过指定行数、列数和数据类型(type)来配置数组。数组的类型(type)决定了它包含什么样的元素,一个有效的数据类型包含数据的类型和通道数,例如CV_32FC3表示三通道的32位浮点型数据。
S--代表---signed int---有符号整形
U--代表--unsigned int--无符号整形
F--代表--float--单精度浮点型
注意:你可以创建超过3通道的数组,但是没有既定的宏用于创建,但是可以通过CV_8UC(7)创建一个7通道的8U数据类型数组。
cv::Mat m;
m.create(3,10,CV_32FC3);
m.setTo(cv::Scalar(1.0f, 0.0f, 1.0f));//将第1通道数据设为1.0,第2通道数据设为0.0,第三通道设为1.0
cv::Mat(3, 10, CV_32FC3, cv::Scalar(1.0f, 0.0f, 1.0f));
上面两种初始化的方式是等价的。
cv::Mat test;
test.create(2, 3, CV_32FC3);
test.setTo(cv::Scalar(1.0, 2.0, 3.0));
LOG(INFO) << "channel0: " << test.at(1, 1)[0];
LOG(INFO) << "channel1: " << test.at(1, 1)[1];
LOG(INFO) << "channel2: " << test.at(1, 1)[2];
float priors_[] = {1.0, 10.0};
cv::Mat priors(1, 2, CV_32F, priors_);
LOG(INFO) << "channel0: " << priors.at(0, 0)[0];
LOG(INFO) << "channel1: " << priors.at(0, 0)[1];
m.row(i) |
m中第i行数组 |
m.col(j) |
m中第j列数组 |
m.rowRange( i0, i1) |
m中第i0行到第i1-1行所构成的数组 |
m.rowRange(cv::Range(i0, i1)) |
m中第i0行到第i1-1行所构成的数组 |
m.colRange(j0, j1) |
m中第j0列到第j1-1列所构成的数组 |
m.colRange(cv::Range(j0, j1)); |
m中第j0列到第j1-1列所构成的数组 |
m.diag( d); |
m中偏移为d的对角线所组成的数组 |
m(cv::Range(i0,i1),cv::Range(j0,j1)) |
m中从点(i0, j0)到点(i1-1, j1-1)所包含数据构成的数组 |
m(cv::Rect(i0, i1, w, h)) |
m中从点(i0, j0)到点(i0+w-1, j0+h-1)所包含数据构成的数组 |
m(ranges) |
m中依据ranges[0]到ranges[ndim-1]所索引区域构成的数组 |
m1= m0.clone() |
从m0进行完全复制,该复制将复制所有的数据元素 |
m0.copyTo(m1) |
将m0复制给m1,如果有必要,将给m1重分配内存空间(等同于m1=m0.clone()) |
m0.copyTo(m1,mask ) |
和m0.copyTo(m1)一样,但是只复制mask所指示的区域 |
m0.convertTo(m1,type,scale,offset) |
转换m0中元素的类型(比如CV 32F)并且在尺度变换(默认为1)和增加偏置(默认为0)之后赋值给m1 |
m0.assignTo( m1,type ) |
只在内部使用(集成在convertTo中) |
m0.setTo( s,mask ) |
设置m0所有元素为s,如果存在mask,则只对mask指示区域进行操作 |
m0.reshape( chan,rows) |
改变二维数组的有效形状,chan和rows变量可能为0,表示不做更改 |
m0.push_back(s) |
在末尾增加一个mx1大小的数组 |
m0.push_back( m1) |
向mxn大小的矩阵m0增加k行并且复制到m1中,m1大小必须是kxn |
m0.pop_back( n) |
从mxn大小的矩阵移除n行(默认是1)a |
m0.locateROI( size,offset ) |
将m0的全尺寸写入cv::size 变量size 如果m0只是一个大矩阵的一块小区域,还会写入一个Point类型的offset |
m0.adjustROI( t,b,l,r ) |
通过四个值t (最上),b(最下),1(最左),(最右)调整ROI范围 |
m0.total() |
计算数组序列的元素的数目 (不包括通道) |
m0.isContinuous() |
如果m0的行之间没有空隙,将返回true |
m0.elemSize() |
返回m0的位长度(比如三通道浮点矩阵将返回12) |
m0.elemSize1() | 返回m0最基本元素的位长度(比如三通道浮点矩阵将返回4) |
m0.type() |
返回m0元素的类型(比如CV 32FC3) |
m0.depth() |
返回m0通道中的元素类型(比如CV 32F) |
m0.channels() |
返回m0的通道数目 |
m0.size() |
以cv::Size返回m0的大小 |
m0.empty() | 如果数组没有元素,将返回true (比如m0.total==0或者m0.data==NULL) |
答案:浅拷贝,形参和实参共用矩阵。
例
void A()
{
Mat A; //仅创建信息头部分
A = imread("1.jpg");
rectangle(A, cvPoint(250, 350), cvPoint(450, 550), Scalar(0, 255, 0), 1, 1, 0); //对A对象进行操作
imshow("A", A); //显示A对象图像
test(A);
cout << "实参指针地址:" << &A;
imshow("A", A); //显示A对象图像
}
void test(Mat B)
{
cout << "形参指针地址:" << &B;
rectangle(B, cvPoint(100, 200), cvPoint(300, 400), Scalar(0, 0, 255), 1, 1, 0); //对B对象进行操作
imshow("B", B);
}
在函数调用的过程中,对B对象和A对象的操作会彼此影响,说明两个对象的矩阵是同一个。
答案:有区别,传值时对象发生浅拷贝,传地址传递对象地址。
例子
void test(Mat E)
{
cout << "形参指针地址:" << &E << endl;
rectangle(E, cvPoint(150, 250), cvPoint(350, 450), Scalar(0, 0, 255), 1, 1, 0); //对E对象进行操作
imshow("E", E);
}
void test2(Mat & E)
{
cout << "形参指针地址:" << &E << endl;
rectangle(E, cvPoint(200, 300), cvPoint(400, 500), Scalar(0, 0, 255), 1, 1, 0); //对E对象进行操作
imshow("E2", E);
}
void mat()
{
Mat A; //仅创建信息头部分
A = imread(MediaPath + "1.jpg");
rectangle(A, cvPoint(100, 200), cvPoint(300, 400), Scalar(0, 0, 255), 1, 1, 0); //对C对象进行操作
imshow("A", A); //显示A对象图像
cout << "实参地址:" << &A << endl;
test2(A);
test(A);
imshow("A", A); //显示A对象图像
}
结果