NCNN 源码学习【三】:数据处理

一、Topic:数据处理

这次我们来一段NCNN应用代码中,除了推理外最重要的一部分代码,数据处理:

python 复制代码
    ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR, bgr.cols, bgr.rows, 227, 227);

    const float mean_vals[3] = {104.f, 117.f, 123.f};
    in.substract_mean_normalize(mean_vals, 0);

这一部分代码由两部分组成:

  • from_pixels_resize:将cv::Mat数据转换到ncnn::Mat同时进行resize操作
  • substract_mean_normalize:这个就是减均值除方差

二、from_pixels_resize

先看名字,from_pixels_resize由两部分组成:

  1. from_pixels:从unsigned char* 的数组转换到 ncnn::Mat
  2. resize:unsigned char* 的数据下进行resize

源码中是先进行resize再进行from_pixels。

A、resize

这个代码支持三种图像类型:单通道的GRAY、三通道的RGB和BGR、四通道的RGBA。源码使用的都是bilinear插值,这里我们挑个简单的单通道GRAY的来看看,函数名字很直观,就叫做resize_bilinear_c1,后面的c1就是chennel 1的意思。具体的代码在mat_pixel.cpp的第1414行,这个我就不细说了,大家可以去看这个文章,这个虽然写的是TNN的,但仔细看下来会发现其实跟NCNN的实现是一样的(变量名也一样)。

这个大体流程就是:

  1. 先算x、y方向上插值点的位置索引xofs和yofs
  2. 再算x、y方向上插值点左右的两个插值稀疏iapha和ibeta
  3. 遍历插值,x方向上的插值用xofs和ialpha得到,y方向上的插值用yofs和ibeta得到

这个计算的细节还是很多的,大家感兴趣的可以去仔细研究一下,这里就不细写了,ncnn的代码为例效率,可能写的不是特别美观。

B、from_pixels

这个就很简单了,就是开辟一块ncnn::Mat的内存,然后遍历数组一个一个填进去就好了,同样的这里支持单通道、三通道、四通道,而且一些颜色转换RGB2BGR、RGB2GRAY这些都是实现支持的,我们挑一个典型的RGB2GRAY的实现来看,源码在mat_pixel.cpp的第539行,函数名就是from_rgb2gray。

cpp 复制代码
static Mat from_rgb2gray(const unsigned char* rgb, int w, int h)
{
    const unsigned char Y_shift = 8;//14
    const unsigned char R2Y = 77;
    const unsigned char G2Y = 150;
    const unsigned char B2Y = 29;

    Mat m(w, h, 1);
    if (m.empty())
        return m;

    float* ptr = m;

    int size = w * h;
    int remain = size;

    for (; remain > 0; remain--)
    {
        *ptr = (rgb[0] * R2Y + rgb[1] * G2Y + rgb[2] * B2Y) >> Y_shift;

        rgb += 3;
        ptr++;
    }

    return m;
}

这个代码很直观,前面就是定义了转换时R、G、B对应要乘的系数,这里作者用的是整数乘法,所以系数放大了2^8^,后面算结果那里要右移回去。后面就是一个暴力for循环,全部遍历把数据塞进去ncnn::Mat就完了。但这里我还想放一下GRAY2RGB的代码,看下很值得注意的细节。

python 复制代码
static Mat from_gray2rgb(const unsigned char* gray, int w, int h)
{
    Mat m(w, h, 3);
    if (m.empty())
        return m;

    float* ptr0 = m.channel(0);
    float* ptr1 = m.channel(1);
    float* ptr2 = m.channel(2);

    int size = w * h;

    int remain = size;

    for (; remain>0; remain--)
    {
        *ptr0 = *gray;
        *ptr1 = *gray;
        *ptr2 = *gray;

        gray++;
        ptr0++;
        ptr1++;
        ptr2++;
    }

    return m;
}

从这个可以看出来,获取ncnn::Mat的三个通道的数据,是要用channel索引出来的,这里就是一个需要留意的点,ncnn::Mat的数据存储,channel间的需要对齐,不一定是连续的,也就是不要理所当然的用channel(0)的指针,自己加加加想去访问其他channel的数据,很容易翻车(我就因为这个翻车过),这个我们后面有时间可以好好写一写ncnn的数据排布。

三、substract_mean_normalize

substract_mean_normalize的源码在mat.cpp的第25行,这个代码是支持只mean不norm,只norm不mean,mean和norm都做得,由于这些都大同小异,我就直接贴都做mean和norm的代码了:

cpp 复制代码
void Mat::substract_mean_normalize(const float* mean_vals, const float* norm_vals)
{
    int size = w * h;

    for (int q = 0; q < c; q++)
    {
        float* ptr = data + cstep * q;
        const float mean = mean_vals[q];
        const float norm = norm_vals[q];

        int remain = size;

        for (; remain > 0; remain--)
        {
            *ptr = (*ptr - mean) * norm;
            ptr++;
        }
    }
}

上面比较核心的就一句:

cpp 复制代码
*ptr = (*ptr - mean) * norm;

就是遍历Mat的所有数据,给他减mean乘norm,要注意这里是乘norm,不是一般说的除方差,方差的倒数才是这里的norm。

参考&致谢:

https://zhuanlan.zhihu.com/p/456238585

相关推荐
不做签到员24 天前
YOLOv8-Pose NCNN安卓部署
android·yolo·安卓·yolov8·ncnn·yolov8pose·优化部署
Thanks_ks1 个月前
利用 TensorFlow 与 Docker 构建深度学习模型训练与部署流水线
深度学习·docker·tensorflow·模型部署·容器化技术·模型训练·flask 应用
王乐予2 个月前
NCNN 记录1:Ubuntu+CLion+OpenCV+NCNN+Squeezenet 从源码编译到代码输出全流程记录
linux·c++·opencv·ubuntu·clion·ncnn
阿利同学3 个月前
计算机视觉实战项目4(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A*路径规划+单目测距与测速+行人车辆计数等)
目标检测·计算机视觉·分类·模型部署·计算机视觉实战项目·人工智能实战项目
哦豁灬3 个月前
NCNN 学习(2)-Mat
深度学习·学习·ncnn
哦豁灬3 个月前
NCNN 学习(1)-编译与算子注册
深度学习·学习·ncnn
hi944 个月前
Vitis AI 综合实践(DPU example: dpu_resnet50.ipynb)
人工智能·深度学习·模型部署·pynq
我是陈扣题4 个月前
使用RKNN在Orange Pi 5 (RK3588s) 上部署推理PPO深度学习模型
人工智能·深度学习·rk3588·强化学习·模型部署·ppo·orangep
机器学习社区8 个月前
用 LMDeploy 高效部署 Llama-3-8B,1.8倍vLLM推理效率
深度学习·算法·大模型·llama·模型部署·模型微调·模型推理
极智视界9 个月前
Android算法部署项目 | 在Android平台基于NCNN部署YOLOv5目标检测算法
android·算法·目标检测·yolov5·ncnn·优质项目实战·算法部署