在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)通过构造函数创建
指定行数、列数、数据类型:
cpp
// 创建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()
(单位矩阵):
cpp
// 创建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
(不复制数据,仅共享内存):
cpp
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
对象:
cpp
// 读取彩色图像(默认3通道BGR)
cv::Mat img_color = cv::imread("image.jpg");
// 读取灰度图(单通道)
cv::Mat img_gray = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);
2. 访问cv::Mat
的属性
通过成员函数或成员变量获取元信息:
cpp
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]
(多通道)。
cpp
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>()
):
cpp
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]; // B
uchar g = row_ptr[pos + 1]; // G
uchar 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>
遍历,自动处理边界:
cpp
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()
处理多通道图像:cppcv::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()
):cppcv::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
:cppcv::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
的组成和使用方法,可高效处理图像数据,避免内存问题并优化操作性能。