学习OpenCV(2)--- 图像基础容器Mat
一.Mat类基本概念
1.1 Mat类的组成
Mat是一个类,由两个部分组成:
①矩阵头(包含矩阵尺寸、存储方法、存储地址等信息)。
②一个指向存储所有像素值的矩阵(根据所选存储方法的不同,矩阵可以是不同的维数)的指针。
矩阵头的尺寸是常数值,但矩阵本身尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。
OpenCv使用了引用计数机制。其思路是让每一个Mat对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。
1.2 Mat 类核心特性
自动内存管理:基于引用计数的浅拷贝 + 显式深拷贝,避免内存泄漏(超出作用域自动释放)。
多维支持:支持 2D(图像)、3D(视频帧序列 / 立体图像)甚至更高维数组。
类型灵活:支持不同数据类型(uchar/int/float/double)和通道数(1/3/4 通道,对应灰度 / RGB / 透明图)。
与 Numpy 互操作 :OpenCV-Python 中cv2.Mat与numpy.ndarray无缝转换(底层共享内存)。
1.3 内存管理与引用计数
cv::Mat 使用引用计数机制(类似智能指针)。
当多个 Mat 共享同一数据时,只有当所有引用都被销毁后,数据才会被释放。
若需深拷贝,使用:
c++
cv::Mat clone = original.clone();
// 或
cv::Mat copy;
original.copyTo(copy);
二、Mat 类的基本使用
2.1 Mat类的构造
2.1.1 使用Mat构造函数
c++
// 方式1:Mat 构造函数
// 构造一个2*2 的8位无符号char 类型的3通道的Mat 对象
Mat mat1(2,2,CV_8UC3,Scalar(0,0,255));
cout << "mat1 = " << endl << " " << mat1 << endl << endl;
2.1.2 在C/C++中通过构造函数进行初始化
c++
// 方式2:在C/C++中通过构造函数进行初始化
// 构造一个超过2维的的矩阵,指定维数,然后传递一个指向数组的指针
int sz[3] = { 2,2,2 };
Mat mat2(3,sz,CV_8UC(2), Scalar::all(0));
2.1.3 为已存在的IplImage指针创建信息头
c++
// 方式3:为已存在的IplImage指针创建信息头
IplImage* pImage = cvLoadImage("1.jpg",1);
Mat mat3(pImage);
说明:我用的是OpenCV 4.11.0版本,其中IplImage已经被移除,cvLoadImage 也没有,这种方式推荐在低版本中使用,比如2.4.x版本中。
2.1.4 方式4:利用Create()函数
c++
// 方式4:利用Create()函数
Mat mat4;
mat4.create(4,4,CV_8UC(2));
cout << "mat4 = " << endl << " " << mat4 << endl << endl;
2.1.5 方式5:采用Matlab式的初始化方式:zeros、ones()、eye()
C++
// 方式5:采用Matlab式的初始化方式:zeros、ones()、eye()
Mat matEye = Mat::eye(4,4,CV_64F);
cout << "matEye = " << endl << " " << matEye << endl << endl;
Mat matOne = Mat::ones(2,2,CV_32F);
cout << "matOne = " << endl << " " << matOne << endl << endl;
Mat matZero = Mat::zeros(3,3, CV_8UC1);
cout << "matZero = " << endl << " " << matZero << endl << endl;
2.1.6 对小矩阵使用逗号分隔式初始化函数
C++
// 方式6:对小矩阵使用逗号分隔式初始化函数
Mat mat6 = (Mat_<double>(3, 3) << 0,-1,0,-1,5,-1,0,-1,0);
cout << "mat6 = " << endl << " " << mat6 << endl << endl;
2.1.7 为已存在的对象创建信息头
c++
// 方式7:为已存在的对象创建信息头
Mat mat7 = mat6.row(1).clone();
cout << "mat7 = " << endl << " " << mat7 << endl << endl;
2.2 类型说明(Type)
OpenCV 通过宏定义统一数据类型,格式:CV_{位数}{类型}{通道数}:
| 类型宏 | 说明 | 对应 C++ 类型 | 适用场景 |
|---|---|---|---|
CV_8UC1 |
8 位无符号单通道 | unsigned char | 灰度图像(0-255) |
CV_8UC3 |
8 位无符号 3 通道 | unsigned char[3] | RGB 彩色图像 |
CV_32SC1 |
32 位有符号单通道 | int | 整数运算(如差值计算) |
CV_32FC1 |
32 位浮点单通道 | float | 浮点运算(如滤波) |
CV_64FC3 |
64 位浮点 3 通道 | double[3] | 高精度浮点计算 |
注:
U=unsigned(无符号),S=signed(有符号),F=float(浮点);通道数可省略(默认 1)
可通过 mat.type() 获取类型,使用 CV_MAT_DEPTH(type) 和 CV_MAT_CN(type) 分别提取深度和通道数。
C++
// 单通道
cv::Mat gray(100, 100, CV_8UC1); // 灰度图像
cv::Mat floatMat(100, 100, CV_32FC1); // 浮点矩阵
// 多通道
cv::Mat color(100, 100, CV_8UC3); // BGR彩色图像
cv::Mat rgba(100, 100, CV_8UC4); // RGBA图像
2.3 拷贝与赋值
1.浅拷贝(仅拷贝头部,共享数据区)
c++
cv::Mat img1 = cv::imread("test.jpg");
cv::Mat img2 = img1; // 浅拷贝
cv::Mat img3(img1); // 浅拷贝
img2.at<cv::Vec3b>(0,0) = cv::Vec3b(255,0,0); // img1、img3的(0,0)像素也会变
2.深拷贝(独立数据区)
c++
cv::Mat img1 = cv::imread("test.jpg");
cv::Mat img2 = img1.clone(); // 深拷贝(推荐)
cv::Mat img3;
img1.copyTo(img3); // 深拷贝(可指定掩码)
img2.at<cv::Vec3b>(0,0) = cv::Vec3b(255,0,0); // img1不受影响
要点:
- 赋值/拷贝构造只复制头部,不复制像素;
- 引用计数为 0 时自动
free(); - 一旦某个实例调用
.clone()、.copyTo()、.create()或reshape(),就会断开共享,自己管自己的内存。
三. 访问像素数据
3.1 at<T>(row, col)(直观但效率低,适合少量访问)
c++
Mat matGray(480,640, CV_8UC3,Scalar(0,0,0));
// 访问单通道(CV_8UC1)
uchar gray_pixel = matGray.at<uchar>(100,200);
// 访问三通道
Vec3b& rgb_pixel = matGray.at<Vec3b>(100, 200);
rgb_pixel[0] = 255; // B通道 (OpenCV默认BGR!)
rgb_pixel[1] = 0; // G通道
rgb_pixel[2] = 0; // R通道
3.2 指针访问(高效,适合遍历所有像素)
C++
// 使用指针访问
Mat mat(480, 640, CV_8UC3);
for (int i=0;i< mat.rows;i++)
{
// 获取第i行的指针
uchar* row_ptr = mat.ptr<uchar>(i);
for (int j=0;i<mat.cols;j++)
{
row_ptr[j * 3] = 255;
row_ptr[j * 3 + 1] = 0;
row_ptr[j * 3 + 2] = 0;
}
}
3.3 迭代器访问(安全,不关心尺寸)
c++
// 迭代器访问
Mat mat2(480, 640, CV_8UC3);
// 3通道迭代器
MatIterator_ <Vec3b> it = mat2.begin<Vec3b>();
MatIterator_ <Vec3b> it_end = mat2.end<Vec3b>();
for (; it != it_end;it++)
{
(*it)[0] = 0;
(*it)[1] = 255;
(*it)[2] = 0;
}
四.常用操作
4.0 读取图像
C++
Mat mat = cv::imread("test.jpg");
4.1 调整大小
c++
// 1.调整大小
Mat mat = cv::imread("test.jpg");
Mat mat_resize;
// 缩放(宽800,高600)
cv::resize(mat, mat_resize,cv::Size(800,600));
// 按比例缩放
cv::resize(mat, mat_resize, cv::Size(),0.5,0.5);
4.2 颜色空间转换
c++
// 颜色空间转换
Mat gray, hsv;
cv::cvtColor(mat, gray,cv::COLOR_BGR2GRAY);
cv::cvtColor(mat, hsv, cv::COLOR_BGR2HSV);
4.3 通道拆分与合并
c++
// 通道拆分与合并
vector<Mat> channels;
split(mat, channels); // 拆分:channels[0]=B, channels[1]=G, channels[2]=R
Mat bgr_r;
channels[2].copyTo(bgr_r);
Mat mat_Merge;
cv::merge(channels,mat_Merge);
4.4 ROI(Range Of Interest) 提取
c++
// ROI 提取
cv::Rect rect(200,100,200,200); // 提取ROI:行100-300,列200-400(Rect(x, y, width, height))
Mat img_roi = mat(rect); // 浅拷贝!修改img_roi会影响原图
4.5 保存图像
c++
// 保存图像
cv::imwrite("output.jpg", mat);
4.6 阈值处理
c++
// 阈值处理
cv::Mat binary;
cv::threshold(gray, binary, 128, 255, cv::THRESH_BINARY);
4.7 滤波
c++
// 滤波
cv::Mat blurred;
cv::GaussianBlur(src, blurred, cv::Size(5, 5), 0);
4.8 形态学操作
C++
// 形态学操作
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
cv::Mat eroded, dilated;
cv::erode(src, eroded, kernel);
cv::dilate(src, dilated, kernel);
五. 注意事项
- 内存管理:Mat使用引用计数,注意浅拷贝和深拷贝的区别
- 数据类型:确保操作时使用正确的数据类型
- 边界检查:访问像素时要确保不越界
- 连续存储:对于性能敏感的代码,注意矩阵的连续性
- 多通道访问:注意不同通道的存储顺序(通常是BGR)
参考文章