OpenCV中Mat数据结构

转自:

豆瓣作者Lirpa,不知道为何一贴上链接就非法···


第一部分:Mat数据存储方式

首先看Opencv官方文档中:

注意到地址计算公式,那么step这个数组到底是什么呢?如此的神奇!

先来看一下Opencv中Mat数据结构:

class CV_EXPORTS Mat
{
public:
    /*
	* functions
	*/
    enum { MAGIC_VAL=0x42FF0000, AUTO_STEP=0, CONTINUOUS_FLAG=CV_MAT_CONT_FLAG, SUBMATRIX_FLAG=CV_SUBMAT_FLAG };

    /*! includes several bit-fields:
         - the magic signature ---//Magic number-wikipedia
         - continuity flag
         - depth
         - number of channels
     */
    int flags;
    //! the matrix dimensionality, >= 2
    int dims;
    //! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
    int rows, cols;
    //! pointer to the data
    uchar* data;

    //! pointer to the reference counter;
    // when matrix points to user-allocated data, the pointer is NULL
    int* refcount;
    
    //! helper fields used in locateROI and adjustROI
    uchar* datastart;
    uchar* dataend;
    uchar* datalimit;
    
    //! custom allocator
    MatAllocator* allocator;
    
    struct CV_EXPORTS MSize
    {
        MSize(int* _p);
        Size operator()() const;
        const int& operator[](int i) const;
        int& operator[](int i);
        operator const int*() const;
        bool operator == (const MSize& sz) const;
        bool operator != (const MSize& sz) const;
        
        int* p;
    };
    
    struct CV_EXPORTS MStep
    {
        MStep();
        MStep(size_t s);
        const size_t& operator[](int i) const;
        size_t& operator[](int i);
        operator size_t() const;
        MStep& operator = (size_t s);
        
        size_t* p;
        size_t buf[2];
    protected:
        MStep& operator = (const MStep&);
    };
    
    MSize size;
    MStep step;
};


下面是上面注释部分的内容解释:

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 (step / elemSize1),指的是每个对应维度元素的个数M.step[m-1] 总是等于 elemSize,M.step1(m-1)总是等于 channels;


elemSize : 矩阵中每一个元素的数据大小,如果Mat中的数据的数据类型是 CV_8U 那么 elemSize = 1,CV_8UC3 那么 elemSize = 3,CV_16UC2 那么 elemSize = 4;记住另外有个 elemSize1 表示的是矩阵中数据类型的大小,即 elemSize / channels 的大小

依照上图举个例子:

上面是一个 3 X 4 的矩阵,假设其数据类型为 CV_8UC3,也就是三通道的 uchar 类型

M.dims == 2; M.channels() == 3;M.depth() == 0;
M.elemSize() == 3//每一个元素所占字节数,即nchannels个uchar字节长度

M.elemSize1() == 1 (elemSize / channels)//每一个元素类型所占字节数,即uchar所占字节数
M.step[0] == M.cols * M.elemSize() == 12, //每一行所占字节数,对应Iplimage中的stepWidth

M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;//每第一维度的第二维度单位长度,这儿指一个元素所占字节数,所以有M.steps[m-1] = elemSize
M.step1(0) == M.cols * M.channels() == 12 //第一维(行)数据元素个数

; M.step1(1) == M.channels() == 3;//第一维度中第二维(每个元素)数据元素个数,即通道个数


上面是一个 3 X 4 X 6 的矩阵,注意第一维指的是面的个数。假设其数据类型为 CV_16SC4,也就是 short 类型

M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;
M.rows == M.cols == –1;//参见Mat数据结构注释
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(第三维度(即元素) * 通道数);


第二部分: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]

第三部分:Mat 的访问方式

我们来用各种方法来实现减少图像的颜色数量
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

出于性能方面的考虑,在图像每一行的最后可能会填充一些像素,这样图像的数据就不是连续的了

我们可以用函数isContinuous()来判断图像的数据是否连续

这样,我们就提出了对第一种方法的改进,当然除此之外还可以将

