【学习OpenCV】——Mat类详解

官方文档说明:点击打开链接

C++的接口,基于opencv 2.4.9

Mat类

class CV_EXPORTS Mat
{
public:
    // ... a lot of methods ...
    ...

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

    //! pointer to the reference counter;
    // when array points to user-allocated data, the pointer is NULL
    int* refcount;

    // other members
    ...
};

Mat元素的寻址:

原理:

The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. It can be used to store real or complex-valued vectors and 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 ). The data layout of the array M is defined by the arrayM.step[], so that the address of element where is computed as:

In case of a 2-dimensional array, the above formula is reduced to:

M.step代表矩阵在各个维度上的大小,例如上面的 i 代表行下标,j 代表列下标,那么M.step[0] 就是行数,M.step[1] 就是列数;M.data是首地址,也就是M[0][0] 

方法1:

M.at<double>(i,j) += 1.f;

如果是遍历矩阵,不要用这种方法费时,at 适用于随机访问。实践中发现,在release版本的代码中,at方法的效率与指针访问无异,所以为了可读性,还是采用at方法吧

方法2:
按照原理所述用指针访问,详情请参考:【学习OpenCV】指针访问Mat元素

图像的遍历

详情请参考:【学习OpenCV】高效遍历Mat

分行、列处理:

// compute sum of positive matrix elements
// (assuming that M isa double-precision matrix)
double sum=0;
for(int i = 0; i < M.rows; i++)
{
    const double* Mi = M.ptr<double>(i);
    for(int j = 0; j < M.cols; j++)
        sum += std::max(Mi[j], 0.);
}


将图像视为一维:

// compute the sum of positive matrix elements, optimized variant
double sum=0;
int cols = M.cols, rows = M.rows;
if(M.isContinuous())
{
    cols *= rows;
    rows = 1;
}
for(int i = 0; i < rows; i++)
{
    const double* Mi = M.ptr<double>(i);
    for(int j = 0; j < cols; j++)
        sum += std::max(Mi[j], 0.);
}

这里需要一个条件:图像是连续的。原因:

为了便于计算机处理,图像的行列会被扩充至2的n次幂。Mat的成员函数isContinuous可用于判断图像是否进行过填补,返回真时说明没有进行填补,可以视为一维数组。

多通道图像访问

通过模板类template<typename _Tp, int cn> class Vec : public Matx<_Tp, cn, 1>

typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;

typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;

typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;

typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec6i;
typedef Vec<int, 8> Vec8i;

typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;

typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;

例如访问ARGB图像的一个像素:

cv::Vec4b value = img.at<cv::Vec4b>(y, x);
uchar bvalue = value[0];
uchar gvalue = value[1];
uchar rvalue = value[2];
uchar avalue = value[3];

基于STL的迭代器方法:

// compute sum of positive matrix elements, iterator-based variant
double sum=0;
MatConstIterator_<double> it = M.begin<double>(), it_end = M.end<double>();
for(; it != it_end; ++it)
    sum += std::max(*it, 0.);

因为采用了模板,效率没有上面的高

Mat的创建

// make a 7x7 complex matrix filled with 1+3j.
Mat M(7,7,CV_32FC2,Scalar(1,3));
// and now turn M to a 100x60 15-channel 8-bit matrix.
// The old content will be deallocated
M.create(100,60,CV_8UC(15));
// create a 100x100x100 8-bit array
int sz[] = {100, 100, 100};
Mat bigCube(3, sz, CV_8U, Scalar::all(0));

Mat的局部操作

访问某行(列)

// add the 5-th row, multiplied by 3 to the 3rd row
M.row(3) = M.row(3) + M.row(5)*3;

// now copy the 7-th column to the 1-st column
// M.col(1) = M.col(7); // this will not work
Mat M1 = M.col(1);
M.col(7).copyTo(M1);

roi的使用,配合Rect

// create a new 320x240 image
Mat img(Size(320,240),CV_8UC3);
// select a ROI
Mat roi(img, Rect(10,10,100,100));
// fill the ROI with (0,255,0) (which is green in RGB space);
// the original 320x240 image will be modified
roi = Scalar(0,255,0);

