Skip to content

Opencv中数据结构Mat的相关属性 #21

Description

@upcAutoLang

Opencv中数据结构Mat的相关属性

注:文中部分公式在GitHub上不能显示,可以移步我的CSDN博客进行查阅:
http://blog.csdn.net/ajianyingxiaoqinghan/article/details/72179896

前言:

The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. It can be used to store real or complex-valued vectors and matrices, grayscale or color images, voxel volumes, vector fields, point clouds, tensors, histograms (though, very high-dimensional histograms may be better stored in a SparseMat ).
Mat类用于表示一个多维的单通道或者多通道的稠密数组。它能够用来存储实数或附属的向量、矩阵、灰度/彩色图像、立体元素、点云、张量,以及直方图(虽然高维的直方图用SparseMat保存比较好)。

以上摘自OpenCV 2.4.9的官方文档opencv2refman.pdf。

以前虽然能够比较熟练的使用OpenCV,但是最近感觉其实笔者自己对OpenCV的最底层数据结构Mat与IplImage都不怎么熟悉…… 由于笔者比较反感总是需要管理内存的IplImage,所以对Mat数据结构做一下学习工作还是有必要的。

官方说明文档opencv2refman.pdf中,写出了Mat的定义如下:

class CV_EXPORTS Mat
{
public:
// ... a lot of methods ...
...
/*! includes several bit-fields:
- the magic signature
- continuity flag
- depth
- number of channels
*/
int flags;
//! the array dimensionality, >= 2
int dims;
//! the number of rows and columns or (-1, -1) when the array has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;
//! pointer to the reference counter;
// when array points to user-allocated data, the pointer is NULL
int* refcount;
// other members
...
};

下面笔者将从几个方面总结Mat数据结构的主要组成。

参考网址:
《OpenCV中对Mat里面depth,dims,channels,step,data,elemSize和数据地址计算的理解 》
《OpenCV Mat的常见属性》
《OpenCV学习笔记(四十)——再谈OpenCV数据结构Mat详解》

参考文档:
《opencv2refman.pdf》

一. Mat重要数据成员简要列举

如上面的Mat定义源码,Mat类中有很多重要的数据类型成员。
下面进行简单的列举。

  • dims:两者表示矩阵M的维度,如34的矩阵为2维,34*5的矩阵为3维;
  • data:Mat对象中的指针,指向存放内存中存放矩阵数据的一块内存,即:uchar* data;
  • step:定义了矩阵布局的数组,具体见后面的图片解释;
  • rows, cols:矩阵的行数、列数;
  • depth:即图像每一个像素的位数(bits);具体在后面的地址部分解释;
  • type:表示了矩阵中元素的类型(depth)与矩阵的通道个数(channels);具体在后面解释;
  • channels:通道数量;若图像为RGB、HSV等三通道图像,则channels = 3;若图像为灰度图,则为单通道;
  • elemSize:矩阵中每一个元素的数据大小;具体在后面进行解释;

1. depth, channels, type, elemSize, elemSize1

把这四个数据成员放在一起,是因为这四个数据成员相互之间有关系。

(1) depth

数据的存储一直都是个值得关注的问题,所以数据元素存储的位数和范围就十分重要了。depth就体现了每一个像素的位数,即深度。
Mat中包含的图像深度如下所示:

  • CV_8U:8位无符号整数(0--255),对应Mat_< uchar>
  • CV_8S:8位有符号整数(-128--127),对应Mat_< char >
  • CV_16U:16位无符号整数(0--65535)
  • CV_16S:16位有符号整数(-32768--32767)
  • CV_32S:32位有符号整数(-2147483648--2147483647),对应Mat_< int >
  • CV_32F:32位浮点数(-FLT_MAX..FLT_MAX, INF, NAN),对应Mat_< float >
  • CV_64F:64位浮点数 ( -DBL_MAX..DBL_MAX, INF, NAN)

另外还需要注意:大部分OpenCV的函数支持的数据深度只有8位和32位,所以尽量使用CV_64F。

(2) channels

channels表示了矩阵拥有的通道数量,这个比较容易理解:

  • 若图像为RGB、HSV等三通道图像,则channels == 3;
  • 若图像为灰度图,则为单通道,channels == 1;

(3) type

type表示矩阵中元素的类型(depth)与矩阵的通道个数(channels),可以理解成上面的depth与channels的综合说明。type是一系列预定义的常量,命名规则如下:
CV_+位数+数据类型+通道数
具体有如下值:

