OpenCV学习笔记(二) cv::Mat
部分内容转自:OpenCV Tuturial,ggicci
在OpenCV Tuturial中可查看Mat的初始化与打印方法。
Mat本质上是由两个数据部分组成的类:
- 矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)
- 一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针
OpenCV使用引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则 只拷贝信息头和矩阵指针 ,而不拷贝矩阵。
特性
- reference counting:当counting为0时,会自动释放内存;
- shallow copy:当令mat1=mat2时,二者指向的是同一份image data,对mat2的修改会等效作用于mat1上,如果确实要拷贝出一个副本时,需要调用copyTo函数或者clone函数。
Mat A, C; // 只创建信息头部分 A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存 Mat B(A); // 使用拷贝构造函数 C = A; // 赋值运算符
以上代码中的所有Mat对象最终都指向同一个也是唯一一个数据矩阵。虽然它们的信息头不同,但通过任何一个对象所做的改变也会影响其它对象。实际上,不同的对象只是访问相同数据的不同途径而已。注意,这个特性要求OpenCV中的类应该返回副本copyTo(returnMat),否则当某个类的实体发生改变时,该类的其余实体都会发生改变。
- 你可以创建只引用部分数据的信息头。比如想要创建一个感兴趣区域( ROI ),你只需要创建包含边界信息的信息头:
// ROI是某个矩形 Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle Mat E = A(Range:all(), Range(1,3)); // using row and column boundaries // 假如ROI是某些行或者列时: cv::Mat imageROI= image.rowRange(start,end) ; cv::Mat imageROI= image.colRange(start,end) ; // 单行或者单列时: image.row(rowNum),image.col(colNum)
- 出于效率优化,每行的结尾可能存在padding,使得每行大小是2的整数次幂,可以通过M.isContinuous()判断是否存在padding(True:不存在padding)。当不存在padding时,Mat image的内存占用为(byte)=image.elemSize() * image.total()
属性
- data:Mat对象中的一个指针,指向内存中存放矩阵数据的一块内存 (uchar* data)
- dims:Mat所代表的矩阵的维度,如 3 * 4 的矩阵为 2 维, 3 * 4 * 5 的为3维
- channels():通道,矩阵中的每一个矩阵元素拥有的值的个数,比如说 3 * 4 矩阵中一共 12 个元素,如果每个元素有三个值,那么就说这个矩阵是 3 通道的,即 channels = 3。常见的是一张彩色图片有红、绿、蓝三个通道。
- depth():深度,即每一个像素的位数(bits),在opencv的Mat.depth()中得到的是一个 0 – 6 的数字,分别代表不同的位数:enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; 可见 0和1都代表8位, 2和3都代表16位,4和5代表32位,6代表64位;
- step:是一个数组,定义了矩阵的布局,包括padding部分,具体见下面图片分析,另外注意M.step[m-1] == M.elemSize();
- step1(n) == step[n] / elemSize1,M.step1(m-1)总是等于 channels;
- elemSize() : 矩阵中每一个元素的数据大小,如果Mat中的数据的数据类型是 CV_8U 那么 elemSize = 1,CV_8UC3 那么 elemSize = 3,CV_16UC2 那么 elemSize = 4;
- elemSize1(): 表示的是矩阵中数据类型的大小,即 elemSize / channels 的大小
- total():像素的总数
地址计算
灰度图的每个像素都是0~255的8 bit值。彩色图有BGR三通道,其像素可视为一个三维向量,每个分量也是一个0~255的8 bit值。代码中有时存在第四维alpha,表示透明度。
最小的数据类型可能是 char 类型,这意味着一个字节或 8 位。这可能是有符号(值-127 到 + 127)或无符号(以便可以存储从 0 到 255 之间的值)。虽然这三个组件的情况下已经给 16 万可能的颜色来表示 (如 RGB 的情况下),我们可通过使用浮点数 (4 字节 = 32 位) 或double(8 字节 = 64 位) 数据类型的每个组件获得甚至更精细的控制。
注意:当目标为ROI时,地址计算失效。
addr(Mi0,i1,…im-1)=M.data + M.step[0] * i0 + M.step[1] * i1 + … + M.step[m-1] * im-1
(其中 m = M.dims M的维度)
考虑二维情况(stored row by row)按行存储:
![]() |
当数据类型为 CV_8U单通道的 uchar 时:
当数据类型是 CV_8UC3三通道:
|
![]() |
当数据类型为 CV_16SC4,也就是 short 类型:
|
其他
P.S.1
在OpenCV1中采用的IplImage(Intel Image Processing Library)类型应尽量不再使用。可用以下方法将IplImage转为Mat:
IplImage* iplImage = cvLoadImage("c:\\img.jpg"); cv::Mat image(iplImage,false); //false是默认参数,表示浅拷贝,即image指向同一区域,不额外占用空间。
若确实需要使用IplImage时,应注意dangling pointer的问题,可选择:
- 使用reference counting pointer:cv::Ptr<IplImage> iplImage = cvLoadImage("c:\\img.jpg");
- 显式销毁指针:cvReleaseImage(&iplImage);
P.S.2
Mat 中的channel是BGR,在Qt中显示的图像需是QImage类型(通道为RGB),可通过以下方式转换:
// change color channel ordering cv::cvtColor(image,image,CV_BGR2RGB); // Qt image QImage img= QImage((const unsigned char*)(image.data), image.cols,image.rows,QImage::Format_RGB888); // display on label ui->label->setPixmap(QPixmap::fromImage(img)); // resize the label to fit the image ui->label->resize(ui->label->pixmap()->size());
P.S.3
Mat的几个method:
cv::Mat image=imread("image.jpg"), result; // 第i行的地址 uchar* data = image.ptr<uchar>(0); // 按照image的大小和类型格式化 Mat result result.create(image.rows,image.cols,image.type()); // ROI访问某行或者某列 result.row(0).setTo(cv::Scalar(0)); result.row(result.rows-1).setTo(cv::Scalar(0)); // 在不占用内存的情况下,改变矩阵的维数 image.reshape(1, // new number of channels image.cols*image.rows) ; // new number of rows // True:每行的结尾不存在padding if (image.isContinuous()){ /*图像的内存空间是连续的*/ nc= nc*nl; nl= 1; // it is now a 1D array }
完
posted on 2014-03-01 20:12 eric.xing 阅读(6533) 评论(0) 编辑 收藏 举报