range的使用(matlab风格)

Mat A = Mat::eye(10, 10, CV_32S);
// extracts A columns, 1 (inclusive) to 3 (exclusive).
Mat B = A(Range::all(), Range(1, 3));
// extracts B rows, 5 (inclusive) to 9 (exclusive).
// that is, C ~ A(Range(5, 9), Range(1, 3))
Mat C = B(Range(5, 9), Range::all());
Size size; Point ofs;
C.locateROI(size, ofs);
// size will be (width=10,height=10) and the ofs will be (x=1, y=5)

例子详见:【学习OpenCV】矩阵的ROI拷贝_Kelvin_Ngan的博客-CSDN博客

矩阵运算

详情请参考:【学习OpenCV】矩阵操作的函数

matlab风格的矩阵运算,整体进行,不需要循环

	double m[3][3] = {{2, 124, 14}, {5, 1, 245}, {234, 56, 1}}; 
	Mat M = Mat(3, 3, CV_64F, m);
	Mat N;
	divide(M,256,N);
	std::cout<<"\n"<<N<<endl;

类似于matlab中的对角、零矩阵、全一矩阵:

Mat E = Mat::eye(4,4,CV_64F);

Mat E = Mat::ones(4,4,CV_64F);

Mat E = Mat::zeros(4,4,CV_64F);

Mat的浅拷贝和深拷贝

赋值运算符和拷贝构造函数属于浅拷贝, 只拷贝信息头和矩阵指针 ,而不拷贝矩阵。(通过任何一个对象所做的改变也会影响其它对象)

Mat A, C;      //只创建信息头部分  
A = imread(argv[1], CV_LOAD_IMAGE_COLOR);  //这里为矩阵开辟内存  
MatB(A);       //使用拷贝构造函数  
C = A;         //赋值运算符

同样,下面感兴趣区域的操作也属于浅拷贝:

Mat D(A, Rect(10, 10, 100, 100));    //using a rectangle  
Mat E = A(Range:all(), Range(1, 3)); //using row and column boundaries

注意:正因为Rect和Range获得的Mat属于浅拷贝,所以不能直接使用该Mat的data指针成员访问矩阵元素,如

cv::Mat total_mat(9,9,CV_16UC1);
cv::Mat local_mat = total_mat(cv::Rect(2,2,5,5));

/*

  0 1 2 3 4 5 6 7 8 
0 
1
2     - - - - -     
3     A - - - - B
4     - - - - -
5     - - - - -
6     - - - - -
7
8

*/

//访问local_mat的第二行、第一列的元素,即元素A
ushort* loc_ptr = (ushort*)local_mat.data;
val_loc = *(loc_ptr + 1 * 5 + 0);   //错误做法,通过指针寻址,此时获取的其实是元素B
local_mat.at<ushort>(1,0);  //正确做法,通过at方法


 clone() 或者 copyTo()属于深拷贝,使用时要注意开销

Mat F = A.clone();  
Mat G;  
A.copyTo(G);

Mat的初始化

double m[3][3] = {{a, b, c}, {d, e, f}, {g, h, i}};
Mat M = Mat(3, 3, CV_64F, m).inv();


下面这种需要用到Mat_类:

// create a 3x3 double-precision identity matrix
Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);

注:这种方法有一个好处,就是可以直接在matlab中复制数组数据,粘贴到C++中,然后在数据间添加逗号,因为opencv对二维转一维数组是先行后列的顺序,而C++数组初始化

则是按先列后行的方式。

Mat与旧接口的兼容(CvMat, IplImage , or CvMatND)

Partial yet very common cases of this user-allocated data case are conversions from CvMat and IplImage to Mat. For this purpose, there are special constructors taking pointers to CvMat orIplImage and the optional flag indicating whether to copy the data or not.

Backward conversion from Mat to CvMat or IplImage is provided via cast operators Mat::operator CvMat() const and Mat::operator IplImage(). The operators do NOT copy the data.

IplImage* img = cvLoadImage("greatwave.jpg", 1);
Mat mtx(img); // convert IplImage* -> Mat
CvMat oldmat = mtx; // convert Mat -> CvMat
CV_Assert(oldmat.cols == img->width && oldmat.rows == img->height &&
    oldmat.data.ptr == (uchar*)img->imageData && oldmat.step == img->widthStep);

Mat转CvMat

强制类型转换,CvMat * cvmat= (CvMat)mat;

CvMat转Mat

cvarrToMat函数

用Mat接收数据流

目标:用Mat来存放设备传输过来的或者文件中的二进制数据流。

优点:方便,对于一组先行后列(切记!)的数据,将其指针直接赋给Mat.data,之后就可以用Mat的方法对数据进行访问和各种操作。如果用数组或者自己定义的指针,那下标的处理将非常容易造成错误。

假设lena.dat前两个int16存放图像的高和宽,后面依次存放uchar大小的灰度值。

Mat src;
ifstream datfile ("I:\\project\\lena.dat", ios::in|ios::binary);
int16_t height;
int16_t width;
datfile.read ((char*)&height, sizeof(int16_t));
datfile.read ((char*)&width, sizeof(int16_t));	
src.create(height, width, CV_8UC1);
datfile.read ((char*)src.data, height*width*sizeof(datasize));
datfile.close();

Mat数据的内存属性

在对Mat数据进行的处理时,要非常注意其数据类型,这一点在底层的指针处理、数据输入输出时非常重要,一旦数据不能够对齐,内存中就是一团乱码。

OpenCV如何扫描图像、利用查找表和计时

主要的内存属性有3个:type、step、depth

type 

表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,其命名规则为CV_(位数)+(数据类型)+(通道数)。具体的有以下值: 

CV_8UC1CV_8UC2CV_8UC3CV_8UC4
CV_8SC1CV_8SC2CV_8SC3CV_8SC4
CV_16UC1CV_16UC2CV_16UC3CV_16UC4
CV_16SC1CV_16SC2CV_16SC3CV_16SC4
CV_32SC1CV_32SC2CV_32SC3CV_32SC4
CV_32FC1CV_32FC2CV_32FC3CV_32FC4
CV_64FC1CV_64FC2CV_64FC3CV_64FC4

这里U(unsigned integer)表示的是无符号整数,S(signed integer)是有符号整数,F(float)是浮点数。 
例如:CV_16UC2,表示的是元素类型是一个16位的无符号整数,通道为2. 
C1,C2,C3,C4则表示通道是1,2,3,4

C++中打印各类型标识的值:

	cout<<"CV_8U:      "<<CV_8U<<endl;
	cout<<"CV_8UC1:      "<<CV_8UC1<<endl;
	cout<<"CV_8UC2:      "<<CV_8UC2<<endl;
	cout<<"CV_8UC3:      "<<CV_8UC3<<endl;
	cout<<"CV_8UC4:      "<<CV_8UC4<<endl;
	cout<<"CV_8S:      "<<CV_8S<<endl;
	cout<<"CV_8SC1:      "<<CV_8SC1<<endl;
	cout<<"CV_8SC2:      "<<CV_8SC2<<endl;
	cout<<"CV_8SC3:      "<<CV_8SC3<<endl;
	cout<<"CV_8SC4:      "<<CV_8SC4<<endl;
	cout<<"CV_16U:      "<<CV_16U<<endl;
	cout<<"CV_16UC1:      "<<CV_16UC1<<endl;
	cout<<"CV_16UC2:      "<<CV_16UC2<<endl;
	cout<<"CV_16UC3:      "<<CV_16UC3<<endl;
	cout<<"CV_16UC4:      "<<CV_16UC4<<endl;
	cout<<"CV_16S:      "<<CV_16S<<endl;
	cout<<"CV_16SC1:      "<<CV_16SC1<<endl;
	cout<<"CV_16SC2:      "<<CV_16SC2<<endl;
	cout<<"CV_16SC3:      "<<CV_16SC3<<endl;
	cout<<"CV_16SC4:      "<<CV_16SC4<<endl;
	cout<<"CV_32S:      "<<CV_32S<<endl;
	cout<<"CV_32SC1:      "<<CV_32SC1<<endl;
	cout<<"CV_32SC2:      "<<CV_32SC2<<endl;
	cout<<"CV_32SC3:      "<<CV_32SC3<<endl;
	cout<<"CV_32SC4:      "<<CV_32SC4<<endl;
	cout<<"CV_32F:      "<<CV_32F<<endl;
	cout<<"CV_32FC1:      "<<CV_32FC1<<endl;
	cout<<"CV_32FC2:      "<<CV_32FC2<<endl;
	cout<<"CV_32FC3:      "<<CV_32FC3<<endl;
	cout<<"CV_32FC4:      "<<CV_32FC4<<endl;
	cout<<"CV_64F:      "<<CV_64F<<endl;
	cout<<"CV_64FC1:      "<<CV_64FC1<<endl;
	cout<<"CV_64FC2:      "<<CV_64FC2<<endl;
	cout<<"CV_64FC3:      "<<CV_64FC3<<endl;
	cout<<"CV_64FC4:      "<<CV_64FC4<<endl;

