OpenCV(2)-Mat數據結構及訪問Mat中像素


Mat數據結構

一開始OpenCV是基於C語言的,在比較早的教材例如《學習OpenCV》中,講解的存儲圖像的數據結構還是IplImage,這樣需要手動管理內存。現在存儲圖像的基本數據結構是Mat
Mat是opencv中保存圖像數據的基本容器。其定義如下:

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類可以表示n維的單通道或多通道數組,它可以存儲實數/復數的向量和矩陣,單色或彩色圖像等。向量\(M\)的布局是由數組\(M.step[]\)決定的,元素\((i_0, ..., i_{M.dims-1})\)的地址為(其中\(0 \leq i_k < M.size[k]\)):

\[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} \]

Mat對象的數據布局和CvMat、Numpy等兼容,實際上它和以step(strides)方式計算像素地址方式的數據結構兼容。
在上面的數據結構可以看出,Mat數據結構中指針信息可以共享,即矩陣頭信息獨立,矩陣數據可以共享,使用引用計數器,類似智能指針。這樣用戶使用時,用戶可以分配Mat的頭信息,共享數據信息,並在原地處理信息,這樣可以極大的節省內存。

創建Mat對象

1、使用構造函數Mat(nrows, ncols, type[, fillValue])或者create(nrows, ncols, type)
這樣可以創建一個nrows行,ncol列的矩陣,類型為type。例如CV_8UC1表示8位單通道, CV_32FC2表示雙通道32位floating-point雙通道。

// 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));

對於type,格式為CV_位數+數值類型+C通道數,例如:
CV_8UC1表示:單通道陣列,8bit無符號整數
CV_8US2表示:2通道陣列,8bit有符號整數)
2、創建多維矩陣

// create a 100x100x100 8-bit array
int sz[] = {100, 100, 100};
Mat bigCube(3, sz, CV_8U, Scalar::all(0));

3、使用拷貝構造函數或賦值操作符時,只是創建了矩陣頭,共享了矩陣信息,時間復雜度為O(1)。Mat::clone()函數是深拷貝,拷貝了Mat的所有信息。

4、只創建信息頭部分,時間復雜度為O(1),可以使用這個特征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);

// 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);

可以創建ROI(Region of interest)區域

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)

5、使用用戶開辟的數據創建Mat的header部分

6、使用Mat::eye(), Mat::zeros(), Mat::ones()創建矩陣;或使用Mat_<destination_type>()

Mat E = Mat::ones(2, 2, CV_32F);
Mat O = (Mat_<float>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);

矩陣運算

Mat支持矩陣運算。A、B表示Mat類型對象,s表示標量,alpha表示實數,支持以下運算:
** 加:A+B, A-B, A+s, s+B,-A
** 縮放:Aalpha
** 對應像素相乘/除:A.mul(B),A/B, alpha/A。這里要求A、B大小相等,數據類型通道數一致。
** 轉置:A.t(),相當於A^T
** 求逆和偽逆矩陣。A.inv([method])寶石求逆,A.inv([method])
B表示求X,其中X滿足AX=B
** 邏輯位操作 A & B, ~A
**內積:A.cross(B), A.dot(B),表示對應像素相乘,求和。

常用接口

1、C++: size_t Mat::total() const
返回像素總數
2、C++: int Mat::depth() const
返回矩陣type類型對應的數值。
3、C++: int Mat::channels() const
返回通道數
4、C++: bool Mat::empty() const
Mat::total() = 0 或 Mat::data = NULL,則方法返回 true


訪問Mat中的像素

Mat中存儲的圖像像素,具體如何存儲取決於使用的顏色模型和通道數,例如RGB圖像對應的存儲矩陣如下

image

RGB存儲的子列通道是反過來的:BGR。如果內存足夠大,可以連續存儲,通過方法Mat::isContinuous()可以判斷矩陣是否連續。
訪問圖像的像素,即訪問某位置像素在內存中對應的地址。以提取彩色RGB圖像某一通道圖像為例:可以有如下方法:

1、使用指針

Mat存儲的圖像,每一行都是連續的,可以取得每一行開頭指針來訪問圖像像素。例如提取一副圖像中R通道的圖像,G、B通道像素全部置零,可以獲取每一行開頭的指針,使用指針遍歷每一行的所有像素。如果圖像在內存中的存儲是連續的,還可以一次遍歷所有像素。

/*
original:原圖像
new_image:新圖像
channel:提取的通道 0 1 2分別表示RGB
*/
void ExtractRGB(Mat& original, Mat& new_image, int channel){
	CV_Assert(channel < 3);
	// accept only char type matrices
	CV_Assert(original.depth() != sizeof(uchar));

	int channels = original.channels();
	//只接受3通道圖像
	CV_Assert(channels == 3);

	new_image = original.clone();

	int nRows = new_image.rows;
	int nCols = new_image.cols * channels;

	if (new_image.isContinuous())
	{
		nCols *= nRows;
		nRows = 1;
	}

	int i, j;
	uchar* p;
	for (i = 0; i < nRows; ++i)
	{
		p = new_image.ptr<uchar>(i);
		for (j = 0; j < nCols; ++j)
		{
			if (0 == (j + 1 + channel) % 3){
				//保留
			}
			else
				p[j] = 0;
			
		}
	}
	return ;
}

2、使用迭代器

在上面的使用裸指針的方法,不安全,不注意的話會造成內存越界訪問。迭代器是封裝了的指針,相對指針更加安全。

/*
original:原圖像
new_image:新圖像
channel:提取的通道 0 1 2分別表示BGR
*/
void ExtractRGBIterator(Mat& original, Mat& new_image, int channel){
	CV_Assert(channel < 3);
	// accept only char type matrices
	CV_Assert(original.depth() != sizeof(uchar));

	int channels = original.channels();
	//只接受3通道圖像
	CV_Assert(channels == 3);

	new_image = original.clone();

	int i = (channel + 1) % 3;
	int j = (channel + 2) % 3;

	MatIterator_<Vec3b> it, end;
	//3通道的圖像,迭代器對應三個像素(*it)[0]、(*it)[1]、(*it)[2]
	for (it = new_image.begin<Vec3b>(), end = new_image.end<Vec3b>(); it != end; ++it)
	{
		(*it)[i] = 0;
		(*it)[j] = 0;
	}
}

3、實時計算

如果想隨機獲取某一位置像素,例如(i,j)出的像素,要動態實時計算其偏移,OpenCV提供相關接口

/*
original:原圖像
new_image:新圖像
channel:提取的通道 0 1 2分別表示BGR
*/
void ExtractRGBRandomAcccess(Mat& original, Mat& new_image, int channel){
	CV_Assert(channel < 3);
	// accept only char type matrices
	CV_Assert(original.depth() != sizeof(uchar));

	int channels = original.channels();
	//只接受3通道圖像
	CV_Assert(channels == 3);

	new_image = original.clone();

	Mat_<Vec3b> _I = new_image;

	int m = (channel + 1) % 3;
	int n = (channel + 2) % 3;

	for (int i = 0; i < new_image.rows; ++i)
		for (int j = 0; j < new_image.cols; ++j)
		{
			_I(i, j)[m] = 0;
			_I(i, j)[n] = 0;
			
		}
}

以上3個方法中,第一種最快,第三種最慢;因為第三種是隨機訪問像素使用的,每次都會計算(i,j)像素對應的地址。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM