数字图像可看作一个数值矩阵, 其中的每个元素代表一个像素点,如下:
数值矩阵在 OpenCV 中用 Mat 表示,它是一种非常重要的数据结构,因为 OpenCV 中的大部分函数都和 Mat 有关:
- 有的是 Mat 的成员函数;
- 有的把 Mat 作为参数;
- 有的将 Mat 作为返回值
1 Mat 简介
Mat 表示的是 N 维稠密矩阵,与之相对的是稀疏矩阵 (只存储非零的像素值),后者常用于直方图处理中,表示为 cv::SparseMat
如下:第一个是稠密矩阵的存储方式,它存储所有的像素数值;第二个是稀疏矩阵的存储方式,它只存储非零的像素值
当 N=1 时,所有像素存储为一行;当 N=2 时,所有像素按照一行行的顺序排列;当 N=3 时,所有像素按照一面面的顺序排列,其中一行行的像素构成一个平面。
灰度图的存储方式,如下:
RGB 图像的存储方式,如下:注意其存储顺序为 BGR (蓝-绿-红)
2 Mat 特点
2.1 组成
Mat 类包含两部分:矩阵头 (matrix header) 和 矩阵指针 (pointer to matrix),部分矩阵头如下:
1 2 3 4 5 6 | int flags; // signaling the contents of the matrix int dims; // dimensions int rows, cols; // rows and columns MatSize size; // MatStep step; // |
矩阵指针如下,指向包含所有像素值的矩阵
1 | uchar* data; // pointer to the data |
2.2 赋值算子
Mat 类中的赋值算子 "=" 和 拷贝构造函数,涉及的是浅拷贝,因此,当执行这两个操作时,仅仅是复制了矩阵头。
如果想要深拷贝,达到复制图像矩阵的目的,应使用 clone() 或 copyTo() 函数,如下图所示:
2.3 代码验证
新建矩阵 m1 并初始化,m1 通过 "=" 赋值给 m2,两者指向同样的数据块。因此,如果改变了 m1,则 m2 的数值,也会随之变化
新建矩阵 m3,通过 copyTo() 赋值给 m1,此时可看到,m1 和 m2 的数值同时发生了变化
1 2 3 4 5 6 7 8 9 10 | Mat m1(3, 3, CV_32FC1, Scalar(1.1f) ); cout << "m1 = " << endl << " " << m1 << endl << endl; // using assign operator Mat m2 = m1; cout << "m2 = " << endl << " " << m2 << endl << endl; Mat m3(3, 3, CV_32FC1, Scalar(3.3f) ); m3.copyTo(m1); cout << "m1 = " << endl << " " << m1 << endl << endl; cout << "m2 = " << endl << " " << m2 << endl << endl; |
3 Mat 创建
3.1 数据类型
在创建 Mat 之前,首先了解 Mat 中元素的数据类型,其格式为 CV_{8U, 16S, 16U, 32S, 32F, 64F}C{1, 2, 3} 或 CV_{8U, 16S, 16U, 32S, 32F, 64F}C(n)
第一个 {} 表示数据的类型:
1 2 3 4 5 6 7 | CV_8U - 8-bit 无符号整数 ( 0..255 ) CV_8S - 8-bit 有符号整数 ( -128..127 ) CV_16U - 16-bit 无符号整数 ( 0..65535 ) CV_16S - 16-bit 有符号整数 ( -32768..32767 ) CV_32S - 32-bit 有符号整数 ( -2147483648..2147483647 ) CV_32F - 32-bit 浮点数 ( -FLT_MAX..FLT_MAX, INF, NAN ) CV_64F - 64-bit 浮点数 ( -DBL_MAX..DBL_MAX, INF, NAN ) |
第二个 {} 或 (n),表示的是通道:
1 | CV_8UC3 等价于 CV_8UC(3) - 3通道 8-bit 无符号整数 |
3.2 创建方式
3.2.1 构造函数
直接赋值,创建一个 3 行 3 列的单位矩阵
1 2 | // create a 3x3 double-precision identity matrix Mat M = (Mat_< double >(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1); |
创建一个 3 行 5 列,3 通道 32 位,浮点型的矩阵,通道 1, 2, 3 的值分别为 1.1f,2.2f,3.3f
1 2 | Mat m(3, 5, CV_32FC3, Scalar(1.1f, 2.2f, 3.3f) ); cout << "m = " << endl << " " << m << endl << endl; |
输出矩阵如下:
3.2.2 create 函数
使用 Mat() + create() + setTo(),也可以构建如上的数值矩阵
1 2 3 4 5 6 | Mat m; // Create data area for 3 rows and 10 columns of 3-channel 32-bit floats m.create(3,5,CV_32FC3); // Set the values in the 1st channel to 1.0, the 2nd to 0.0, and the 3rd to 1.0 m.setTo(Scalar(1.1f, 2.2f,3.3f)); cout << "m = " << endl << " " << m << endl << endl; |
3.2.3 特殊矩阵
单位矩阵 (ones),对角矩阵 (eye),零矩阵 (zeros),如下所示:
1 2 3 4 5 6 7 8 9 | // 单位矩阵 Mat O = Mat::ones(3, 3, CV_32F); cout << "O = " << endl << " " << O << endl << endl; // 零矩阵 Mat Z = Mat::zeros(3, 3, CV_8UC1); cout << "Z = " << endl << " " << Z << endl << endl; // 对角矩阵 Mat E = Mat::eye(3, 3, CV_64F); cout << "E = " << endl << " " << E << endl << endl; |
4 Mat 遍历
4.1 at<>() 函数
常用来遍历 Mat 元素的基本函数为 at<>(),其中 <> 内的数据类型,取决于 Mat 中元素的数据类型,二者的对应关系如下:
1 2 3 4 5 6 7 | CV_8U -- Mat.at<uchar>(y,x) CV_8S -- Mat.at<schar>(y,x) CV_16U -- Mat.at<ushort>(y,x) CV_16S -- Mat.at< short >(y,x) CV_32S -- Mat.at< int >(y,x) CV_32F -- Mat.at< float >(y,x) CV_64F -- Mat.at< double >(y,x) |
简单的遍历如下,使用了 Qt 的 qDebug() 来显示输出
1 2 3 4 5 6 7 | Mat m1 = Mat::eye(10, 10, CV_32FC1); // use qDebug() qDebug() << "Element (3,3) is : " << m1.at< float >(3,3); Mat m2 = Mat::eye(10, 10, CV_32FC2); // use qDebug() qDebug() << "Element (3,3) is " << m2.at<cv::Vec2f>(3,3)[0] << "," << m2.at<cv::Vec2f>(3,3)[1]; |
注意:at<>() 函数中 () 内,行索引号在前,列索引号在后,也即 (y, x)
4.2 遍历方式
4.2.1 高效遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Mat& ScanImageAndReduceC(Mat& src, const uchar* const table) { // accept only char type matrices CV_Assert(src.depth() == CV_8U); int channels = src.channels(); int nRows = src.rows; int nCols = src.cols * channels; if (src.isContinuous()) { nCols *= nRows; nRows = 1; } int i,j; uchar* p; for (i=0; i<nRows; ++i) { p = src.ptr<uchar>(i); for (j = 0; j<nCols; ++j) { p[j] = table[p[j]]; } } return src; } |
4.2.2 迭代器遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | Mat& ScanImageAndReduceIterator(Mat& src, const uchar* const table) { // accept only char type matrices CV_Assert(src.depth() == CV_8U); const int channels = src.channels(); switch (channels) { case 1: { MatIterator_<uchar> it, end; for (it=src.begin<uchar>(), end=src.end<uchar>(); it!=end; ++it) *it = table[*it]; break ; } case 3: { MatIterator_<Vec3b> it, end; for (it=src.begin<Vec3b>(), end=src.end<Vec3b>(); it!=end; ++it) { (*it)[0] = table[(*it)[0]]; (*it)[1] = table[(*it)[1]]; (*it)[2] = table[(*it)[2]]; } } } return src; } |
4.2.3 耗时计算
比较上面两种方法的耗时,可用如下代码:
1 2 3 4 | double t = ( double )getTickCount(); // do something ... t = (( double )getTickCount() - t)/getTickFrequency(); cout << "Times passed in seconds: " << t << endl; |
参考资料
《Learning OpenCV3》 chapter 4
OpenCV Tutorials / The Core Functionality (core module) / Mat - The Basic Image Container
OpenCV Tutorials / The Core Functionality (core module) / How to scan images, lookup tables and time measurement with OpenCV
原文链接: http://www.cnblogs.com/xinxue/
专注于机器视觉、OpenCV、C++ 编程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人