部分內容轉自:OpenCV Tuturial,ggicci
在OpenCV Tuturial中可查看Mat的初始化與打印方法。
Mat本質上是由兩個數據部分組成的類:
- 矩陣頭(包含矩陣尺寸,存儲方法,存儲地址等信息)
- 一個指向存儲所有像素值的矩陣(根據所選存儲方法的不同矩陣可以是不同的維數)的指針
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)按行存儲:
![]() |
當數據類型為 CV_8U單通道的 uchar 時:
當數據類型是 CV_8UC3三通道:
|
![]() |
當數據類型為 CV_16SC4,也就是 short 類型:
|
其他
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 }
完