AI学习者 · 2021年11月17日

【NCNN源码解读】Mat类(一)

由于最近项目需要用到NCNN,发现用起来还是非常舒适的,纯c++代码,忍不住看了看源码,顺便做做笔记,实际就是在源码上做了些注释。由于代码较多,这里只记录比较重要的部分。

构造函数

/* 创建一个Mat
  elemsize: float32/ int32: 4; float16: 2; int8/uint8: 1; empty: 0
  elempack: 表示一个数据包的元素个数,就是对Mat的元素分组打包
  dims: Mat维度
  w、h、c: Mat的宽、高、通道数
  cstep: 一个通道的大小,可以看作是w*h的乘积
  */
 inline Mat::Mat()
     : data(0), refcount(0), elemsize(0), elempack(0), allocator(0), dims(0), w(0), h(0), c(0), cstep(0)
 {
 }
 ​
 inline Mat::Mat(int _w, int _h, int _c, size_t _elemsize, Allocator* _allocator)
     : data(0), refcount(0), elemsize(0), elempack(0), allocator(0), dims(0), w(0), h(0), c(0), cstep(0)
 {
     create(_w, _h, _c, _elemsize, _allocator);
 }
 ​
 inline Mat::Mat(int _w, int _h, int _c, size_t _elemsize, int _elempack, Allocator* _allocator)
     : data(0), refcount(0), elemsize(0), elempack(0), allocator(0), dims(0), w(0), h(0), c(0), cstep(0)
 {
     create(_w, _h, _c, _elemsize, _elempack, _allocator);
 }
 /*
  创建Mat的函数
  */
 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)  // 如果当前的Mat初始化结果满足create的结果,无需再次创建新的Mat,则直接返回即可
         return;
 ​
     release();
 ​
     elemsize = _elemsize;
     elempack = _elempack;
     allocator = _allocator;
 ​
     dims = 3;
     w = _w;
     h = _h;
     c = _c;
 ​
     cstep = alignSize((size_t)w * h * elemsize, 16) / elemsize;  //每个通道的元素个数
 ​
     if (total() > 0)  // total()表示所有的元素个数,cstep*c
     {
         size_t totalsize = alignSize(total() * elemsize, 4); // 除了数据需要分配的空间大小,还需要一个引用计数的空间
         if (allocator)
             data = allocator->fastMalloc(totalsize + (int)sizeof(*refcount));
         else
             data = fastMalloc(totalsize + (int)sizeof(*refcount));
         refcount = (int*)(((unsigned char*)data) + totalsize);
         *refcount = 1;
     }
 }
 ​
 /*
  创建一个shape和m相同的Mat
  */
 inline void Mat::create_like(const Mat& m, Allocator* _allocator)
 {
     int _dims = m.dims;
     if (_dims == 1)
         create(m.w, m.elemsize, m.elempack, _allocator);
     if (_dims == 2)
         create(m.w, m.h, m.elemsize, m.elempack, _allocator);
     if (_dims == 3)
         create(m.w, m.h, m.c, m.elemsize, m.elempack, _allocator);
 }

shape相关

/*
  shape函数返回的是一个shape大小的Mat, 而不是pytorch那样直接用字符串表示的shape
 */
 inline Mat Mat::shape() const
 {
     if (dims == 1)
         return Mat(w * elempack, (void*)0);
     if (dims == 2)
         return Mat(w, h * elempack, (void*)0);
     if (dims == 3)
         return Mat(w, h, c * elempack, (void*)0);
     return Mat();
 }
 ​
 /*
  reshape到指定的维度大小
 */
 inline Mat Mat::reshape(int _w, int _h, int _c, Allocator* _allocator) const
 {
     if (w * h * c != _w * _h * _c)  // 如果元素总数不对应,则返回空
         return Mat();
 ​
     if (dims < 3) // 当前的Mat是一维向量或者二维矩阵,要转换成三维的情况
     {
         if ((size_t)_w * _h != alignSize((size_t)_w * _h * elemsize, 16) / elemsize)
         {
             Mat m;
             m.create(_w, _h, _c, elemsize, elempack, _allocator);
 ​
             // align channel
             for (int i = 0; i < _c; i++)  // 每个通道放入_w*_h个元素
             {
                 const void* ptr = (unsigned char*)data + (size_t)i * _w * _h * elemsize; // 从data的头指针开始的第i * _w * _h * elemsize位置为起始位置 写
                 void* mptr = (unsigned char*)m.data + i * m.cstep * m.elemsize; // 从新Mat m的第i * m.cstep * m.elemsize位置接收数据
                 memcpy(mptr, ptr, (size_t)_w * _h * elemsize); // 拷贝到mptr _w*_h个数据
             }
 ​
             return m;
         }
     }
     else if (c != _c)  // 如果当前Mat是三维的,并且通道数不同,则先将当前的Mat展平成一维向量,然后reshape
     {
         // flatten and then align
         Mat tmp = reshape(_w * _h * _c, _allocator);
         return tmp.reshape(_w, _h, _c, _allocator);
     }
     //如果是三维的,并且通道数也相同,按照设定的尺寸重新分配空间,可能的情况是3x4x5->3x5x4, 宽和高转换维度
     Mat m = *this;
 ​
     m.dims = 3;
     m.w = _w;
     m.h = _h;
     m.c = _c;
     m.cstep = alignSize((size_t)_w * _h * elemsize, 16) / elemsize;
     return m;
 }

索引函数

inline Mat Mat::channel(int _c)  // 返回第_c个通道的Mat, _c作为起始位置
 {
     return Mat(w, h, (unsigned char*)data + cstep * _c * elemsize, elemsize, elempack, allocator);
 }
 ​
 inline Mat Mat::channel_range(int _c, int channels) // 返回从第_c个通道开始的channels个通道的Mat
 {
     return Mat(w, h, channels, (unsigned char*)data + cstep * _c * elemsize, elemsize, elempack, allocator);
 }
 ​
 inline Mat Mat::row_range(int y, int rows) // 返回从第y行开始,取rows行的Mat
 {
     return Mat(w, rows, (unsigned char*)data + (size_t)w * y * elemsize, elemsize, elempack, allocator);
 }
 ​
 inline Mat Mat::range(int x, int n) // 返回从第x个元素开始,n个元素的Mat
 {
     return Mat(n, (unsigned char*)data + x * elemsize, elemsize, elempack, allocator);
 }

需要注意的是,NCNN Mat可以使用[]进行索引,但是存在补齐的机制,所以最好不要直接去直接对整个Mat索引,最好是先取到一个channel,再用[]索引。

比如这个,一个通道的元素数是2x3是6个,会在元素5的后面补两个空位,导致m[8]为6
image.png
经过实验,不会对16补齐,只会对4和8补齐
image.png
image.png

文章转载于:知乎
作者:闪电侠的右手

推荐阅读

推荐阅读
关注数
18810
内容数
1352
嵌入式端AI,包括AI算法在推理框架Tengine,MNN,NCNN,PaddlePaddle及相关芯片上的实现。欢迎加入微信交流群,微信号:aijishu20(备注:嵌入式)
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息