1. 程式人生 > >【學習OpenCV】Mat::data指標

【學習OpenCV】Mat::data指標

指標遍歷Mat

這是一個很簡單的問題,但是如果粗心大意寫錯了i和j,將會造成資料出錯。

為什麼要用指標訪問Mat?在Release模式下的at方法其實效率跟指標是一樣的,編碼時沒要為了效率犧牲可讀性而使用指標。但有一種場合必須使用指標,就是編寫opencv無關的API,例如寫dll函式時,呼叫方不想涉及任何關於opencv的東西,包括其資料結構,此時就不能採用Mat傳遞引數了,只能採用指標。因為Mat是C++的資料結構,如果在子函式內部定義了Mat,在該函式返回時會自動釋放掉Mat的資料,所以不要想著通過取Mat的資料指標來傳參。只能通過內部new一段記憶體,把Mat的資料逐個元素地扔進記憶體裡。

RGB圖(3維矩陣)

        BYTE* iPtr = new BYTE [height*width*3];
	for(int i=0;i<height;i++)
	{
		for(int j=0;j<width;j++)
		{
			for(int k=0;k<3;k++)
			{
				iPtr[i*width*3+j*3+k] = img.at<Vec3b>(i,j)[k];
			}
		}
	}
其中,img是一個3維uchar的Mat,Vec3b代表3個uchar

對於灰度圖、4維矩陣等,只要把通道數和at的資料型別改一下就可以套用以上格式

還有一點千萬注意,Mat的(i,j)是按(行,列)的規則,而影象中則是(高,寬),跟Size(x,y),Rect(x,y)的(x,y)是不同的

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

Mat::data的使用

在opencv中,Mat很方便;但當用到不是以opencv為主體的程式碼中,就不能直接使用Mat,而要轉換為其它形式的資料結構。最簡單的解決方案是指標,即將Mat拷貝到自定義的指標中。


Mat::data是資料段的首地址;使用memcpy()將Mat的資料拷貝至某個指標中,當然要先new一段記憶體。


memcpy的說明:

http://blog.csdn.net/sszgg2006/article/details/7989404
常用到vector<Mat>,要用push_back方法對其進行賦值,vector的使用說明:http://blog.csdn.net/hancunai0017/article/details/7032383

4位元組對齊的情況

但如果影象大小不是4的整數倍,某些場合下不能直接使用Mat::data。因為影象在OpenCV裡的儲存機制問題,行與行之間可能有空白單元(一般是補夠4的倍數或8的倍數,稱為padding。這些空白單元對影象來說是沒有意思的,只是為了在某些架構上能夠更有效率,比如intel MMX可以更有效的處理那種個數是4或8倍數的行。Mat提供了一個檢測影象是否連續的函式isContinuous()。當影象連通時,我們就可以把影象完全展開,看成是一行。此時呼叫Mat::ptr<>()方法就等價於Mat::data

 int nr=image.rows;      
int nc=image.cols;  
if(image.isContinuous())  
    {  
        nr=1;  
        nc=nc*image.rows*image.channels();  
    }   
    for(int i=0;i<nr;i++)  
    {       
        const uchar* inData=image.ptr<uchar>(i);         
        uchar* outData=outImage.ptr<uchar>(i);        
        for(int j=0;j<nc;j++)  
        {  
            *outData++=*inData++;  
        }  
    }  

例如儲存BMP格式的影象時,BMP要求影象資料按四位元組對齊,此時就需要對Mat中的資料進行補零
對齊方法就是在每一行尾部補零,零的個數可能是1~3個

但其實大部分時候,Mat的記憶體都是連續的,只有極個別時候需要擔心這個問題,這裡有說明,和這裡

Mat::data的預設型別

Mat::data的預設型別為uchar*,但很多時候需要處理其它型別,如float、int,此時需要將data強制型別轉換,如:

Mat src(1000,1000,CV_32F);
float* myptr = (float*)src.data;

無論Mat的type是何種型別,Mat::data均為uchar*

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

指標資料拷貝至Mat

這個千萬注意,使用Mat接收指標指定的一段記憶體資料,通過指標初始化一個Mat:

Mat(row,col,CV_8U,ptr)
此時,Mat::data就等於ptr,例子
	uchar ptr[25]={0,1};
	Mat mat(5,5,CV_8U,ptr);
	mat = mat*255;

修改mat就修改了ptr指向記憶體的值

注意:Mat的型別要與指標的型別一致,如,uchar指標對應CV_8U,double指標對應CV_64F,如果把double指標賦給一個CV_32F的Mat,那Mat的每個元素只佔32位,即把double數一分為二,是錯誤的。

這裡又涉及一個問題,型別轉換。opencv提供了Mat::convertTo介面進行型別轉換,當然我們可以逐個元素進行強制型別轉換,但有時候兩者是有區別的。例如,把double轉unsigned int,這是有符號數轉無符號數,如果使用convertTo方法,會把負數置零;如果使用強制型別轉換(unsigned int),結果是錯誤的,因為負數應該變成其補數,無符號的第一個位元位是有意義的

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

Mat資料拷貝至指標

有些時候,我們不希望函式的呼叫者看到opencv的資料結構Mat,可以通過把Mat資料拷貝至一段動態申請的記憶體,此時千萬要注意資料型別,指標和Mat要統一。

typedef ushort  mtype;
Mat src;
...
	mtype* psrc = (mtype*)src.data;
	mtype *pdst = new mtype [src.total()];
	for(int i=0;i<h;i++)	//遍歷行
	{		
		const mtype* p0 = psrc + i*w;
		mtype* p1 = pdst + i*w;
		for(int j=0;j<w;j++)	//遍歷列
		{	
			*p1++= p0[j];		
		}
	}	//耗時:0.7ms
以上是最淺顯的做法,如果使用memcpy可以更高效:
memcpy(pdst,psrc,src.total()*sizeof(mtype));  //耗時:0.5ms
src是一個1000*1000的ushort矩陣
需要注意的是,src.data需要先進行強制型別轉換

通過指標構造Mat

方法:

Mat img(image_size,TYPE,img_ptr);

這樣,可以避免重複申請記憶體,對Mat的修改就是對指標內容的修改

--------------------------------END-------------------------------------