void colorReduce2(cv::Mat& image, cv::Mat& result, int div){
    if(image.isContinuous()){
        int ncol * =image.cols*image.rows;//可以替代为image.reshape(1,image.cols*image.rows);这样会不改变内存的情况下将Mat当行向量处理
    }
    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++: template<typename T> T& Mat::at(int i, int j)
其作用是Returns a reference to the specified array element.

void colorReduce3(cv::Mat& image, cv::Mat& result, int div){
    int nrow = image.rows;
    int ncol = image.cols * image.channels();
    for(int i=0; i<nrow; i++){
        for(int j=0; j<ncol; j++){
            image.at<cv::Vec3b>(j,i)[0]= image.at<cv::Vec3b>(j,i)[0]/div*div + div/2;
            image.at<cv::Vec3b>(j,i)[1]= image.at<cv::Vec3b>(j,i)[1]/div*div + div/2;
            image.at<cv::Vec3b>(j,i)[2]= image.at<cv::Vec3b>(j,i)[2]/div*div + div/2;
        }
    }
}

第四种方案是使用迭代器(比较推荐使用,在面向对象编程中常用)

Mat_<返回类型>::iterator it;或MatIterator_ <返回类型> it;  还可以指定const_Iterator来说明你不想修改image的值或image本身就是const image,此时使用:

Mat_<返回类型>::const_iterator it;或MatConstIterator_<返回类型> it;

void colorReduce4(cv::Mat& image, cv::Mat& result, int div){
    cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();//注意尖括号中为迭代器返回值,必须在编译时确定。
    cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
    cv::Mat_<cv::Vec3b>::iterator itout = result.begin<cv::Vec3b>();
    for(; it!=itend; ++it,++itout){
        (*itout)[0] = (*it)[0]/div*div + div/2;
        (*itout)[1] = (*it)[1]/div*div + div/2;
        (*itout)[2] = (*it)[2]/div*div + div/2;
    }
}


第四部分:Mat中step和Iplimage中的stepWidth

转自http://blog.csdn.net/ljbkiss/article/details/7369947

参考第三部分可以知道:


其中M.size[k]是第k维长度,ex.在rows*cols=300*200的二维图像中,第0维的size,即M.size[0]=300,M.size[1]=200,M.dims=2。


step[k],即步长可以看作是与第k维的存储单位,在2维的矩阵中,因为存储是按照行的顺序存储的,整个矩阵存储为一个平面,所以第k=0维的步长也就是单位肯定就是一行所占的字节数;如果是3维的话,第0维是按照面为单位来存储的,第1维是按照行为单位来存储的,第2维是按照元素类型为单位存储的,每个元素类型是基本类型(即uchar,float,short等等)与通道数的乘积...;
也就是基本数据类型与通道数组成元素,多个元素组成了行,多行组成了面,多个面组成了3维体,多个3维体 组成4维超体。。。以此类推,如此看来某一维的步长应该等于低一维的步长step(该维所占字节数)*低一维的大小size,那么>=是怎么回事?
这就是内存对齐啦!
为了提高计算速度,对数据存储按照字长进行了对齐,在32位机器中是按照4字节对齐的,就像一个row=100,cols=101大小的单通道uchar图像,如果进行字节对齐则其step[0]=104,而不是101;
##注##--使用cv::Mat时,如果数据是从图像加载的,或者使用构造函数、create等创建分配的数据,是没有字节对齐的,所有的数据都是顺序存储,是continuous的,但是如果
  (1)是使用自己的数据,然后用了Mat的头来访问,而指定的step不是AUTO_STEP的话,那么是否存在字节对齐就不一定了,但是如果是指定了step=AUTO_STEP则没有字节对齐,这个可以从手册中使用外部数据的构造函数对step参数的介绍中知道。
  (2)图像是用IplImage加载的,然后转换为cv::Mat那就存在字节对齐的现象(不复制数据时),如果复制数据进行初始化则不会内存对齐。

总结:Mat默认不内存对齐,所有数据连续存储。对于Iplimage和CvMat这种原就默认内存对齐的情况,如果直接创建矩阵头初始化而不复制数据(默认方式),则依旧内存对齐mat.step[0]=image->stepWidth;因为没有重新分配内存
如果明确指定要内存对齐,那肯定就要内存对齐啦!

下面给出一个关于step,size的测试例子,从其输出中可以验证这些


Mat load_mat = imread("d:/picture/temp1.bmp");
	cout<<"step[0]="<<load_mat.step[0]<<",size[0]="<<load_mat.size[0]<<",step[1]="<<load_mat.step[1]<<",size[1]="<<load_mat.size[1]<<endl;
	cout<<"rows="<<load_mat.rows<<",cols="<<load_mat.cols<<"elemSize="<<load_mat.elemSize()<<",continuous="<<load_mat.isContinuous()<<endl;
	cout<<"----------------------------------------------------"<<endl;

	Mat mem_mat(101,104, CV_8UC3, Scalar::all(255));
	cout<<"step[0]="<<mem_mat.step[0]<<",size[0]="<<mem_mat.size[0]<<",step[1]="<<mem_mat.step[1]<<",size[1]="<<mem_mat.size[1]<<endl;
	cout<<"rows="<<mem_mat.rows<<",cols="<<mem_mat.cols<<"elemSize="<<mem_mat.elemSize()<<",continuous="<<mem_mat.isContinuous()<<endl;
	cout<<"----------------------------------------------------"<<endl;
	
	int sz[]={101,101,101};
	Mat cube(3, sz, CV_32FC3, Scalar::all(0));
	cout<<"step[0]="<<cube.step[0]<<",size[0]="<<cube.size[0]<<",step[1]="<<cube.step[1]<<",size[1]="<<cube.size[1]<<endl;
	cout<<"step[2]="<<cube.step[2]<<",size[2]="<<cube.size[2]<<",step1*size1="<<cube.step[1]*cube.size[1]<<endl;
	cout<<"step2*size2="<<cube.size[2]*cube.step[2]<<",elemSize="<<cube.elemSize()<<",continuous="<<cube.isContinuous()<<endl;
	cout<<"rows="<<cube.rows<<",cols="<<cube.cols<<endl;
	cout<<"----------------------------------------------------"<<endl;

	uchar data[1212]={0};
	Mat createdmat(cv::Size(101,12),CV_8UC1, data, 104);
	cout<<"step[0]="<<createdmat.step[0]<<",size[0]="<<createdmat.size[0]<<",step[1]="<<createdmat.step[1]<<",size[1]="<<createdmat.size[1]<<endl;
	cout<<"rows="<<createdmat.rows<<",cols="<<createdmat.cols<<"elemSize="<<createdmat.elemSize()<<",continuous="<<createdmat.isContinuous()<<endl;
	cout<<"----------------------------------------------------"<<endl;
	
	Mat createdmat2(cv::Size(101,12),CV_8UC1, data);
	cout<<"step[0]="<<createdmat2.step[0]<<",size[0]="<<createdmat2.size[0]<<",step[1]="<<createdmat2.step[1]<<",size[1]="<<createdmat2.size[1]<<endl;
	cout<<"rows="<<createdmat2.rows<<",cols="<<createdmat2.cols<<"elemSize="<<createdmat2.elemSize()<<",continuous="<<createdmat2.isContinuous()<<endl;
	cout<<"----------------------------------------------------"<<endl;

	IplImage *image = cvLoadImage("d:/picture/temp1.bmp");
	cout<<"widthstep="<<image->widthStep<<",height="<<image->height<<",width="<<image->width<<endl;
	Mat cvted(image);
	cout<<"step[0]="<<cvted.step[0]<<",size[0]="<<cvted.size[0]<<",step[1]="<<cvted.step[1]<<",size[1]="<<cvted.size[1]<<endl;
	cout<<",step1*size1="<<cvted.step[1]*cvted.size[1]<<"elemSize="<<cvted.elemSize()<<",continuous="<<cvted.isContinuous()<<endl;
	cout<<"=========================================================="<<endl;

	cout<<"sizeof(loadmat)="<<sizeof(load_mat)<<",sizeof(memmat)="<<sizeof(mem_mat)<<",sizeof(cube)="<<sizeof(cube)<<endl;
	cout<<"sizeof(createdmat)="<<sizeof(createdmat)<<",sizeof(createdmat2)="<<sizeof(createdmat2)<<",sizeof(cvted)="<<sizeof(cvted)<<endl;
		
	cvReleaseImage(&image);

下面是输出:


结合上面的分析,可以看出第一种情况是使用imread加载的图像,虽然图像的cols=355,但是其step[0]=1065=355*3 没有字节对齐,从continuous=1也可以看出,下面的第二种情况也是,第三种情况是3维矩阵,也没有进行字节对齐,此时可以看出cols=rows=-1;

第四种情况,使用外部数据,指定了step=104进行字节对齐,可以看出此时step[0]!=step[1]*size[1]了,continuous=0,

但是第五种情况下,step采用默认AUTO_STEP时,没有字节对齐---因为一般只是给连续数组空间加上矩阵头,不重新分配存储空间当然不内存对齐,

第六种情况,采用IplImage加载与第一种同一幅图像时,可以看出此时使用了字节对齐,因为存储区没变化(并没有重新分配存储空间)其widthstep等于转换为Mat之后的step[0]的值,大于step[1]*size[1],continuous=0,

在这儿需要加上第七种情况,就是采用Mat初始化Iplimage并重新分配存储空间时:

	IplImage *image =cvCreateImage(cvSize(3,3),IPL_DEPTH_8U,3);
	Mat img_matF(image,false);
	Mat img_matT(image,true);
	
	cout<<image->widthStep<<endl;//12
	cout<<img_matF.step[0]<<endl;//12
	cout<<img_matT.step[0]<<endl;//9

另外几个常用的成员变量:
--------------------------------------------------
   int flags; 标志位,包含几个位域:
 --magic signature 魔法签名或者更确切的说应该是文件的签名,用于区分不同文件格式的标志,是文件的"身份证";在Mat构造函数中可以发现一些端倪, 在使用外部数据构造Mat的构造函数中:flags(MAGIC_VAL + (_type & TYPE_MASK)),在默认构造函数中:flags(0),在imread图像加载时应该也进行了相应的设置【没有确认?】,
 关于该数字签名的一些相关东西,可以参考一下WikiPedia的Magic number词条
 --continuity flag 即是否是连续存储的,标志有没有使用字节对齐
 --depth 元素深度即基本元素类型,uchar,short,float等
 --number of channels 通道数
 --------------------------------------------------
   cols,rows 矩阵的行数,列数【注意,在图像中行数对应的是高度,列数对应的是宽度】,当维数大于2时,均为-1;
   dims 矩阵的维数;
   uchar* data; 存储具体数据的地址指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值