OpenCV Mat类

OpenCV Mat类

class Mat 是 OpenCV 库的核心,用于矩阵数据存储,它包括两部分,头部 (矩阵的尺寸,存储的函数方法等),和数据块 (指向像素矩阵的指针)。Mat 不仅支持 二维矩阵,也支持多维张量。

和 OpenCV 传统的数据结构 IplImage 相比,Mat 无需手动分配和释放内存,非常方便。

1. 头部信息

变量或函数 功能
rows 矩阵行数,如果有填充的行,那么这里是填充后的行数
cols 矩阵列数,和上面类似
dims 矩阵维度, ≥ 2
size() 返回 Size 类型,cv::Size(cols, rows)
total() 矩阵元素总个数, value = ∏isize[i]
type() 矩阵每个元素类型,e.g. CV_8UC3 表示 unsigned 8 bit, 3 channels
elemSize() 矩阵每个元素占用的字节数
channels() 矩阵每个元素对应的通道数,比如 RGB 就是三通道,黑白就是单通道
depth() 矩阵的每个元素的数据类型,后面会有解释
step step[i] 表示第 i 维相邻元素的字节间隔,这取决于矩阵的存储顺序
data uchar* data ,指向图片内存块的首地址的指针

上面的函数看起来很多,本质就两个信息:

  1. 矩阵的尺寸 – 每个维度的尺寸
  2. 矩阵每个元素的类型 (type) – 通道数和数据类型

对于前者, OpenCV 是通过 Mat 的成员变量 MatSize size 管理的,由于 MatSize 实现了 () 操作符的重载,所以我们上面才可以使用 size()

对于后者,OpenCV 是通过成员变量 int flags 管理的,Mat 构造函数一般都需要传递这个变量

flags 通过 每个 bit 位存储了类型信息

  1. magic value = 0x42FF0000,低 16 位为 0
  2. continuity flag (1 bit,表示矩阵是否连续,第 14 位)
  3. number of channels ( 9 bit ),所以 Mat 最多有 29 = 512 个通道
  4. depth ( 3 bit )

其中 depth 表示的数据类型有 7 种,如下所示

我们说 type() 返回的是通道信息和数据类型信息的结合,OpenCV 提供如下的宏用于构造我们需要的 type,其中宏 CV_MAKETYPE(depth,cn) 进一步简化,就有了 CV_8UC3 , CV_16UC3 等我们经常使用的宏

CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]

2. 构造函数和浅层拷贝 (shallow copy)

OpenCV 提供多达 21 种 Mat 构造函数

上面几乎所有的构造函数都是浅层拷贝,也就是说,拷贝只拷贝头部和指向大型矩阵的指针,而不拷贝数据本身,这意味着,修改其中一个 Mat 的数据,其他 Mat 对应的数据也都会改变,但它们的头部信息是相互独立的。和 std 标准库的 shared_ptr 类似,Mat 内部有一个计数器,每拷贝一次,计数器的值加 1,每次头部清空,计数器的值减 1,当计数器的值为 0 时,会自动释放Mat 数据块占用的内存。

比如下面的例子:

值得一提的是,创建 region of interest (ROI) 也仅仅是浅层拷贝,它使用了 3 个成员变量

如果想深度拷贝,OpenCV 提供了 clone()copyTo() 函数,实现数据的拷贝

2.1 额外的构造方法

  1. 读取图片 imread

  2. Mat::create 函数,这个函数是 Mat 的核心方法。算法流程如下:

    • 如果当前形状和类型和新的匹配,直接返回。否则,通过 Mat::release() 解引用先前的数据,表示自己不再使用
    • 初始化新的头部结构
    • 分配新的内存,字节数为 total() * elemSize()
    • 分配新的计数器,和内存数据的引用相联系,并设为 1
  3. 类似 Matlab 的函数 zeros(), ones(), eye()

  4. 对于小型数据,可以使用逗号分割初始化

3. 遍历方法

3.1 通过 Match::ptr 返回每行首地址

如果数据是连续存储,则可以通过一次循环实现

3.2 通过 Mat::at 选择指定坐标

e.g. 选择 列为 j,行为 i 对应点的三通道 value

注意:

  • Mat::at() 的第一个参数是 ,第二个参数是 (可以从 GUI的角度理解,如果左上角为原点,那么一个点的横坐标是列数, 纵坐标是行数)
  • 存储通道的数据的次序是蓝色、绿色、红色 (BGR)

3.3 C语言 指针

Mat的头部有一个 指针 uchar * data, 表示数据块的首地址 通过 data += image.step[0] 实现

3.4 迭代器方法遍历

迭代器的两种表示方式:

begin() / end() 获得起始迭代器和终止迭代器

注意: 在循环内部使用 取值运算符* 来访问当前元素 读 -> element = *it; 写 -> *it = element;

3.5 速度比较

首选第一种方法 Mat::ptr<T>() ,如果像素值是连续存储的,使用 C 语言指针的方法也是可行的。

如果只需要改变个别位置像素点的值,推荐使用 Mat::at

虽然使用迭代器很方便,但速度大约比 Mat::ptr<T>() 慢 10%,对于实时性要求比较高的场合,不太适用。

4. Mat_ 类的使用

The class Mat_<_Tp> is a “thin” template wrapper on top of the Mat class. It does not have any extra data fields. Nor this class nor Mat has any virtual methods. Thus, references or pointers to these two classes can be freely but carefully converted one to another.

While Mat is sufficient in most cases, Mat_ can be more convenient if you use a lot of element access operations and if you know matrix type at the compilation time. Note: Mat::at<_Tp>(int y, int x) and Mat_<_Tp>::operator ()(int y, int x) do absolutely the same and run at the same speed, but the latter is certainly shorter

To use Mat_ for multi-channel images/matrices, pass Vec as a Mat_ parameter:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: