cv::Mat
depth/dims/channels/step/data/elemSize
The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. It can be used to store (Mat类的对象用于表示一个多维度的单通道或者多通道稠密数组,它可以用来存储以下东西)
real or complex-valued vectors or matrices (实数值或复合值向量、矩阵)
grayscale or color images (灰度图或者彩色图)
voxel volumes (立体元素)
vector fields (矢量场)
point clouds (点云)
tensors (张量)
histograms (though, very high-dimensional histograms may be better stored in a SparseMat ) (直方图,高纬度的最好存放在SparseMat中)
旧版本的OpenCV中的C结构体有 CvMat 和 CvMatND,目前我用的是 2.3 版,里面的文档指出 CvMat 和 CvMatND 弃用了,在C++封装中用 Mat 代替,另外旧版还有一个 IplImage,同样用 Mat 代替(可以参考博文 OpenCV中的结构体、类与Emgu.CV的对应表).
矩阵 (M) 中数据元素的地址计算公式:
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的维度)
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:是一个数组,定义了矩阵的布局,具体见下面图片分析,另外注意 step1 (0)一行中基本数据类型的个数,M.step[0] 一行中元素的个数;
elemSize : 矩阵中每一个元素的数据大小,如果Mat中的数据的数据类型是 CV_8U 那么 elemSize() = 1,CV_8UC3 那么 elemSize() = 3,CV_16UC2 那么 elemSize = 4;记住另外有个 elemSize1() 表示的是矩阵中数据类型的大小,即 elemSize / channels 的大小.
图片分析1:考虑二维情况(stored row by row)按行存储
上面是一个 3 X 4 的矩阵,假设其数据类型为 CV_8U,也就是单通道的 uchar 类型
这是一个二维矩阵,那么维度为 2 (M.dims == 2);
M.rows == 3; M.cols == 4;
sizeof(uchar) = 1,那么每一个数据元素大小为 1 (M.elemSize() == 1, M.elemSize1() == 1);
CV_8U 得到 M.depth() == 0, M.channels() == 1;
因为是二维矩阵,那么 step 数组只有两个值, step[0] 和 step[1] 分别代表一行的数据大小和一个元素的数据大小,则 M.step[0] == 4, M.step[1] == 1;
M.step1(0) == M.cols = 4; M.step1(1) == 1;
假设上面的矩阵数据类型是 CV_8UC3,也就是三通道
M.dims == 2; M.channels() == 3;M.depth() == 0;
M.elemSize() == 3 (每一个元素包含3个uchar值) M.elemSize1() == 1 (elemSize / channels)
M.step[0] == M.cols * M.elemSize() == 12, M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;
M.step(0) == M.cols * M.channels() == 12 ; M.step(1) == M.channels() == 3;
图片分析2:考虑三维情况(stored plane by plane)按面存储
上面是一个 3 X 4 X 6 的矩阵,假设其数据类型为 CV_16SC4,也就是 short 类型
M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;
M.rows == M.cols == –1;
M.elemSize() == M.elemSize1() * M.channels() == M.step[M.dims-1] == M.step[2] == 2 * 4 == 8;
M.step[0] == 4 * 6 * M.elemSize() == 192;
M.step[1] == 6 * M.elemSize() == 48;
M.step[2] == M.elemSize() == 8;
M.step1(0) == M.step[0] / M.elemSize() == 48 / 2 == 96 (第一维度(即面的元素个数) * 通道数);
M.step1(1) == M.step[1] / M.elemSize() == 12 / 2 == 24(第二维度(即行的元素个数/列宽) * 通道数);
M.step1(2) == M.step[2] / M.elemSize() == M.channels() == 4(第三维度(即元素) * 通道数);
End :
Author : Ggicci
本文讲解Mat 的一些基本的初始化
// m为3*5的矩阵,float型的单通道,把每个点都初始化为1
Mat m(3, 5, CV_32FC1, 1);
或者 Mat m(3, 5, CV_32FC1, Scalar(1));
cout<<m;
输出为:
[1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1]
// m为3*5的矩阵,float型的2通道,把每个点都初始化为1 2
Mat m(3, 5, CV_32FC2, Scalar(1, 2));
cout<<m;
输出为
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2;
1, 2, 1, 2, 1, 2, 1, 2, 1, 2;
1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
// m为3*5的矩阵,float型的3通道,把每个点都初始化为1 2 3
Mat m(3, 5, CV_32FC3, Scalar(1, 2, 3));
cout << m;
输出为
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3;
1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3;
1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
// 从已有的数据源初始化
double *data = new double[15];
for (int i = 0; i < 15; i++)
{
data[i] = 1.2;
}
Mat m(3, 5, CV_32FC1, data);
cout << m;
输出为:
[1.2, 1.2, 1.2, 1.2, 1.2;
1.2, 1.2, 1.2, 1.2, 1.2;
1.2, 1.2, 1.2, 1.2, 1.2]
如果接着
delete [] data;
cout << m;
输出为:
[-1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
-1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
-1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144]
可见,这里只是进行了浅拷贝,当数据源不在的时候,Mat里的数据也就是乱码了。
// 从图像初始化
Mat m = imread("1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
cout<< "channels ="<<m.channels()<<endl;
cout << "cols ="<<m.cols<<endl;
cout << "rows ="<<m.rows<<endl;
cout << m;
输出为:
channels =1
cols =13
rows =12
[179, 173, 175, 189, 173, 163, 148, 190, 68, 14, 19, 31, 22;
172, 172, 172, 180, 172, 177, 162, 190, 64, 13, 19, 30, 17;
177, 180, 176, 175, 169, 184, 165, 181, 58, 12, 23, 38, 25;
181, 183, 178, 178, 170, 181, 163, 182, 52, 8, 23, 37, 23;
176, 173, 173, 184, 175, 178, 164, 195, 60, 14, 24, 35, 16;
179, 175, 176, 187, 176, 175, 158, 191, 70, 21, 28, 37, 20;
182, 183, 180, 184, 174, 179, 155, 174, 54, 1, 5, 15, 2;
173, 182, 178, 176, 173, 191, 165, 169, 157, 101, 100, 107, 93;
181, 182, 180, 177, 177, 177, 171, 162, 183, 185, 186, 185, 182;
178, 180, 179, 177, 178, 179, 174, 167, 172, 174, 175, 174, 172;
175, 178, 179, 178, 180, 182, 179, 173, 172, 174, 175, 175, 174;
175, 179, 181, 180, 181, 183, 181, 177, 178, 180, 182, 183, 182]
内容来自《OpenCV 2 Computer Vision Application Programming Cookbook》
OpenCV2 访问图像的各个像素有各种方法
我们来用各种方法来实现减少图像的颜色数量
color = color/div*div +div/2;
若div为8,则原来RGB每个通道的256种颜色减少为32种。
若div为64,则原来RGB每个通道的256种颜色减少为4种,此时三通道所有能表示的颜色有4×4×4 = 64 种
首先,我们来看一个函数
C++: uchar* Mat::ptr(int i=0)
i 是行号,返回的是该行数据的指针。
在OpenCV中,一张3通道图像的一个像素点是按BGR的顺序存储的。
先来看看第一种访问方案
void colorReduce1(cv::Mat& image, cv::Mat& result, int div=64){
int nrow = image.rows;
int ncol = image.cols * image.channels();
for(int i=0; i<nrow; i++){
uchar* data = image.ptr<uchar>(i);
uchar* data_out = result.ptr<uchar>(i);
for(int j=0; j<ncol; j++){
data_out[j] = data[j]/div*div +div/2;
}
}
}
第二种方案:
先来看如下函数:
C++: bool Mat::isContinuous() const
C++: Mat Mat::reshape(int cn, int rows=0) const
因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种4或是8倍数的行。出于性能方面的考虑,在图像每一行的最后可能会填充一些像素,这样图像的数据就不是连续的了
我们可以用函数isContinuous()来判断图像的数据是否连续
reshape函数的作用如下:
Changes the shape and/or the number of channels of a 2D matrix without copying the data.
这样,我们就提出了对第一种方法的改进
void colorReduce2(cv::Mat& image, cv::Mat& result, int div){
if(image.isContinuous()){
image.reshape(1,image.cols*image.rows);
}
int nrow = image.rows;
int ncol = image.cols * image.channels();
for(int i=0; i<nrow; i++){
uchar* data = image.ptr<uchar>(i);
uchar* data_out = result.ptr<uchar>(i);
for(int j=0; j<ncol; j++){
data_out[j] = data[j]/div*div +div/2;
}
}
}
或:
1 void colorReduce(const Mat& image,Mat& outImage,int div)
2 {
3 int nr=image.rows;
4 int nc=image.cols;
5 outImage.create(image.size(),image.type());
6 if(image.isContinuous()&&outImage.isContinuous())
7 {
8 nr=1;
9 nc=nc*image.rows*image.channels();
10 }
11 for(int i=0;i<nr;i++)
12 {
13 const uchar* inData=image.ptr<uchar>(i);
14 uchar* outData=outImage.ptr<uchar>(i);
15 for(int j=0;j<nc;j++)
16 {
17 *outData++=*inData++/div*div+div/2;
18 }
19 }
20 }
}
}
但是一定要注意了,这个例子其实是不对的!因为我们说过,分配矩阵内存空间时,是以四字节为最小单位的,这就很有可能有不到四个字节而取成四个字节的情况,所以,如果用矩阵首地址从头到尾指下去访问数据,就很有可能访问到不是数据的字节上去!这一点请务必牢记!!
综上所述,如果要直接访问矩阵中数据,请记住使用step的方案。
另一个需要知道的情况是,我们需要了解一个多维数组(矩阵)和一个一维,但是包含高维数据的数组之间的区别。假设,你有n个点(每个点有x,y,z坐标值)需要保存到CvMat*中,你其实有四种方式可以使用,但这四种方式的存储形式不同。你可能使用一个二维矩阵,矩阵大小为n行3列,数据类型为CV32FC1。你还可以使用一个二维矩阵,矩阵大小为3行n列,数据类型为CV32FC1;第三种可能性是,你使用一个一维矩阵,n行1列,数据类型为CV32FC3;最后,你还可以使用1行三列,数据类型为CV32FC3.这几种方式,在内存分配上,有些是相同的,有些是不同的,如下所示:
n个点的集合(n=5);
(x0 y0 z0) (x1 y1 z1) (x2 y2 z2) (x3 y3 z3) (x4 y4 z4)
n行1列时(数据类型CV32FC3)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
1行n列时(数据类型CV32FC3)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
n行3列时(数据类型CV32FC1)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
3行n列时(数据类型CV32FC1)内存分配情况
x0 x1 x2 x3 x4 y0 y1 y2 y3 y4 z0 z1 z2 z3 z4
我们可以看出,前三种的内存分配情况相同,但最后一种的内存分配不同。更复杂的是,如果有n维数组,每个数组的元素是c维(c可能是通道数)时。所以,多维数组(矩阵)和一个一维但包含多维数据的数组一般是不同的。
对于一个Rows行Cols列,通道数为Channels的矩阵,访问其中第row行,第col列,第channel通道的数据,可以使用如下公式:
数据地址偏移量=row*Cols*Channels+col*Channels+channel