Opencv C++ 基本数据结构 Mat
Opencv C++ 基本数据结构 Mat
Mat
Mat代表矩阵,该类声明在头文件opencv2/core/core.hpp中
其构造函数为:
Mat(int rows, int cols, int type)
rows代表行数,cols代表列数
type可以设置为 CV_8UC(n)、CV_8SC(n)、CV_16SC(n)、、CV_16UC(n)、CV_32FC(n)、*、CV_32SC(n)、CV_64FC(n)
其中8U、8S、16S、16U、32S、32F、64F前的数字代表Mat中每一个数值的位数
U代表uchar类型、S代表int类型、F代表浮点型(32F为float、64F为double)其他类似。
构造单通道Mat对象
如代码所示:
# include <opencv2\core\core.hpp>
using namespace cv;
int main() {
//构造两行三列的float型矩阵
Mat m = Mat(2, 3, CV_32FC(1));
//利用Size对象构造两行3列矩阵,Size(列,行)
Mat m1 = Mat(Size(3, 2), CV_32FC(1));
//使用Mat类中的成员函数create完成Mat对象构造
Mat m2;
m2.create(Size(2, 3), CV_32FC1);
//构造零矩阵和1矩阵
Mat o = Mat::ones(2, 3, CV_32FC1);
Mat z = Mat::zeros(Size(3,2), CV_32FC1);
//初始化小型矩阵
Mat m = (Mat_<int>(2, 3) << 1, 2, 3, 4, 5, 6;
return 0;
}
获取单通道Mat的基本信息
注:单通道与ndarray中的二维对应,多通道与ndarray的三维对应
以三行两列的矩阵为例
m = [ 11 12 33 43 51 16 ] m=\left[ \begin{matrix} 11 & 12 \\ 33 & 43 \\ 51& 16 \end{matrix} \right] m=⎣⎡113351124316⎦⎤
1、获取行数和列数
# include <opencv2\core\core.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main() {
//快速构造矩阵
Mat m = (Mat_<int>(3, 2) << 11, 12, 33, 43, 51, 16);
//矩阵的行数
cout << "行数:" << m.rows << endl;
//矩阵的列数
cout << "行数:" << m.cols << endl;
return 0;
}
输出:
2、使用成员函数size()获取矩阵的尺寸
成员函数**size()**返回的是Size对象
//获取尺寸
Size size = m.size();
cout << "尺寸:" << size << endl;
3、使用成员函数channels()获取矩阵的通道数
cout << "通道数:" << m.channels() << endl;
输出:
4、使用成员函数total获得面积(行数乘列数)
cout << "面积:" << m.total() << endl;
输出
5、成员变量dims(维数)
cout << "维数:" << m.dims << endl;
输出:
访问单通道对象中的值
1、使用成员函数at
例如访问单通道数据类型为CV_32F的对象m,访问其第r行c列的值:
格式为:m.at<类型>(行,列)
m.at<float>(r,c)
遍历上文中矩阵m中的值并输出:
# include <opencv2\core\core.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main() {
//快速构造矩阵
Mat m = (Mat_<int>(3, 2) << 11, 12, 33, 43, 51, 16);
//for循环打印m中的每一个值
for (int r = 0; r < m.rows; r++)
{
for (int c = 0; c < m.cols; c++)
{
cout << m.at<int>(r, c) << ",";
}
cout << endl;
}
//return 0;
}
还可以结合opencv中的Point类和at成员函数来实现:
m.at(r, c)可用m.at(Point(r,c))代替
2、利用成员函数ptr
矩阵每一行的值值是连续存储在内存中的,行与行在内存中也可能存在间隔
通过ptr成员函数可以获得执行每一行地址的指针
格式为:m.ptr<类型>(第几行)
遍历上文中矩阵m中的值并输出:
for (int r = 0; r < m.rows; r++)
{
const int* ptr = m.ptr<int>(r);
//打印第r行所有值
for (int c = 0; c < m.cols; c++)
{
cout << ptr[c] << ",";
//cout << *(ptr+c) << ",";
}
cout << endl;
}
3、使用成员函数isContinuous 和 ptr
矩阵在存储时如果每一行在内存中没有间隔则isContinuous返回true
遍历上文中矩阵m中的值并输出:
if (m.isContinuous())
{
//获取指向第一行的指针
int* ptr = m.ptr<int>(0);
for (int i = 0; i < m.total(); i++)
{
cout << ptr[i] << ",";
if (i % m.cols)
{
cout << endl;
}
}
}
4、使用成员变量step和data
对于单通道矩阵来说,step[0]代表每一行所占的字节数,如果有间隔则间隔也作为字节数被计算在内;step[1]代表每一个数值所占的字节数,data是一个指针类型为uchar,它指向第一个数值。
因此无论矩阵行与行之间在内存中是否有间隔,都可以使用以下代码来访问第r行c列:
*((int *)(m.data+m.step[0]*r+c*m.step[1]))
向量类Vec(构建多通道Mat的基础)
可以把这里的向量理解为列向量,构造一个 _cn行x1列的数据类型为_Tp的列向量格式:Vec<Typename _Tp, int _cn>
例如构造一个长度为3类型为int,且初始化为11,87,37的列向量:
Vec<int, 3> vi(11, 87, 37);
这样构建的向量默认为列向量,可以通过输出行数和列数查看
cout << vi.rows << endl;
cout << vi.cols << endl;
可以使用 “[]” 或"()"操作符f访问向量中的值
//访问向量中的值
cout << "访问第0个元素" << vi(0) << endl;
cout << "访问第1个元素" << vi[1] << endl;
opencv 为向量类取了别名:
typedef Vec\<uchar ,3> Vec3b
typedef Vec\<int ,2> Vec2i
typedef Vec\<float,4> Vec4f
typedef Vec\<double ,3> Vec3d
单通道矩阵每个元素都是一个数值,多通道矩阵每个元素可以看作一个向量。
构造多通道Mat对象
构造一个由n个rows*cols二维浮点型矩阵组成的三维矩阵的格式如下:
Mat(int rows,int cols,CV_32FC(n))
例如构造一个2行2列的float类型的三通道矩阵
Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 32),
Vec3f(3, 13, 23), Vec3f(4, 24, 34));
访问多通道对象中的值
1、使用成员函数at
可以将多通道Mat看作一个特殊的二维数组,数组的每个元素不是数值而是一个向量
以上文 创建的mm多通道对象为例遍历输出多通道矩阵mm的每个元素
for (int r = 0; r < mm.rows; r++)
{
for (int c = 0; c < mm.cols; c++)
{
cout << mm.at<Vec3f>(r, c);
}
cout << endl;
}
输出:
2、利用成员函数ptr
多通道Mat的元素在内存中也是按行存储,每一个行存储在连续内存区域中,如果行与行之间由间隔,间隔相等。成员函数ptr返回指向指定行的第一个元素的指针。
使用ptr访问mm对象的每个元素。
for (int r = 0; r < mm.rows; r++)
{
Vec3f* ptr = mm.ptr<Vec3f>(r);
for (int c = 0; c < mm.cols; c++)
{
cout << ptr[c] << ",";
if (c % mm.cols)
{
cout << endl;
}
}
}
输出与使用at相同
3、使用成员函数isContinuous 和 ptr
与单通道Mat对象一样,可通过isContinuous判断整个Mat对象中的元素是否存储在连续内存中,返回true即表示连续存储;如果是连续存储,就可以少一个循环用ptr遍历
if (mm.isContinuous())
{
//获取指向第一行的指针
Vec3f* ptr = mm.ptr<Vec3f>(0);
for (int i = 0; i < mm.total(); i++)
{
cout << ptr[i] << ",";
if (i % mm.cols)
{
cout << endl;
}
}
}
4、使用成员变量step和data
与单通道Mat类似,step[0]代表每一行所占的字节数,如果有间隔则间隔计算在内,step[1]代表每一个个元素所占的字节数,step[1] = elemSize1()*channels()
elemSize1()代表一个元素中一个数值所占的字节数;data代表首个数值的地址
下面使用step和data遍历输出mm中的元素:
for (int r = 0; r < mm.rows; r++)
{
for (int c = 0; c < mm.cols; c++)
{
cout <<*(Vec3f*) (mm.data + r*mm.step[0]+c*mm.step[1]);
}
cout << endl;
}
输出与1,2,3相同
5、分离通道
以上述讨论的mm多通道对象为例,将所有向量第一个数值组成的二维矩阵作为第一通道,将所有向量第二个数值组成的单通道矩阵作为第二通道以此类推;
所以mm分离后的三个通道为
第一通道:2x2的矩阵,元素为 1、2、3、4
第二通道:2x2的矩阵,元素为 11、12、13、24
第三通道:2x2的矩阵,元素为 21、32、23、34
使用split可以将mm分离为多个单通道:
vector<Mat>planes;
split(mm, planes);
5、合并通道
使用merge函数可以将多个单通道矩阵合并为一个三维矩阵
该函数声明为:
void merge (const Mat *mv, size_t count, OutputArray dst)
//其重载函数
void merge (InputArrayofArrays mv, OutputArray dst)
例如将三个2*2int型单通道合并:
Mat plane0 = (Mat_<int>(2, 2) << 1, 2, 3, 4);
Mat plane1 = (Mat_<int>(2, 2) << 5, 6, 7, 8);
Mat plane2 = (Mat_<int>(2, 2) << 9, 10, 11, 12);
//初始化Mat数组也
Mat plane[]{ plane0, plane1, plane2 };
//声明一个mat对象用来保存合并后的多通道对象
Mat mat;
merge(plane, 3, mat);
//将三个单通道矩阵放在vector容器中
vector<Mat>pl;
pl.push_back(plane0);
pl.push_back(plane1);
pl.push_back(plane2);
Mat mat1;
merge(pl, mat);
获得Mat中某一个区域的值
1、使用row(i)或col(j)得到矩阵的第i行j列
Mat mr = m.row(1);
Mat mc = m.col(1);
2、使用rowRange或colRange得到矩阵的连续行或连续列
首先知道Opencv中的Range类,该类用于构造连续整数序列,左闭右开。
Range(2,5)产生2、3、4序列。以5*5矩阵为例
m
a
t
r
i
x
=
[
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
]
matrix = \left[ \begin{matrix} 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 \end{matrix} \right]
matrix=⎣⎢⎢⎢⎢⎡16111621271217223813182349141924510152025⎦⎥⎥⎥⎥⎤
//构造一个5*5int型矩阵
Mat matrix = (Mat_<int>(5, 5) << 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);
//访问第2、3两行
Mat r_range = matrix.rowRange(Range(2, 4));//可以写为matrix.rowRange(2,4)
for (int r = 0; r < r_range.rows; r++)
{
for (int c = 0; c < r_range.cols; c++)
{
cout << r_range.at<int>(r, c) << ",";
}
cout << endl;
}
输出:
colRange与之类似
Mat c_range = matrix.colRange(Range(2, 4));
for (int r = 0; r < c_range.rows; r++)
{
for (int c = 0; c < c_range.cols; c++)
{
cout << c_range.at<int>(r, c) << ",";
}
cout << endl;
}
输出:
注意:成员函数row、 col 、 rowRange 、colRange 返回的矩阵是指向原矩阵的,例如改变c_range第1行1列的值,原矩阵也会改变:
c_range.at<int>(1, 1) = 1000;//改为1000
cout << "输出c_range" <<endl;
for (int r = 0; r < c_range.rows; r++)
{
for (int c = 0; c < c_range.cols; c++)
{
cout << c_range.at<int>(r, c) << ",";
}
cout << endl;
}
cout << "输出原矩阵" << endl;
for (int r = 0; r < matrix.rows; r++)
{
for (int c = 0; c < matrix.cols; c++)
{
cout << matrix.at<int>(r, c) << ",";
}
cout << endl;
}
输出:
使用成员函数clone和copyTo可以解决这个问题
3、使用rowRange或colRange得到矩阵的连续行或连续列
使用这两个成员函数会将矩阵克隆一份,以上述r_range为例
Mat r_range = matrix.rowRange(Range(2, 4)).clone();
r_range.at<int>(0, 0) = 1000;//修改值为1000
cout << "输出r_range" << endl;
for (int r = 0; r < r_range.rows; r++)
{
for (int c = 0; c < r_range.cols; c++)
{
cout << r_range.at<int>(r, c) << ",";
}
cout << endl;
}
cout << "输出原矩阵" << endl;
for (int r = 0; r < matrix.rows; r++)
{
for (int c = 0; c < matrix.cols; c++)
{
cout << matrix.at<int>(r, c) << ",";
}
cout << endl;
}
输出:
copyTo的用法为:
matrix.rowRange(2,4).copyTo(r_range);
4、使用Rect类
如果我们需要矩阵中一块矩形区域,我们可以使用rowRange和colRange来定位,但是Opencv提供了Rect类来简化操作。知道矩形的左上角坐标,和矩形的宽高就可以确定一个矩形,所以其构造函数为:
Rect(int _x,int _y, int _width, int _height);
也可以将 _width 和 _height保存在一个Size中
Rect(int _x,int _y, Size size);
如果知道左上角和右下角的坐标也可以确定一个矩形,所以构造函数为:
Rect(Point2i &pt1,Point2i &pt2);
以之前叙述的5*5矩阵matrix为例,要获得使矩形框中的值为8,9,13,14
可以按如下方式构建矩形框
Mat roi1 = matrix(Rect(Point(2, 1), Point(4, 3)));//左上角和右下角坐标,"左开右闭"
Mat roi2 = matrix(Rect(2, 1, 2, 2)) ;//x,y,宽,高
Mat roi3 = matrix(Rect(Point(2, 1), Size(2,2)));//左上角坐标,尺寸
使用for循环输出:
这样得到的矩形区域仍然是指向原矩阵的所以,仍然可以使用clone和copyTo
Mat roi1 = matrix(Rect(Point(2, 1), Point(4, 3))).clone;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】博客园2025新款「AI繁忙」系列T恤上架,前往周边小店选购
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Android编译时动态插入代码原理与实践
· 解锁.NET 9性能优化黑科技:从内存管理到Web性能的最全指南
· 通过一个DEMO理解MCP(模型上下文协议)的生命周期
· MySQL下200GB大表备份,利用传输表空间解决停服发版表备份问题
· 记一次 .NET某固高运动卡测试 卡慢分析
· .net clr 8年才修复的BUG,你让我损失太多了
· 做Docx预览,一定要做这个神库!!
· 一个开源的 Blazor 跨平台入门级实战项目
· Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码
· 微信小程序/H5 调起确认收款界面