type一般是在创建Mat对象时设定,如果要取得Mat的元素类型,则无需使用type,使用下面的depth

depth 

矩阵中元素的一个通道的数据类型,这个值和type是相关的。例如 type为 CV_16SC2,一个2通道的16位的有符号整数。那么,depth则是CV_16S。depth也是一系列的预定义值, 
将type的预定义值去掉通道信息就是depth值: 
CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F

elemSize 

矩阵一个元素占用的字节数,例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes

elemSize1 

矩阵元素一个通道占用的字节数,例如:type是CV_16CS3,那么elemSize1 = 16  / 8 = 2 bytes = elemSize / channels

Mat img(3, 4, CV_16UC4, Scalar_<uchar>(1, 2, 3, 4));
    
    cout << img << endl;

    cout << "dims:" << img.dims << endl;
    cout << "rows:" << img.rows << endl;
    cout << "cols:" << img.cols << endl;
    cout << "channels:" << img.channels() << endl;
    cout << "type:" << img.type() << endl;
    cout << "depth:" << img.depth() << endl;
    cout << "elemSize:" << img.elemSize() << endl;
    cout << "elemSize1:" << img.elemSize1() << endl;

step


Mat中的step是一个MStep的一个实例。其声明如下:

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&);
    };


从其声明中可以看出,MStep和size_t有比较深的关系。用size_t作为参数的构造函数和重载的赋值运算符

MStep(size_t s);
MStep& operator = (size_t s);


向size_t的类型转换以及重载的[ ]运算符返回size_t

const size_t& operator[](int i) const;
        
size_t& operator[](int i);


size_t的数组以及指针 
size_t* p;       
size_t buf[2];
那么size_t又是什么呢,看代码
typedef  unsigned int   size_t;
size_t就是无符号整数。
再看一下MStep的构造函数,就可以知道其究竟保存的是什么了。

inline Mat::MStep::MStep(size_t s) { p = buf; p[0] = s; p[1] = 0; }

从MStep的定义可以知道,buff是一个size_t[2],而p是size_t *,也就是可以把MStep看做一个size_t[2]。那么step中保存的这个size_t[2]和Mat中的数据有何种关系呢。
step[0]是矩阵中一行元素的字节数。
step[1]是矩阵中一个元素的自己数,也就是和上面所说的elemSize相等。
上面说到,Mat中一个uchar* data指向矩阵数据的首地址,而现在又知道了每一行和每一个元素的数据大小,就可以快速的访问Mat中的任意元素了。

下面就对三维的Mat数据布局以及step(维度大于3的就算了吧)。

三维的数据在Mat中是按面来存储的,上图描述的很清晰,这里不再多说。 

上面言道,step是一个size_t[dims],dims是维度。so,三维的step就是size_t[3]。其余的不多说了,看图就有了。下面来创建一个三维的Mat,实际看看

int dims[3] = { 3, 3, 3 };
    Mat src(3, dims, CV_16SC2, Scalar_<short>(1,2));

    cout << "step[0]:" << src.step[0] << endl;
    cout << "step[1]:" << src.step[1] << endl;
    cout << "step[2]:" << src.step[2] << endl;

