OpenCV學習筆記(二) cv::Mat


部分內容轉自:OpenCV Tuturialggicci

OpenCV Tuturial中可查看Mat的初始化與打印方法。

 

Mat本質上是由兩個數據部分組成的類:

  1. 矩陣頭(包含矩陣尺寸,存儲方法,存儲地址等信息)
  2. 一個指向存儲所有像素值的矩陣(根據所選存儲方法的不同矩陣可以是不同的維數)的指針

OpenCV使用引用計數機制。其思路是讓每個 Mat 對象有自己的信息頭,但共享同一個矩陣。這通過讓矩陣指針指向同一地址而實現。而拷貝構造函數則 只拷貝信息頭和矩陣指針 ,而不拷貝矩陣。

特性

  • reference counting:當counting為0時,會自動釋放內存;
  • shallow copy:當令mat1=mat2時,二者指向的是同一份image data,對mat2的修改會等效作用於mat1上,如果確實要拷貝出一個副本時,需要調用copyTo函數或者clone函數。
Mat A, C;                                 // 只創建信息頭部分
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 這里為矩陣開辟內存
Mat B(A);                                 // 使用拷貝構造函數
C = A;                                    // 賦值運算符

以上代碼中的所有Mat對象最終都指向同一個也是唯一一個數據矩陣。雖然它們的信息頭不同,但通過任何一個對象所做的改變也會影響其它對象。實際上,不同的對象只是訪問相同數據的不同途徑而已。注意,這個特性要求OpenCV中的類應該返回副本copyTo(returnMat),否則當某個類的實體發生改變時,該類的其余實體都會發生改變。

  • 你可以創建只引用部分數據的信息頭。比如想要創建一個感興趣區域( ROI ),你只需要創建包含邊界信息的信息頭:
// ROI是某個矩形
Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle
Mat E = A(Range:all(), Range(1,3)); // using row and column boundaries
// 假如ROI是某些行或者列時:
cv::Mat imageROI= image.rowRange(start,end) ;
cv::Mat imageROI= image.colRange(start,end) ;
// 單行或者單列時:
image.row(rowNum),image.col(colNum)
  • 出於效率優化,每行的結尾可能存在padding,使得每行大小是2的整數次冪,可以通過M.isContinuous()判斷是否存在padding(True:不存在padding)。當不存在padding時,Mat image的內存占用為(byte)=image.elemSize() * image.total()

屬性

  • data:Mat對象中的一個指針,指向內存中存放矩陣數據的一塊內存 (uchar* data)
  • dims:Mat所代表的矩陣的維度,如 3 * 4 的矩陣為 2 維, 3 * 4 * 5 的為3維
  • channels():通道,矩陣中的每一個矩陣元素擁有的值的個數,比如說 3 * 4 矩陣中一共 12 個元素,如果每個元素有三個值,那么就說這個矩陣是 3 通道的,即 channels = 3。常見的是一張彩色圖片有紅、綠、藍三個通道。
  • 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:是一個數組,定義了矩陣的布局,包括padding部分,具體見下面圖片分析,另外注意M.step[m-1] == M.elemSize();
  • step1(n) == step[n] / elemSize1,M.step1(m-1)總是等於 channels;
  • elemSize() : 矩陣中每一個元素的數據大小,如果Mat中的數據的數據類型是 CV_8U 那么 elemSize = 1,CV_8UC3 那么 elemSize = 3,CV_16UC2 那么 elemSize = 4;
  • elemSize1(): 表示的是矩陣中數據類型的大小,即 elemSize / channels 的大小
  • total():像素的總數

地址計算

灰度圖的每個像素都是0~255的8 bit值。彩色圖有BGR三通道,其像素可視為一個三維向量,每個分量也是一個0~255的8 bit值。代碼中有時存在第四維alpha,表示透明度。

最小的數據類型可能是 char 類型,這意味着一個字節或 8 位。這可能是有符號(值-127 到 + 127)或無符號(以便可以存儲從 0 到 255 之間的值)。雖然這三個組件的情況下已經給 16 萬可能的顏色來表示 (如 RGB 的情況下),我們可通過使用浮點數 (4 字節 = 32 位) 或double(8 字節 = 64 位) 數據類型的每個組件獲得甚至更精細的控制。

注意:當目標為ROI時,地址計算失效。

addr(Mi0,i1,…im-1)=M.data + M.step[0] * i0 + M.step[1] * i1 + … + M.step[m-1] * im-1 

(其中 m = M.dims M的維度)

考慮二維情況(stored row by row)按行存儲:

  • M.dims == 2 ; 

當數據類型為 CV_8U單通道的 uchar 時:

  • M.channels() == 1 ;
  • M.elemSize() == 1 
  • M.elemSize1() ==  1 ;
  • M.step[0] ==  4  ; 
  • M.step[1] == 1; 
  • M.step1(0) == 4; 
  • M.step1(1) == 1;

當數據類型是 CV_8UC3三通道:

  • M.channels() == 3;
  • M.elemSize() == 3 
  • M.elemSize1() == 1 
  • M.step[0] == 12 ;
  • M.step[1] ==  3;
  • M.step1(0) == 12 ;
  • M.step1(1) ==  3;

 

 當數據類型為 CV_16SC4,也就是 short 類型:
  • M.dims == 3 ;
  • M.rows == M.cols == –1;
  • M.channels() == 4 ;
  • M.elemSize1() == sizeof(short) == 2 ;
  • 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(第三維度(即元素) * 通道數);

其他

P.S.1

OpenCV1中采用的IplImage(Intel Image Processing Library)類型應盡量不再使用。可用以下方法將IplImage轉為Mat:

IplImage* iplImage = cvLoadImage("c:\\img.jpg");

cv::Mat image(iplImage,false); //false是默認參數,表示淺拷貝,即image指向同一區域,不額外占用空間。

若確實需要使用IplImage時,應注意dangling pointer的問題,可選擇:

  • 使用reference counting pointer:cv::Ptr<IplImage> iplImage = cvLoadImage("c:\\img.jpg");
  • 顯式銷毀指針:cvReleaseImage(&iplImage);

P.S.2

Mat 中的channel是BGR,在Qt中顯示的圖像需是QImage類型(通道為RGB),可通過以下方式轉換:

// change color channel ordering
cv::cvtColor(image,image,CV_BGR2RGB); 
// Qt image
QImage img= QImage((const unsigned char*)(image.data), image.cols,image.rows,QImage::Format_RGB888);
// display on label
ui->label->setPixmap(QPixmap::fromImage(img)); 
// resize the label to fit the image
ui->label->resize(ui->label->pixmap()->size());

P.S.3

Mat的幾個method:

cv::Mat image=imread("image.jpg"), result;
// 第i行的地址
uchar* data = image.ptr<uchar>(0);
// 按照image的大小和類型格式化 Mat result
result.create(image.rows,image.cols,image.type());
// ROI訪問某行或者某列
result.row(0).setTo(cv::Scalar(0));
result.row(result.rows-1).setTo(cv::Scalar(0));
// 在不占用內存的情況下,改變矩陣的維數
image.reshape(1, // new number of channels
              image.cols*image.rows) ; // new number of rows
// True:每行的結尾不存在padding
if (image.isContinuous()){
    /*圖像的內存空間是連續的*/
    nc= nc*nl; 
    nl= 1; // it is now a 1D array
}


免責聲明!

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



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