前言

OpenCV1时代采用基于C语言接口构建函数库,使用名为IplImage的结构体在内存中存储图像,其问题在于需要用户手动管理内存,如果不手动释放内存会造成内存泄漏。

OpenCV2引入面向对象编程思想,加入了一个c 接口,使用Mat类数据结构作为主打,可以实现自动内存管理,且扩展性大大提高。

Mat概述

对于Mat类,首先要知道的是

1)不必手动为其开辟空间;

2)不必再在不需要时将空间释放。

但手动做还也是可以的:大多数OpenCV函数仍会手动地为输出数据开辟空间。当传递一个已经存在的 Mat 对象时,开辟好的矩阵空间会被重用。

Mat是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等)和一个指向存储所有像素值矩阵的指针。

Mat类最重要的一点是浅拷贝和深拷贝问题。由于OpenCV处理图像时很多时候没有必要重新复制一份图像矩阵,因而采用了引用计数机制。其思路是让每个Mat对象有自己的信息头,但共享一个图像矩阵(矩阵指针指向同一地址)。赋值运算符和拷贝构造函数只复制矩阵头和矩阵指针,而不复制矩阵。

Mat A , C;

A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 为矩阵开辟内存

Mat B(A); // 拷贝构造函数

C = A; // 赋值

这里A、B、C矩阵头不同,但指向了相同的图像矩阵,其中一个对象对矩阵数据进行改变也会影响其他对象。如果矩阵属于多个Mat对象,由最后一个使用它的对象进行内存清理。这通过引用计数来判断,每复制一个Mat对象,计数器加一,每释放一个计数器减一,当计数器值为0时矩阵就会被清理。

如果需要进行对象的深拷贝可以采用clone()函数或者copyTo()。

Mat A;

A = imread(argv[1], CV_LOAD_IMAGE_COLOR);

Mat B = A.clone();

Mat C;

A.copyTo(C);

Mat类的数据成员

/*

flag的详细解释可以看 https://blog.csdn.net/yiyuehuan/article/details/43701797

0-2位 depth:每一个像素的位数,也就是每个通道的位数,即数据类型(如CV_8U)

enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }

8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F表示 64 位浮点数

3-11位 number of channels:代表通道数channels,最高512位

0-11位共同代表type:矩阵元素的类型,即通道数和数据类型(如CV_8UC3、CV_16UC2)

14位 continuity flag:代表Mat的内存是否连续

15位 submat flag:代表该Mat是否为某一个Mat的submatrix

16-31位 the magic signature:用来区分Mat的类型,如果Mat和SparseMat

*/

int flags;

//矩阵的维数,一般大于2

int dims;

//矩阵的行数与列数,超过2维矩阵时(-1,-1)

int rows, cols;

//指向存放矩阵数据的内存

uchar* data;

//用来控制ROI区域,来获取一些图像的局部切片,减少计算量或者特殊需求的。

const uchar* datastart;

const uchar* dataend;

const uchar* datalimit;

//如果需要创建一个新矩阵的内存空间,会调用MatAllocator类作为分配符进行内存的分配。

MatAllocator* allocator;

//interaction with UMat

//UMatData结构体总有一个成员refcount:记录了矩阵的数据被其他变量引用了多少次

UMatData* u;

//返回矩阵大小

MatSize size;

//矩阵元素寻址,step[i]表示第i维的总大小,单位字节

//对于2维矩阵:step[0]是矩阵中一行元素的字节数,step[1]是矩阵中一个元素的字节数

//以下公式可以得到Mat中任意元素地址

//addr(M{i,j})=M.data M.step[0]∗i M.step[1]∗j;

MatStep step;

此外其他版本还存在以下成员:

elemSize :矩阵一个元素占用的字节数,例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes。

elemSize1 :矩阵元素一个通道占用的字节数,例如:type是CV_16CS3,那么elemSize1 = 16 / 8 = 2 bytes = elemSize / channels。

Mat类的构造函数

//1、默认构造函数,无参数

Mat::Mat();

//2、行数为rows,列数为cols,类型为type(如CV_8UC1、CV_16UC2)

Mat(int rows, int cols, int type);

//3、矩阵大小为size,类型为type

//注意size的构造函数是Size_(_Tp _width,_Tp _height) 先列后行

Mat(Size size, int type);

//4、行数为rows,列数为cols(或矩阵大小为size),类型为type,所有元素初始化为s

//Scalar表示具有4个元素的数组,如Scalar(a,b,b),其原型为Scalar_