首先创建一个3*3*3,depth为CV_16S的两通道的Mat 
step[0]是一个数据面的大小  3 * 3 * (16 / 8 ) * 2 = 36 
step[1]是一行数据的大小 3 * (16 / 8 ) * 2 = 12 
step[2]是一个元素的大小 2 * (16 / 8) = 4

CV数据类型-type深度剖析

在创建和使用Mat时,经常要注意Mat的类型。CV内部对类型的定义如下:

#define CV_CN_MAX     512
#define CV_CN_SHIFT   3
#define CV_DEPTH_MAX  (1 << CV_CN_SHIFT)

#define CV_8U   0
#define CV_8S   1
#define CV_16U  2
#define CV_16S  3
#define CV_32S  4
#define CV_32F  5
#define CV_64F  6
#define CV_USRTYPE1 7

#define CV_MAT_DEPTH_MASK       (CV_DEPTH_MAX - 1)
#define CV_MAT_DEPTH(flags)     ((flags) & CV_MAT_DEPTH_MASK)

#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT))
#define CV_MAKE_TYPE CV_MAKETYPE

#define CV_8UC1 CV_MAKETYPE(CV_8U,1)
#define CV_8UC2 CV_MAKETYPE(CV_8U,2)
#define CV_8UC3 CV_MAKETYPE(CV_8U,3)
#define CV_8UC4 CV_MAKETYPE(CV_8U,4)
#define CV_8UC(n) CV_MAKETYPE(CV_8U,(n))

#define CV_8SC1 CV_MAKETYPE(CV_8S,1)
#define CV_8SC2 CV_MAKETYPE(CV_8S,2)
#define CV_8SC3 CV_MAKETYPE(CV_8S,3)
#define CV_8SC4 CV_MAKETYPE(CV_8S,4)
#define CV_8SC(n) CV_MAKETYPE(CV_8S,(n))

#define CV_16UC1 CV_MAKETYPE(CV_16U,1)
#define CV_16UC2 CV_MAKETYPE(CV_16U,2)
#define CV_16UC3 CV_MAKETYPE(CV_16U,3)
#define CV_16UC4 CV_MAKETYPE(CV_16U,4)
#define CV_16UC(n) CV_MAKETYPE(CV_16U,(n))

#define CV_16SC1 CV_MAKETYPE(CV_16S,1)
#define CV_16SC2 CV_MAKETYPE(CV_16S,2)
#define CV_16SC3 CV_MAKETYPE(CV_16S,3)
#define CV_16SC4 CV_MAKETYPE(CV_16S,4)
#define CV_16SC(n) CV_MAKETYPE(CV_16S,(n))

#define CV_32SC1 CV_MAKETYPE(CV_32S,1)
#define CV_32SC2 CV_MAKETYPE(CV_32S,2)
#define CV_32SC3 CV_MAKETYPE(CV_32S,3)
#define CV_32SC4 CV_MAKETYPE(CV_32S,4)
#define CV_32SC(n) CV_MAKETYPE(CV_32S,(n))

#define CV_32FC1 CV_MAKETYPE(CV_32F,1)
#define CV_32FC2 CV_MAKETYPE(CV_32F,2)
#define CV_32FC3 CV_MAKETYPE(CV_32F,3)
#define CV_32FC4 CV_MAKETYPE(CV_32F,4)
#define CV_32FC(n) CV_MAKETYPE(CV_32F,(n))

#define CV_64FC1 CV_MAKETYPE(CV_64F,1)
#define CV_64FC2 CV_MAKETYPE(CV_64F,2)
#define CV_64FC3 CV_MAKETYPE(CV_64F,3)
#define CV_64FC4 CV_MAKETYPE(CV_64F,4)
#define CV_64FC(n) CV_MAKETYPE(CV_64F,(n))


其中单通道类型:

#define CV_8U   0
#define CV_8S   1
#define CV_16U  2
#define CV_16S  3
#define CV_32S  4
#define CV_32F  5
#define CV_64F  6
#define CV_USRTYPE1 7

多通道的时候按单通道计算得到:

#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT))

声明的值 = 数据类型的编号 + (通道数 - 1) * 8

---------------------------------------------------------------------------

2019.7  

慎用copyTo方法,发现opencv3.10的该方法会导致异常,程序崩溃在ipp源码了

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值