本文深入探索OpenCV库中cv::Mat类的使用方法和技巧。cv::Mat作为OpenCV中用于存储图像数据的基本数据结构,其使用范围广泛且功能强大。通过本文,你将了解cv::Mat的创建、访问和修改数据的基本操作,以及其在图像处理任务中的具体应用。此外,我还会分享一些在使用cv::Mat时可能遇到的常见问题及其解决方案,帮助你更好地掌握这一工具。无论你是OpenCV的新手还是资深用户,相信这篇博客都能为你提供有价值的参考和启示。
1. 构造
- 无参数构造方法
Mat::Mat()
- 创建行数为 rows,列数为 col,类型为 type 的图像
Mat::Mat(int rows, int cols, int type)
- 创建大小为 size,类型为 type 的图像
Mat::Mat(Size size, int type)
- 创建行数为 rows,列数为 col,类型为 type 的图像,并将所有元素初始化为值 s
Mat::Mat(int rows, int cols, int type, const Scalar& s)
- 创建大小为 size,类型为 type 的图像,并将所有元素初始化为值 s
Mat::Mat(Size size, int type, const Scalar& s)
- 将m赋值给新创建的对象,此处不会对图像数据进行复制,m和新对象共用图像数据,属于浅拷贝
Mat::Mat(const Mat& m)
- 创建行数为rows,列数为col,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由 step指定
Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
- 创建大小为size,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由step指定
Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
- 创建的新图像为m的一部分,具体的范围由rowRange和colRange指定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
- 创建的新图像为m的一部分,具体的范围roi指定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据
Mat::Mat(const Mat& m, const Rect& roi)
2. 初始化
- 使用函数初始化
cv::Mat a = cv::Mat::eye(4, 4, CV_64FC1); // 单通道double类型
cv::Mat b = cv::Mat::eye(4, 4, CV_8UC1); // 单通道char类型
- 使用cv::Point3f和cv::Mat_初始化;
cv::Point3f v1(34.0f, 21.34f, 32.0f);
cv::Mat m{ cv::Mat_<cv::Point3f>(v1) };
//得到1行1列3通道的矩阵
- 使用cv::Point3f初始化
cv::Point3f point(34.0f, 21.34f, 32.0f);
cv::Mat m(point);
//得到3行1列1通道的矩阵
- 使用vector初始化
std::vector<double> vec{ 21.3, -10, 34.0, 3.2, 3.4 };
cv::Mat m{ vec };
//得到5行1列1通道的矩阵
- 使用std::vector<cv::Point2f>初始化
//得到2行1列2通道的矩阵
std::vector<cv::Point2f> vecpts{ cv::Point2f(2.3, 3.1), cv::Point2f(9.3, -10.0) };
cv::Mat m{ vecpts };
- 使用cv::Mat_初始化
//得到3行3列1通道的矩阵
cv::Mat C = (cv::Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
3. 对一部分进行赋值
这些方式看似正确,但实际不行
cv::Mat matrix = cv::Mat::zeros(4, 4, CV_64FC1);
cv::Mat calibration = cv::Mat::eye(3, 3, CV_64FC1);
// 方法一
cv::Mat a = matrix.rowRange(0, 3).colRange(0, 3); // 共享内存
a = calibration;
// 方法二
cv::Mat matrix = cv::Mat::zeros(4, 4, CV_64FC1);
matrix.rowRange(0, 3).colRange(0, 3) = calibration.clone();
// 方法三
calibration.copyTo(matrix.rowRange(0, 3).colRange(0, 3)); // 编译失败
正确的方式
cv::Mat matrix = cv::Mat::zeros(4, 4, CV_64FC1);
cv::Mat a = matrix.rowRange(0, 3).colRange(0, 3); // 共享内存
xa.copyTo(a);
cv::Mat b = matrix.rowRange(0, 3).col(3); // 共享内存
xb.copyTo(b);
4. 与Eigen矩阵的相互转化
注意:只支持double类型的转换,不支持float类型
#include <opencv2/core/eigen.hpp>
cv::cv2eigen(cv_matrix, eigen_matrx);
cv::eigen2cv(eigen_matrx, cv_matrix);
5. 指针
cv::Mat matrix = cv::Mat::zeros(4, 4, CV_64FC1);
// 方法一
const double *data = matrix.ptr<double>(0);
// 方法二
// img.data的默认类型是unsigned char*
double* data = (matrix*)img.data;
6. cv::Mat的缺点
- 弱类型
C++本来是强类型语言,但cv::Mat却是弱类型的,即当你拿到一个cv::Mat的时候你并不知道它里面存储的数据类型,很容易用错;
- 不安全
当你把你的cv::Mat对象传给某函数时,即使函数的形参加了const关键字,但它依然可能会被修改,这是因为cv::Mat有共享内存机制,它的拷贝是浅拷贝;
- 构造时间长
cv::Mat构造时间很长,如果非必要不建议使用,构造过程尽量写在循环外面;
7. 图像操作
- 读入图像
cv::Mat A = imread(“Imgpath”, CV_LOAD_IMAGE_COLOR);
A.at<Vec3b>(row, col)[2] // 访问图像数据
注意 Mat::at 函数是个模板函数,需要指明参数类型,因为这张图是具有红蓝绿三通道的图,所以它的参数类型可以传递一个 Vec3b,这是一个存放 3 个 uchar 数据的 Vec(向量)。这里提供了索引重载,[2]表示的是返回第三个通道,在这里是 Red 通道,第一个通道(Blue)用[0]返回
- 保存图像
imwrite(image_path, A);
- 将彩色图片转为灰度图片
Mat src(m, n CV_8UC3);
Mat dst;
cvtColor(src,dst,CV_BGR2GRAY);