OpenCV(一) 深入理解Mat

前言

刚入门OpenCV不久,觉得Mat是非常关键的东东,于是收集了许多资料结合自己的体会整理这篇深入剖析的文章。话不多说,直接进入主题。

Mat剖析

1, 前世与今生
OpenCV 自 2001 年出现以来。在那些日子里库是围绕C接口构建的。在那些日子里,他们使用名为IplImage C 的结构在内存中存储图像[1]。而C结构最大的问题是要程序员自己分配和管理内存,当代码量大了,管理成本就很高,而导致不能着眼于真正要解决的实际问题。
在这里插入图片描述
随c++的出现,引入类的概念,于是OpenCV2.0版本开始引入C++接口,不需要手动内存管理了,使用起来也更加方便了。这个引入的C++结构就是伟大的图像容器类Mat。

2,庐山真面目
是时候上这副著名的图了,直接祭出来。
在这里插入图片描述
从这张图中,我们明显地可以得出这样一个事实:人类看到的是图像,而计算机看到的是这种数字矩阵。计算机通过矩阵来认识一张图片并存储它,也就是说Mat的本质其实就是矩阵/多维数组。
再来看看C++中Mat的详细定义,追根溯源,窥其源码:

class CV_EXPORTS Mat
{
public:
    // 一堆函数
    // ...
    // 以下是主要成员变量
    // <1>
    /*! includes several bit-fields:
         - the magic signature
         - continuity flag
         - depth
         - number of channels
     */
    int flags;
    //! the matrix dimensionality, >= 2
    int dims;
    //! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
    int rows, cols;
    //! pointer to the data
    uchar* data;
    // <2>
    //! helper fields used in locateROI and adjustROI
    const uchar* datastart;
    const uchar* dataend;
    const uchar* datalimit;
    // <3>
    //! interaction with UMat
    UMatData* u;
}

这里只列出了一些主要的成员变量,省略了函数部分,并且分为三个部分[2]:

  1. 第<1>部分是与矩阵相关的一些信息,flags 储存了矩阵的标识、是否连续、深度、通道数等信息,dims 代表矩阵的维数,rowscols 代表矩阵的行数与列数,data 是指向储存矩阵数据的指针。
  2. 第<2>部分是与感兴趣区域有关的信息,感兴趣区域即 ROI(Region Of Interset)。
  3. 第<3>部分是一个指针,指向一个 UMatData 类型,是跟引用计数相关的。(这是很容易理解的:由一张图片储存成的矩阵通常不小,例如一张 1024 * 768 的三通道彩色图,储存的矩阵就占了 1024 * 768 * 3 * 1 = 2359296 Byte = 2304 KB。而 OpenCV 作为计算机视觉库,它就是负责处理一大堆的这些图像信息,所以经常拷贝大的图像,开销是非常大的。自然地,就采用了引用计数。)

其实综合一下,Mat结构主要包含两个部分:矩阵头,指向矩阵数据的指针
矩阵头包含:矩阵尺寸、存储方式、存储地址、引用计数等,每个Mat,都有恒定大小的矩阵头。指针所指向的矩阵数据则因实际存储的图像不同而不同。
这里插一段,有指针就需要探讨它的拷贝函数是深拷贝还是浅拷贝。假设有了一个Mat对象src, 现在要将它拷贝给另一个空的Mat对象dst。则如下两种方法是浅拷贝:

//1, 赋值运算符
dst = src;
//2, 构造函数
Mat dst(src);

这种浅拷贝,复制矩阵头,以及指向矩阵数据的指针。但真实的矩阵数据仍然只有一份,也就是说改掉dst的某个像素值,src的对应像素值也会被更改。当然下面两种方法也可以实现深拷贝:

//1, clone()方法
dst = src.clone();
//2, copyTo()方法
src.copyTo(dst);

深拷贝,包括拷贝一份真实的矩阵数据,dst,src分别更改时互不干扰。以上两种方法效果都是一样的。
3,武功秘籍

明白了Mat是什么,更要明白它该如何用,发挥它最大的实力,也就是学会它的方法。
3.1 构造方法

Mat ()
Mat (int rows, int cols, int type)
Mat (Size size, int type)
Mat (int rows, int cols, int type, const Scalar &s)
Mat (Size size, int type, const Scalar &s)
Mat (int ndims, const int *sizes, int type)
Mat (int ndims, const int *sizes, int type, const Scalar &s)
Mat (const Mat &m)

比如构造一个Mat对象:Mat M(3, 3, CV_8UC3, Scalar(0, 255, 255)); 表示创建一个3x3,三通道无符号char类型的矩阵,每个通道占8位,每个像素有三个通道且值固定为(0, 255, 255)。
还能通过create()方法构造Mat,但该方法不能提供像素值(也就是没有Scalar参数),通常创建一个与src同大小同类型的Mat: Mat dst = src.create(src.size(), src.type());
除了自身构造,还可以拷贝构造,分深浅拷贝,之前说过了这里就不多费口舌了。

3.2 convertTo()方法

void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;

可以在缩放或者不缩放的情况下改变数据类型。dst表示目标矩阵;type是需要输出的类型,若是负值(常用-1)则表示输入输出矩阵类型相同;alpha是比例因子;beta是将输入数组元素按比例缩放后添加的值[3]。
比如经常会在svm中出现src.convertTo(src, CV_32FC1); 因为svm的训练数据集必须是单通道32位有符号char类型数据。

3.3 reshape()方法

Mat reshape(int cn, int rows=0) const;
Mat reshape(int cn, int newndims, const int* newsz) const;

其中cn表示通道数,rows表示行数。若灰度图像src调用 src.reshape(1, 1);可以实现将灰度图扁平化,变成一个“虚vector”(有点类似python的numpy里面的ArrayByte()方法)。这里还找到许多reshape的例子,例子在[4]。

3.4 at()方法

这个方法有很多重载,定义时用了泛型,就不放上来了。简单来说at()方法可以用于获取图像矩阵某点的值或者改变它。
对于单通道图像的使用方法:src.at< T >(i, j); 其中T表示泛型,可以是uchar, float, double等
对于三通道图像的使用方法:src.at< T >(i, j)[0] = 255; 之类, 表示修改(i, j)像素第0通道的值为255。

3.5 定义小矩阵

可以用Mat模板的子类Mat_< T >来定义小矩阵。它的计算比较省时间,效率高。通常用于卷积的核就被声明为Mat_< T>。比如Mat kernel = (Mat_< float >(3, 3)<< 0, -1, 0, -1, 5, -1, 0, -1, 0);
(注意:Mat_< T >取像素值或改变它不需要用at()方法了,比如 RGB图像 src(i, j)[0] = 255 )

3.6 vector通过Mat

vector可以通过Mat, 就是说,vector可以传入Mat的构造函数,构造成一个Mat。例如:

vector< float > data;
Mat M = Mat(data);

有时候,想直接输出一个vector, 但cout<<没有对vector重载,于是,,可以cout<<Mat(data)<<endl;就可以输出vector了。

当然,还有很多其他神奇的方法,限于篇幅,大家自己去探索了。

参考:
[1] https://blog.csdn.net/guyuealian/article/details/70159660 《OpenCV Mat类详解和用法》
[2] http://www.alinshans.com/2017/07/09/p1707091 《OpenCV 笔记(二):Mat 类初探》
[3] https://blog.csdn.net/qq_22764813/article/details/52135686 《openCV中convertTo的用法》
[4] https://www.cnblogs.com/denny402/p/5035535.html 《opencv3学习:reshape函数》

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值