Mat(int rows, int cols, int type, const Scalar& s);

Mat(Size size, int type, const Scalar& s);

//5、矩阵维数为ndims,sizes为指定ndims维数组形状的整数数组,所有元素初始化为s

Mat(int ndims, const int* sizes, int type);

Mat(const std::vector& sizes, int type);

Mat(int ndims, const int* sizes, int type, const Scalar& s);

Mat(const std::vector& sizes, int type, const Scalar& s);

//6、拷贝构造函数,将m赋值给新创建的对象,浅拷贝

Mat(const Mat& m);

//7、行数为rows,列数为cols,类型为type,矩阵数据为data,直接使用data所指内存,浅拷贝

Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);

Mat(Size size, int type, void* data, size_t step=AUTO_STEP);

Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0);

Mat(const std::vector& sizes, int type, void* data, const size_t* steps=0);

//8、创建的新图像为m的一部分,范围由rowRange和colRange指定,新图像与m共用图像数据,浅拷贝

Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());

Mat(const Mat& m, const Range* ranges);

Mat(const Mat& m, const std::vector& ranges);

//创建的新图像为m的一部分,具体的范围由矩阵对象roi指定

//Rect的成员函数有x,y,width,height,分别为左上角点的坐标好矩阵宽和高

Mat(const Mat& m, const Rect& roi);

创建Mat对象

1、使用Mat()构造函数

Mat M(2,2, CV_8UC3, Scalar(0,0,255));

需要指定行数、列数、存储元素的数据类型以及每个矩阵点的通道数。

2、使用Mat()构造函数2

int sz[3] = {2,2,2};

Mat L(3,sz, CV_8UC(1), Scalar::all(0));

常用于创建一个超过两维的矩阵:指定维数,然后传递一个指向一个数组的指针,这个数组包含每个维度的尺寸,其余的相同

3、为已存在IplImage指针创建信息头(一般不用)

IplImage* img = cvLoadImage("greatwave.png", 1);

Mat mtx(img); // convert IplImage* -> Mat

4、利用create()函数

这个创建方法不能为矩阵设初值,它只是在改变尺寸时重新为矩阵数据开辟内存

M.create(4,4, CV_8UC(2));//4X4的图像矩阵,通道数为2,没有初值

5、MATLAB形式的初始化方式: zeros(), ones(), eyes()

Mat E = Mat::eye(4, 4, CV_64F);//4X4的单位矩阵

Mat O = Mat::ones(2, 2, CV_32F);//2X2的全为1矩阵

Mat Z = Mat::zeros(3,3, CV_8UC1);//3X3的零矩阵

6、小矩阵使用逗号分隔式初始化

Mat C = (Mat_(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1); //3X3的单位矩阵

7、使用clone()或copyto()为已存在对象创建新信息头

Mat RowClone = C.row(1).clone();//复制 C中的第2行[0,1,0]作为新矩阵(深拷贝)

遍历Mat对象

首先假设需要对图像将那些颜色空间缩减,如imgnew=imgold/10∗10img_{new} = img_{old} / 10 * 10imgnew​=imgold​/10∗10。对于较大的图像,一般不是对每个像素点每个通道的值进行如上计算,而是预先计算所有可能的值,构建查找表,然后利用查找表对其直接复制。

//构建查找表table

int divideWith;

cin >> divideWith;

uchar table[256];

for (int i = 0; i < 256; i)

table[i] = divideWith* (i/divideWith);

1、ptr指针单像素单通道值访问

即通过uchar* ptr =img.ptr(i); 得到第i行首地址,然后采用指针运算( )或者操作符[]遍历。

Mat& ScanImage(Mat& img,const uchar* const table)

{

CV_Assert(img.depth() != sizeof(uchar))//只接收uchar类型矩阵

int channels = img.channel();

int nRows = img.rows * channels;//注意行数乘以通道数

int nCols = img.cols;

if(img.isContinuous())//如果存储连续则可以使用一次循环

{

nCols *= nRows;

nRows = 1;

}

uchar* ptr;

for(int i=0; i

{

ptr = img.ptr(i);

for( j=0; j

{

ptr[j] = table[ptr[j]]

//*ptr = table[*ptr]

}

}

return I;

}

2、ptr指针单像素访问

利用cv::Vec3b *ptr =img.ptr<:vec3b>(i);得到第i行第一个像素的3个通道地址。

Mat& ScanImage(Mat& img,const uchar* const table)

{

CV_Assert(img.depth() != sizeof(uchar))//只接收uchar类型矩阵

int channels = img.channel();

int nRows = img.rows;//每一行

int nCols = img.cols;

Vec3b *ptr;

for(int i=0; i

{

ptr = img.ptr(i);

for( j=0; j

{

ptr[j][0] = table[ptr[j][0]];

ptr[j][1] = table[ptr[j][1]];

ptr[j][2] = table[ptr[j][2]];

}

}

return I;

}

3、对data操作

data会从Mat中返回指向矩阵第一行第一列的指针。常用来检查图像是否被成功读入(如果指针为NULL则表示无输入)。当矩阵式连续存储时,可以通过data遍历矩阵。

也可以采用addr(M{i,j})=M.data M.step[0]∗i M.step[1]∗j;得到每个元素的地址,但是不常用。

if(I.isContinuous())

{

uchar* p = img.data;

for( unsigned int i =0; i < ncol * nrows; i)

*p = table[*p];

}

4、迭代器iterator访问

使用迭代器操作像素,与STL库用法相似,只需要获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。

迭代器可以采用MatIterator_以及Mat的模板子类Mat_,它重载了operator()。

MatIterator_ it = img.begin();

Mat_::iterator it = img.begin();

Mat& ScanImage(Mat& I,const uchar* const table)

{

CV_Assert(img.depth() != sizeof(uchar));

const int channels = img.channels();

switch(channels)

{

case 1:

{

Mat_::iterator it = img.begin();

Mat_::iterator itend = img.end();

for(;it != itend; it)

*it = table[*it];

break;

}

case 3:

{

Mat_::iterator it = img.begin();

Mat_::iterator itend = img.end();

for(; it != itend ; it)

{

(*it)[0] = table[(*it)[0]];

(*it)[1] = table[(*it)[1]];

(*it)[2] = table[(*it)[2]];

}

}

}

return img;

}

5、动态地址计算(at)

该方法一般用于获取或更改图像中的某个元素(或随机元素),而不用于进行全图扫描。它的基本用途是要确定你试图访问的元素的所在行数与列数。

在debug模式下该方法会检查你的输入坐标是否有效或者超出范围,如果坐标有误则会输出一个标准的错误信息;在release模式下,它和其他的区别仅仅是对于矩阵的每个元素,都会获取一个新的行指针,然后通过该指针和[]操作获取元素。

Mat& ScanImage(Mat& img,const uchar* const table)

{

CV_Assert(img.depth() != sizeof(uchar));

const int channels = img.channels();

switch(channels)

{

case 1:

{

for(int i=0; i

for(int j=0; j

img.at(i,j) = table[img.at(i,j)];

break;

}

case 3:

{

for(int i=0; i

{

for(int j=0; j

{

img.at(i,j)[0] = table[img.at(i,j)[0]];

img.at(i,j)[1] = table[img.at(i,j)[1]];

img.at(i,j)[2] = table[img.at(i,j)[2]];

}

}

break;

}

}

return img;

}

6、LUT函数

LUT(Look up table)被官方推荐用于实现批量图像元素查找和更改。对于修改像素值,OpenCV提供函数operationsOnArray:LUT(),直接实现该操作,而不需要扫描图像。

//建立mat类对象用于查表

Mat LookUpTable(1,256,CV_8U);

uchar* p = LookUpTable.data;

for(int i=0; i<256; i)

p[i] = table[i];

//调用函数(I时输入,J是输出)

LUT(I, LookUpTable, J);

一般认为,采用ptr指针访问图像像素的效率更高;而迭代器则被认为是更安全的方式,仅需要获得图像矩阵的begin和end;at方法一般不用于对图像进行遍历;而调用LUT函数可以获得最快的速度,因为OpenCV库可以通过英特尔线程架构启用多线程。其他也存在一些比较偏的遍历方法,不过这几种最为常见,效率和安全性也相对较好。

来源:http://www.icode9.com/content-4-148551.html

c语言构造mat型数据类型,视觉SLAM——OpenCV之Mat结构详解 数据成员和构造函数 创建Mat方法 遍历Mat方法...相关推荐

  1. timm 视觉库中的 create_model 函数详解

    timm 视觉库中的 create_model 函数详解 最近一年 Vision Transformer 及其相关改进的工作层出不穷,在他们开源的代码中,大部分都用到了这样一个库:timm.各位炼丹师 ...

  2. Linux TC 流量控制与排队规则 qdisc 树型结构详解(以HTB和RED为例)

    1. 背景 Linux 操作系统中的流量控制器 TC (Traffic Control) 用于Linux内核的流量控制,它规定建立处理数据包的队列,并定义队列中的数据包被发送的方式,从而实现对流量的控 ...

  3. 在C语言中实现协程库(一)----------协程切换原理详解

    从这篇文章开始,我将一点一点详细介绍如何在c语言中实现协程库.并对其中涉及到的技术进行详细的解释. 感兴趣的小伙伴欢迎一起参与 代码地址 协程切换原理 使用glibc中<ucontext.h&g ...

  4. C语言之实型数据类型

    实型数据类型指的就是浮点数或实数 1.实型数据类型的分类 类型说明符 比特数(字节数) 有效数字 数的范围 float 32(4) 6~7 -10的38次方~10的38次方 double 64(8) ...

  5. C语言之字符型数据类型

    字符型数据类型就是字符 1.字符数据的表示 字符型数据是用单引号括起来的一个字符.例如: 'a'.'b'.'='.'+'.'?'都是合法字符型数据. 在C语言中,字符型数据有以下特点: 字符型数据只能 ...

  6. OpenCV 4.x API 详解与C++实例-Mat数据类型详解

    第二节 Mat数据类型详解 1.Mat数据类型描述 我们有多种从现实世界中获取数字图像的方法:数码相机,扫描仪,计算机断层扫描和磁共振成像等等. 在每种情况下,我们(人类)看到的都是图像. 但是,当将 ...

  7. c语言程序关键字是什么,C语言中32个关键字详解

    C语言中32个关键字详解 由 ANSI 标准定义的 C 语言关键字共32个,根据关键字的作用,可以将关键字分为数据类型关键字和流程控制关键字两大类. 一.数据类型关键字 A 基本数据类型(5个) vo ...

  8. mysql 短整型_C++ int,short,long(详解版)

    C++ 有许多不同类型的数据.变量根据其数据类型进行分类,并确定可能存储在其中的信息种类.在这些数据类型中,整型变量只能保存整数. 计算机程序从现实世界收集数据,并以各种方式操作它们.有许多不同类型的 ...

  9. Java中的String数据类型,String类(字符串)详解

    目录 第一章.String概述 1)String是什么 2)String长什么样 3)String的构造方法(声明方式) 第二章.String类的详解 1)String底层是什么 2)字符串存储的内存 ...

最新文章

  1. Java oracle查询语句无法赋值给_java.sql.SQLException: 无法转换为内部表示 -〉java 查询oracle数据库返回错误信息...
  2. Python基础之:数字字符串和列表
  3. 【恋上数据结构】图代码实现、BFS、DFS、拓扑排序
  4. CentOS7 安装 Mysql 服务
  5. vb mysql登录界面_VB\数据库--模拟系统登录界面
  6. Ubuntu下安装JRE7
  7. MongoDB可视化管理工具-Robo 3T
  8. Qt开发的超轻量http server
  9. zblog php修改代码,zblog模板建站新上线必须修改的优化代码
  10. 计算机ip怎么换路由器,路由器怎么换ip地址
  11. 孩子兄弟存储结构的几个统计算法实现
  12. 微信局域网测试环境搭建方法
  13. C#winform中OpenFileDialog的用法
  14. ARM linux系统调用的实现原理
  15. docker inspect container_name | grep Mounts -A 20
  16. 如何使用TensorFlow实现卷积神经网络
  17. 【转载文章】批处理经典入门教程!(从不懂到高手)____附加我的学习笔记
  18. 去除Typora红色波浪线
  19. 【NumberValidators】增值税发票代码验证
  20. rematch:当你受不了redux繁琐写法的时候,是时候了解一波rematch了

热门文章

  1. vue.js海康威视监控使用,以及遇到的问题
  2. 读《我是一个网卡》有感
  3. pyecharts echarts 更改数据集
  4. 对表格的td标签执行点击事件
  5. 【视频抖动程度检测】基于LK光流算法的视频图像序列抖动程度计算matlab仿真
  6. 在win10系统下搭建网站遇到“无法枚举容器中的对象,访问被拒绝”问题
  7. emqttd 服务器搭建及测试方法
  8. blueMoon (哗--)论坛自动点广告完成星成就自动道具工具
  9. 类模板分文件编写:错误 LNK2019 无法解析的外部符号 “public: __cdecl Person
  10. 基于最新的MAUI混合VUE3开发Android应用(2022-11-01)