1. 程式人生 > >【學習OpenCV】——Mat類詳解

【學習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 (i_0,...,i_{M.dims-1}), where 0\leq i_k<M.size[k], is computed as:

addr(M_{i_0,...,i_{M.dims-1}}) = M.data + M.step[0]*i_0 + M.step[1]*i_1 + ... + M.step[M.dims-1]*i_{M.dims-1}

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

addr(M_{i,j}) = M.data + M.step[0]*i + M.step[1]*j

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方法吧

影象的遍歷

分行、列處理:

// 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可用於判斷影象是否進行過填補,返回真時說明沒有進行填補,可以視為一維陣列。

基於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)

矩陣運算

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

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資料進行的處理時,要非常注意其資料型別,這一點在底層的指標處理、資料輸入輸出時非常重要,一旦資料不能夠對齊,記憶體中就是一團亂碼。 主要的記憶體屬性有3個:type、step、depth

type

表示了矩陣中元素的型別以及矩陣的通道個數,它是一系列的預定義的常量,其命名規則為CV_(位數)+(資料型別)+(通道數)。具體的有以下值: 
CV_8UC1 CV_8UC2 CV_8UC3 CV_8UC4
CV_8SC1 CV_8SC2 CV_8SC3 CV_8SC4
CV_16UC1 CV_16UC2 CV_16UC3 CV_16UC4
CV_16SC1 CV_16SC2 CV_16SC3 CV_16SC4
CV_32SC1 CV_32SC2 CV_32SC3 CV_32SC4
CV_32FC1 CV_32FC2 CV_32FC3 CV_32FC4
CV_64FC1 CV_64FC2 CV_64FC3 CV_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