数据类型 1 2 3 4
CV_8U CV_8UC1 CV_8UC2 CV_8UC3 CV_8UC4
CV_8S CV_8SC1 CV_8SC2 CV_8SC3 CV_8SC4
CV_16U CV_16UC1 CV_16UC2 CV_16UC3 CV_16UC4
CV_16S CV_16SC1 CV_16SC2 CV_16SC3 CV_16SC4
CV_32S CV_32SC1 CV_32SC2 CV_32SC3 CV_32SC4
CV_32F CV_32FC1 CV_32FC2 CV_32FC3 CV_32FC4
CV_64F CV_64FC1 CV_64FC2 CV_64FC3 CV_64FC4

表格中,行代表了通道数量channels,列代表了图像深度depth。
例如CV_8UC3,可以拆分为:

  • CV_:type的前缀
  • 8U:8位无符号整数(depth)
  • C3:3通道(channels)

注:type一般是在创建Mat对象时设定,若要去的Mat的元素类型,可以不使用type,使用depth。

(4) elemSize

elemSize表示了矩阵中每一个元素的数据大小,单位是字节。公式如下:
elemSize = channels * depth / 8
例如type == CV_16SC3,则elemSize = 3 * 16 / 8 = 6 Bytes。

(5) elemSize1

elemSize1表示了矩阵元素的一个通道占用的数据大小,单位是字节。公式如下:
elemSize = depth / 8
例如type == CV_16SC3,则elemSize1 = 16 / 8 = 2 Bytes。

二. Mat数据元素地址

使用OpenCV处理图像时,最普遍的处理方式便是遍历图像,即访问所有的图像像素点。但有的算法还需要访问目标像素的邻域,所以这时候就需要了解访问Mat数据元素地址的方式。

1. 像素地址计算公式

假设有矩阵M,则数据元素的地址计算公式如下:
$$ addr(M_{i_{0}, i_{1}, ... i_{m-1}}) = M.data + M.step[0] * i_{0} + M.step[1] * i_{1} + ... + M.step[M.dims - 1] * i_{M_{dims-1}} $$
如果是二维数组,则上述公式就简化成:
$$ addr(M_{i,j}) = M.data + M.step[0] * i + M.step[1] * j $$

注:式中m = M.dims,即矩阵的维度。

2. 举例说明

###(1) 二维矩阵
假设存在一个二维矩阵如下图所示:

上面是一个3 × 4的矩阵。此时我们按照数据类型为CV_8U, CV_8UC3的情况,分别对其进行讨论。

首先假设其数据类型为CV_8U,也就是单通道的uchar类型,则可以得出上面的数据成员情况分别为:

  • M.dims == 2:二维矩阵;
  • M.rows == 3:图像共三行;
  • M.cols == 4:图像共四列;
  • M.channels == 1:图像通道数为1;
  • M.elemSize() == 1:矩阵中每一个元素的数据大小为1,因为sizeof(uchar) == 1;
  • M.step = [4, 1]:由于是二维矩阵,所以step数组只有两个值;
    • step[0]代表一行数据大小,所以step[0] = 4 * 1 = 4;
    • step[1]代表一个元素的数据大小,即通道个数,所以step[1] = 1;

若假设其数据类型为CV_8UC3,也就是三通道的uchar类型,则可以得出上面的数据成员情况分别为:

  • M.dims == 2:二维矩阵;
  • M.rows == 3:图像共三行;
  • M.cols == 4:图像共四列;
  • M.channels == 3:图像通道数为3;
  • M.elemSize() == 1:矩阵中每一个元素的数据大小为1,因为sizeof(uchar) == 1;
  • M.step = [12, 3]:由于是二维矩阵,所以step数组只有两个值;
    • step[0]代表一行数据大小,所以step[0] = 4 * 3 = 12;
    • step[1]代表一个元素的数据大小,即通道个数,所以step[1] = 3;

(2) 三维矩阵

假设存在一个三维矩阵如下图所示:

上面是一个3 × 4 × 6的矩阵。假设其数据类型为CV_16SC4,此时对其进行讨论。

  • M.dims == 3:三维矩阵;
  • M.channels == 4:图像通道数为4;
  • M.elemSize() == M.elemSize1() * M.channels() == 2 * 4 = 8;
  • M.step = [192, 48, 3]:由于是三维矩阵,所以step数组有三个值;
    • M.step[2] == M.elemSize == 8;
    • M.step[1] == 6 * M.elemSize() == 48;
    • M.step[0] == 4 * 6 * M.elemSize() == 192;

3. 地址访问

关于OpenCV地址访问方法及效率的部分,请见笔者其他博文。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions