在OpenCV中,cv::Mat
是用于存儲圖像、矩陣等多維數據的核心數據結構,替代了早期的IplImage
(需手動管理內存),其設計的核心目標是自動內存管理和高效數據操作。下面詳細介紹其組成原理及使用方法。
一、cv::Mat
的組成原理
cv::Mat
的結構由兩部分組成:矩陣頭(Matrix Header) 和數據指針(Data Pointer),二者分離的設計使其既能高效傳遞,又能避免冗余內存占用。
1. 矩陣頭(Matrix Header)
矩陣頭是一個輕量級結構體,存儲了數據的元信息,不直接存儲像素數據。核心成員包括:
rows
:行數(圖像的高度,單位為像素)。cols
:列數(圖像的寬度,單位為像素)。size()
:返回cv::Size(cols, rows)
,便捷表示尺寸。type()
:數據類型,由位深度和通道數組成(如CV_8UC3
表示8位無符號整數,3通道)。- 位深度:如8U(8位無符號)、16S(16位有符號)、32F(32位浮點)等。
- 通道數:單通道(灰度圖)、3通道(RGB/BGR)、4通道(帶Alpha通道)等。
channels()
:返回通道數(由type()
推導,如CV_8UC3
的通道數為3)。step
:行步長(每行數據的總字節數,包括像素數據和可能的填充字節,用于快速定位某一行的起始地址)。refcount
:引用計數器(用于內存自動釋放,記錄當前有多少個Mat
對象共享同一塊數據)。
2. 數據指針(Data Pointer)
數據指針(uchar* data
)指向存儲實際像素數據的內存區域。數據在內存中的排列方式由通道數決定:
- 單通道(灰度圖):按行存儲,每行像素依次排列(如
[p0, p1, p2, ..., p_cols-1]
)。 - 多通道:每個像素的通道數據連續存儲(如3通道圖像的一個像素為
[B, G, R]
,按B0, G0, R0, B1, G1, R1, ...
排列,OpenCV默認通道順序為BGR而非RGB)。
3. 內存管理機制:引用計數
cv::Mat
通過引用計數實現高效內存管理:
- 當復制
Mat
對象(如Mat B = A
)時,僅復制矩陣頭,數據指針指向同一塊內存,引用計數refcount
加1。 - 當
Mat
對象銷毀時,引用計數減1;當refcount
為0時,自動釋放數據內存,避免內存泄漏。 - 若需深拷貝(獨立數據),需使用
clone()
或copyTo()
方法(如Mat C = A.clone()
)。
二、cv::Mat
的使用方法
1. 創建cv::Mat
對象
常用創建方式包括:
(1)通過構造函數創建
指定行數、列數、數據類型:
// 創建3行2列的8位無符號單通道矩陣(初始值隨機)
cv::Mat mat1(3, 2, CV_8UC1);// 創建3行2列的32位浮點3通道矩陣(初始值隨機)
cv::Mat mat2(3, 2, CV_32FC3);// 用cv::Size指定尺寸(寬x高)
cv::Mat mat3(cv::Size(200, 100), CV_8UC3); // 寬200,高100(rows=100, cols=200)
(2)創建并初始化特殊矩陣
使用zeros()
、ones()
、eye()
(單位矩陣):
// 創建3x3的8位無符號單通道零矩陣
cv::Mat zeros_mat = cv::Mat::zeros(3, 3, CV_8UC1);// 創建2x4的32位浮點3通道全1矩陣
cv::Mat ones_mat = cv::Mat::ones(2, 4, CV_32FC3);// 創建5x5的64位浮點單通道單位矩陣
cv::Mat eye_mat = cv::Mat::eye(5, 5, CV_64FC1);
(3)從已有數據創建
將外部數組數據包裝為Mat
(不復制數據,僅共享內存):
float data[] = {1.0f, 2.0f, 3.0f, 4.0f};
// 創建2行2列的32位浮點單通道矩陣,數據指向data數組
cv::Mat mat_from_data(2, 2, CV_32FC1, data);
(4)從圖像文件讀取
使用cv::imread
讀取圖像,返回Mat
對象:
// 讀取彩色圖像(默認3通道BGR)
cv::Mat img_color = cv::imread("image.jpg");// 讀取灰度圖(單通道)
cv::Mat img_gray = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);
2. 訪問cv::Mat
的屬性
通過成員函數或成員變量獲取元信息:
cv::Mat img = cv::imread("image.jpg");
int rows = img.rows; // 圖像高度(行數)
int cols = img.cols; // 圖像寬度(列數)
cv::Size size = img.size(); // 尺寸(cols, rows)
int channels = img.channels(); // 通道數(如3)
int type = img.type(); // 數據類型(如CV_8UC3)
size_t step = img.step; // 行步長(每行字節數)
3. 訪問像素數據
根據場景選擇不同方法(效率和便捷性權衡):
(1)at<T>()
方法(便捷,適合單像素訪問)
需指定數據類型T
(與type()
匹配),語法:mat.at<T>(row, col)
(單通道)或mat.at<T>(row, col)[channel]
(多通道)。
cv::Mat img = cv::imread("image.jpg"); // CV_8UC3類型// 訪問(10, 20)處的像素(行10,列20)
cv::Vec3b pixel = img.at<cv::Vec3b>(10, 20); // Vec3b對應8UC3(3個uchar)
uchar blue = pixel[0]; // B通道
uchar green = pixel[1]; // G通道
uchar red = pixel[2]; // R通道// 修改像素值
img.at<cv::Vec3b>(10, 20) = cv::Vec3b(255, 0, 0); // 改為藍色
- 常用類型對應:
CV_8UC1
→uchar
,CV_32FC1
→float
,CV_8UC3
→cv::Vec3b
,CV_32FC3
→cv::Vec3f
。
(2)行指針ptr<T>()
(高效,適合遍歷行)
獲取某一行的起始指針,通過指針遍歷像素(效率高于at<T>()
):
cv::Mat img = cv::imread("image.jpg"); // 8UC3
for (int i = 0; i < img.rows; ++i) {// 獲取第i行的指針(uchar*,因8UC3每個像素3字節)uchar* row_ptr = img.ptr<uchar>(i);for (int j = 0; j < img.cols; ++j) {// 計算當前像素的起始位置(每行j列的像素:j*通道數)int pos = j * 3;uchar b = row_ptr[pos]; // Buchar g = row_ptr[pos + 1]; // Guchar r = row_ptr[pos + 2]; // R// 修改為灰度(簡單平均)row_ptr[pos] = row_ptr[pos + 1] = row_ptr[pos + 2] = (b + g + r) / 3;}
}
(3)迭代器(安全,適合復雜遍歷)
使用cv::MatIterator_<T>
遍歷,自動處理邊界:
cv::Mat img = cv::imread("image.jpg");
// 3通道迭代器
cv::MatIterator_<cv::Vec3b> it = img.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> it_end = img.end<cv::Vec3b>();
for (; it != it_end; ++it) {// 每個迭代器指向一個像素(Vec3b)(*it)[0] = 0; // 將B通道置0
}
4. 常用操作
-
通道分離與合并:用
split()
和merge()
處理多通道圖像:cv::Mat img = cv::imread("image.jpg"); // BGR std::vector<cv::Mat> channels; cv::split(img, channels); // 分離為3個單通道(B, G, R) channels[2] = cv::Mat::zeros(img.size(), CV_8UC1); // 將R通道置0 cv::Mat img_no_red; cv::merge(channels, img_no_red); // 合并回3通道
-
ROI(感興趣區域):提取子矩陣(共享原數據,需深拷貝時用
clone()
):cv::Mat img = cv::imread("image.jpg"); // 提取從(10, 20)開始,寬100、高50的區域(行范圍[10,10+50),列范圍[20,20+100)) cv::Mat roi = img(cv::Rect(20, 10, 100, 50)); // Rect(x, y, width, height) roi.setTo(cv::Scalar(0, 255, 0)); // 直接修改ROI,原圖像也會變化
-
保存圖像:用
cv::imwrite
:cv::Mat img = cv::imread("image.jpg"); cv::imwrite("output.jpg", img); // 保存為JPG
三、注意事項
- 數據類型匹配:訪問像素時,
at<T>()
或ptr<T>()
的T
必須與Mat::type()
匹配(如CV_8UC3
對應cv::Vec3b
),否則會導致內存訪問錯誤。 - 引用計數與深拷貝:默認復制為淺拷貝(共享數據),需獨立數據時用
clone()
或copyTo()
。 - 通道順序:OpenCV默認圖像通道為BGR(而非RGB),處理時需注意轉換(可通過
cv::cvtColor(img, img_rgb, cv::COLOR_BGR2RGB)
轉換)。
通過理解cv::Mat
的組成和使用方法,可高效處理圖像數據,避免內存問題并優化操作性能。