< NCNN-Lession-7 > Mat类的实现

< NCNN-Lession-7 > Mat类的实现

开始

这一节我们终于要学习Mat类.大家可以看到,这个类的名字”Mat”其实和Opencv中常用的Mat类是一样的名字,但二者不是同一个东西,一个是ncnn的Mat类,一个是Opencv的Mat类,大家要注意.好,废话不多说,我们再插上一个小红旗(压压惊):

作用

Mat类的作用其实存放神经网络需要处理的数据,这些数据一般包含一下几种(不限于):

  • 输入图片
  • 模型的weight和bias
  • 模型处理的top_blob和bottom_blob

Mat好处在于如下(不限于):

  • 方便的构建数据,如根据长宽高构建不同维度的数据.
  • 方便的索引数据,如按照channel索引数据.
  • 可以很方便的获取数据信息,如维度,长,宽等等.
  • 可以很方便的拷贝数据,如clone方法

实现

首先不废话,先上个Mat的概况图:

上图中,右半部分是Mat类所包含的必要的成员变量,左半部分是Mat所包含的必要的成员函数.

成员函数很简单,名字和类型都在上图中有写到,这里就不再做过多的说明.下面我们着重说一下Mat类的成员函数.

我们从开辟内存的函数Mat::create来说起.我看先来看一下它的实现:

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
26
27
inline void Mat::create(int _w, int _h, int _c, size_t _elemsize, int _elempack,          
Allocator* _allocator) {
if (dims == 3 && w == _w && h == _h && c == _c && elemsize == _elemsize &&
elempack == _elempack && allocator == _allocator)
return;

release();
elemsize = _elemsize;
elempack = _elempack;
allocator = _allocator;

dims = 3;
w = _w;
h = _h;
c = _c; //pad_y
cstep = alignSize((size_t)w * h * elemsize, 16) / elemsize;

if (total() > 0) {
//pad_z
size_t totalsize = alignSize(total() * elemsize, 4);
//pad_x
data = fastMalloc(totalsize + (int)sizeof(*refcount));
refcount = (int*)(((unsigned char*)data) + totalsize);
*refcount = 1;
}
}

上述函数针对一个三维的数组开辟空间,三个维度分别为长h,宽w和通道数c(channel).

其中cstep使用了AlignSize()函数,它的含义是计算出一个大于步长数字且该数可以刚好被对齐数(16)整除,这个数字其实就是每一个channel所包含的字节空间数.

计算这个数字的目的是为了进行另外一个维度的对齐,也就是空间尺寸上的对齐.我们在上一节中讲到,alllocator中的alignPtr函数做到了首地址上的对齐.

由于每一个元素的字节数是elemsize,所以一共需要申请的空间如下:

1
2
size_t tmp_size = c*  cstep * elemsize
size_t totalsize = alignSize(tmp_size, 4);

关于具体的空间分配情况,我做了一个如下的表格来说明,假如说我们申请了一个(2x2x4)的Mat,具体的空间分配方式如下所示

head pad_x (head_use)elemsize elemsize elemsize elemsize pad_y pad_y… (x)
(x) (x) (channel2)elemsize elemsize elemsize elemsize pad_y pad_y… (x)
(x) (x) (channel3)elemsize elemsize elemsize elemsize pad_y pad_y… (x)
(x) (x) (channel4)elemsize elemsize elemsize elemsize pad_y pad_y… pad_z…

下面对上面的表格做一个说明:

  • 其中head代表总的申请空间的地址.

  • pad_x是为了让使用的空间的首地址能够对齐来进行的把head指针向前推移的操作,

  • elemsize是每一个元素占用的空间.

  • pad_y是为了使得申请的cstep得到空间上的对齐而额外pad的空间,pay_z是为了使的申请的total_size对对齐而额外pad的空间.

  • (x)没有任何意义,请跳过

  • 上个分为4行表示主要是因为我们的channel共有4个,每一行代表一个channel,在实际的内存中,这四行其实是连续的.

下面我们说一下channel这个函数,这个函数其实是构造了一个Mat对象,只不过把Mat对象的数据指针给移动到了对应的位置上:

1
2
3
inline Mat Mat::channel(int _c) {                                                         
return Mat(w, h, (unsigned char*)data + cstep * _c * elemsize, elemsize, elempack, allocator);
}

其他的函数都是大同小异,大家可以看今天的示例代码.

最后还要说一下,Mat类中定义了一个重载类型转化符,当Mat类被某个指针强制转化的时候,其实是返回了它的成员函数data的指针被强制转化的结果:

1
2
3
4
template <typename T>         
inline Mat::operator T*() {
return (T*)data;
}

有的同学可能注意到Mat这个类还有两个的参数没有说,那就是elempack和refcount,我们这一节暂时不讲.

代码示例

测试程序在这里

代码结构如下:

< NCNN-Lession-7 > Mat类的实现

https://zhengtq.github.io/2020/12/15/ncnn-lesson-7/

Author

Billy

Posted on

2020-12-15

Updated on

2021-03-13

Licensed under

Comments