ncnn+PPYOLOv2首次结合!全网最详细代码解读来了(2)

简介: ncnn+PPYOLOv2首次结合!全网最详细代码解读来了

forward()函数即可变形卷积的前向代码,bottom_blobs是可变形卷积的输入,当bottom_blobs里有3个输入时,分别是inputs、offset、mask,表示是DCNv2,当bottom_blobs里有2个输入时,分别是inputs、offset,表示是DCNv1。接下来的代码,我计算了out_h、out_w,即输出特征图的高度和宽度。接下来是对输出张量output开辟空间,获取可变形卷积层的权重、偏置的指针weight_ptr、bias_ptr。最后进入for循环。第1个for循环表示的是卷积窗在h方向滑动,滑了out_h次。第2个for循环表示的是卷积窗在w方向滑动,滑了out_w次;之后计算的h_in、w_in分别表示当前卷积窗位置左上角采样点在pad之后的inputs的y坐标、x坐标(实际上inputs不需要pad,之后你会看到,采样点超出inputs的范围时,采样得到的像素强制取0)。第3个for循环表示的是填写输出特征图的每一个通道,填了num_output次;首先让sum=0,当使用偏置时,sum=bias_ptr[oc],即第oc个偏置。第4、第5、第6个for循环遍历了卷积核的高度、宽度、通道数,计算卷积层权重weight每个卷积采样点每个通道和原图inputs相应位置的像素val(双线性插值得到)和积,再累加到sum中。offset_h、offset_w是当前卷积采样点的y、x偏移,mask_是双线性插值得到的val的重要程度。真正采样位置的y坐标是h_im = 当前卷积窗左上角y坐标h_in + 卷积核内部y偏移i * dilation_h + y偏移offset_h;真正采样位置的x坐标是w_im = 当前卷积窗左上角x坐标w_in + 卷积核内部x偏移j * dilation_w + x偏移offset_w。之后,计算好双线性插值中h_im、w_im上下取整的结果h_low、w_low、h_high、w_high,双线性插值中4个像素的权重w1、w2、w3、w4等。注意,不要在for (int c_im = 0; c_im < in_c; c_im++){}中计算,因为在每一个输入通道中,采样位置h_im、w_im是相等的,所以h_low、w_low、h_high、w_high、w1、w2、w3、w4也是相等的,提前计算好就不用在每个输入通道重复计算,提高计算速度和算法效率。第6个for循环中,遍历每个输入通道,求采样得到像素val,如果采样位置超出inputs的范围,取0;对比cond和v1_cond、v2_cond、v3_cond、v4_cond,会发现cond的边界会比v1_cond、v2_cond、v3_cond、v4_cond的边界大一点,比如当h_im==-1且w_im==-1时, cond是true。这是因为,h_im和w_im会经过上下取整,其中上取整得到的采样点位置是(0, 0),刚好是在inputs范围内,所以cond的边界会比v1_cond、v2_cond、v3_cond、v4_cond的边界大一点。计算好val之后,将val * mask_ * weight_ptr[((oc * in_c + c_im) * kernel_h + i) * kernel_w + j]累加到sum之中。

PPYOLOv2输出解码

PPYOLOv2输出解码比YOLOv3复杂一些,它使用了iou_aware和Grid Sensitive。在YOLOv3中,输出3个特征图,表示3种感受野(大中小)的预测结果,每个特征图的每个格子输出3个bbox,对应3个聚类出来的anchor进行解码。当数据集类别数是80时候,YOLOv3每个特征图通道数是3 * (4+1+80),3表示每个格子输出3个bbox,4表示未解码的xywh,1表示未解码的objness,80表示80个类别未解码的条件概率。PPYOLOv2使用了iou_aware,每个特征图通道数是3 * (1+4+1+80),即每个bbox多出1个ioup属性。共有258个通道,但是前3个通道才是每个bbox的ioup,后255个通道和YOLOv3的排列一样。通过阅读IouAwareLoss的代码,ioup使用F.binary_cross_entropy_with_logits()训练,解码时需要用sigmoid()激活,使用当前预测框和它所学习的gt的iou作为监督信息,所以ioup其实预测的是当前预测框和它所学习的gt的iou。所以,当然是希望ioup越大越好。在mmdet(ppdet)中,用了1条曲线救国的道路对输出解码:

# mmdet/models/heads/yolov3_head.py

...

           

if self.iou_aware:
                    na = len(self.anchors[i])
                    ioup, x = out[:, 0:na, :, :], out[:, na:, :, :]
                    b, c, h, w = x.shape
                    no = c // na
                    x = x.reshape((b, na, no, h * w))
                    ioup = ioup.reshape((b, na, 1, h * w))
                    obj = x[:, :, 4:5, :]
                    ioup = torch.sigmoid(ioup)
                    obj = torch.sigmoid(obj)
                    obj_t = (obj**(1 - self.iou_aware_factor)) * (
                        ioup**self.iou_aware_factor)
                    obj_t = _de_sigmoid(obj_t)
                    loc_t = x[:, :, :4, :]
                    cls_t = x[:, :, 5:, :]
                    y_t = torch.cat([loc_t, obj_t, cls_t], 2)
                    out = y_t.reshape((b, c, h, w))
                box, score = paddle_yolo_box(out, self._anchors[self.anchor_masks[i]], self.downsample[i],
                                             self.num_classes, self.scale_x_y, im_size, self.clip_bbox,
                                             conf_thresh=self.nms_cfg['score_threshold'])

即分别对ioup和obj进行sigmoid激活,再obj_t = (obj ** (1 - self.iou_aware_factor)) * (ioup ** self.iou_aware_factor)作为新的obj,新的obj经过sigmoid的反函数还原成未接码状态,未接码的新obj贴回x中。最后out的通道数是255,只要像原版YOLOv3那样解码out就行了。

这么做的原因是paddle_yolo_box()的作用是对原版YOLOv3的输出进行解码,充分利用paddle_yolo_box()的话就不用自己写解码的代码。所以就走了曲线救国的道路。

从中我们可以得到一些信息,ioup只不过是和obj经过表达式obj_t = (obj ** (1 - self.iou_aware_factor)) * (ioup ** self.iou_aware_factor)得到新的obj,其余只要像YOLOv3一样解码就ok了!

所以在ncnn中,我这样实现PPYOLOv2的解码:

// examples/test2_06_ppyolo_ncnn.cpp
...
class PPYOLODecodeMatrixNMS : public ncnn::Layer
{
public:
    PPYOLODecodeMatrixNMS()
    {
        // miemie2013: if num of input tensors > 1 or num of output tensors > 1, you must set one_blob_only = false
        // And ncnn will use forward(const std::vector<Mat>& bottom_blobs, std::vector<Mat>& top_blobs, const Option& opt) method
        // or forward_inplace(std::vector<Mat>& bottom_top_blobs, const Option& opt) method
        one_blob_only = false;
        support_inplace = false;
    }
    virtual int load_param(const ncnn::ParamDict& pd)
    {
        num_classes = pd.get(0, 80);
        anchors = pd.get(1, ncnn::Mat());
        strides = pd.get(2, ncnn::Mat());
        scale_x_y = pd.get(3, 1.f);
        iou_aware_factor = pd.get(4, 0.5f);
        score_threshold = pd.get(5, 0.1f);
        anchor_per_stride = pd.get(6, 3);
        post_threshold = pd.get(7, 0.1f);
        nms_top_k = pd.get(8, 500);
        keep_top_k = pd.get(9, 100);
        kernel = pd.get(10, 0);
        gaussian_sigma = pd.get(11, 2.f);
        return 0;
    }
    virtual int forward(const std::vector<ncnn::Mat>& bottom_blobs, std::vector<ncnn::Mat>& top_blobs, const ncnn::Option& opt) const
    {
        const ncnn::Mat& bottom_blob = bottom_blobs[0];
        const int tensor_num = bottom_blobs.size() - 1;
        const size_t elemsize = bottom_blob.elemsize;
        const ncnn::Mat& im_scale = bottom_blobs[tensor_num];
        const float scale_x = im_scale[0];
        const float scale_y = im_scale[1];
        int out_num = 0;
        for (size_t b = 0; b < tensor_num; b++)
        {
            const ncnn::Mat& tensor = bottom_blobs[b];
            const int w = tensor.w;
            const int h = tensor.h;
            out_num += anchor_per_stride * h * w;
        }
        ncnn::Mat bboxes;
        bboxes.create(4 * out_num, elemsize, opt.blob_allocator);
        if (bboxes.empty())
            return -100;
        ncnn::Mat scores;
        scores.create(num_classes * out_num, elemsize, opt.blob_allocator);
        if (scores.empty())
            return -100;
        float* bboxes_ptr = bboxes;
        float* scores_ptr = scores;
        // decode
        for (size_t b = 0; b < tensor_num; b++)
        {
            const ncnn::Mat& tensor = bottom_blobs[b];
            const int w = tensor.w;
            const int h = tensor.h;
            const int c = tensor.c;
            const bool use_iou_aware = (c == anchor_per_stride * (num_classes + 6));
            const int channel_stride = use_iou_aware ? (c / anchor_per_stride) - 1 : (c / anchor_per_stride);
            const int cx_pos = use_iou_aware ? anchor_per_stride : 0;
            const int cy_pos = use_iou_aware ? anchor_per_stride + 1 : 1;
            const int w_pos = use_iou_aware ? anchor_per_stride + 2 : 2;
            const int h_pos = use_iou_aware ? anchor_per_stride + 3 : 3;
            const int obj_pos = use_iou_aware ? anchor_per_stride + 4 : 4;
            const int cls_pos = use_iou_aware ? anchor_per_stride + 5 : 5;
            float stride = strides[b];
            #pragma omp parallel for num_threads(opt.num_threads)
            for (int i = 0; i < h; i++)
            {
                for (int j = 0; j < w; j++)
                {
                    for (int k = 0; k < anchor_per_stride; k++)
                    {
                        float obj = tensor.channel(obj_pos + k * channel_stride).row(i)[j];
                        obj = static_cast<float>(1.f / (1.f + expf(-obj)));
                        if (use_iou_aware)
                        {
                            float ioup = tensor.channel(k).row(i)[j];
                            ioup = static_cast<float>(1.f / (1.f + expf(-ioup)));
                            obj = static_cast<float>(pow(obj, 1.f - iou_aware_factor) * pow(ioup, iou_aware_factor));
                        }
                        if (obj > score_threshold)
                        {
                            // Grid Sensitive
                            float cx = static_cast<float>(scale_x_y / (1.f + expf(-tensor.channel(cx_pos + k * channel_stride).row(i)[j])) + j - (scale_x_y - 1.f) * 0.5f);
                            float cy = static_cast<float>(scale_x_y / (1.f + expf(-tensor.channel(cy_pos + k * channel_stride).row(i)[j])) + i - (scale_x_y - 1.f) * 0.5f);
                            cx *= stride;
                            cy *= stride;
                            float dw = static_cast<float>(expf(tensor.channel(w_pos + k * channel_stride).row(i)[j]) * anchors[(b * anchor_per_stride + k) * 2]);
                            float dh = static_cast<float>(expf(tensor.channel(h_pos + k * channel_stride).row(i)[j]) * anchors[(b * anchor_per_stride + k) * 2 + 1]);
                            float x0 = cx - dw * 0.5f;
                            float y0 = cy - dh * 0.5f;
                            float x1 = cx + dw * 0.5f;
                            float y1 = cy + dh * 0.5f;
                            bboxes_ptr[((i * w + j) * anchor_per_stride + k) * 4] = x0 / scale_x;
                            bboxes_ptr[((i * w + j) * anchor_per_stride + k) * 4 + 1] = y0 / scale_y;
                            bboxes_ptr[((i * w + j) * anchor_per_stride + k) * 4 + 2] = x1 / scale_x;
                            bboxes_ptr[((i * w + j) * anchor_per_stride + k) * 4 + 3] = y1 / scale_y;
                            for (int r = 0; r < num_classes; r++)
                            {
                                float score = static_cast<float>(obj / (1.f + expf(-tensor.channel(cls_pos + k * channel_stride + r).row(i)[j])));
                                scores_ptr[((i * w + j) * anchor_per_stride + k) * num_classes + r] = score;
                            }
                        }else
                        {
                            bboxes_ptr[((i * w + j) * anchor_per_stride + k) * 4] = 0.f;
                            bboxes_ptr[((i * w + j) * anchor_per_stride + k) * 4 + 1] = 0.f;
                            bboxes_ptr[((i * w + j) * anchor_per_stride + k) * 4 + 2] = 1.f;
                            bboxes_ptr[((i * w + j) * anchor_per_stride + k) * 4 + 3] = 1.f;
                            for (int r = 0; r < num_classes; r++)
                            {
                                scores_ptr[((i * w + j) * anchor_per_stride + k) * num_classes + r] = -1.f;
                            }
                        }
                    }
                }
            }
            bboxes_ptr += h * w * anchor_per_stride * 4;
            scores_ptr += h * w * anchor_per_stride * num_classes;
        }


...

只要在obj那里动手脚,其余像YOLOv3那样解码就行了,而且,只对obj > score_threshold的bbox解码,其余bbox敷衍处理,提升后处理速度。Grid Sensitive的提出是为了解决训练过程中gt中心点落在格子线上的问题,它允许解码后的x、y超出0~1的范围一点点。

相关文章
|
机器学习/深度学习 PyTorch 算法框架/工具
Pytorch CIFAR10图像分类 Swin Transformer篇(一)
Pytorch CIFAR10图像分类 Swin Transformer篇(一)
vue3导出excel表格方式 ---XLSX文件(最快的导出方法 )
vue3导出excel表格方式 ---XLSX文件(最快的导出方法 )
484 0
|
7月前
|
存储 弹性计算 缓存
阿里云服务器ECS实例选型与性能监控指南:从场景匹配到优化参考
随着云服务器的普及应用,越来越多的企业和个人用户选择将业务迁移到云端,以享受其带来的灵活性、可扩展性和成本效益。阿里云服务器(Elastic Compute Service,简称ECS)以其丰富的实例规格、卓越的性能和稳定的运行环境,赢得了广大用户的信赖。然而,对于很多初次接触云服务器产品的新手用户来说,面对阿里云多达几十种的云服务器实例规格,往往感到无从下手,不知道如何选择最适合自己业务需求的实例规格。本文旨在通过详细解析阿里云ECS实例规格的选择策略,并介绍如何有效监控云服务器性能,确保业务的高效运行。
464 63
|
人工智能 Cloud Native 搜索推荐
【2025云栖大会】阿里云AI搜索年度发布:开启Agent时代,重构搜索新范式
2025云栖大会阿里云AI搜索专场上,发布了年度AI搜索技术与产品升级成果,推出Agentic Search架构创新与云原生引擎技术突破,实现从“信息匹配”到“智能问题解决”的跨越,支持多模态检索、百亿向量处理,助力企业降本增效,推动搜索迈向主动服务新时代。
397 0
|
2月前
|
运维 小程序 数据可视化
小程序开发平台有哪些?SaaS小程序制作平台哪个好
小程序开发模式详解:自主开发、SaaS小程序制作平台与外包全对比 选择合适的小程序开发模式,是项目成功的基石。这三种模式在成本、周期、控制力和灵活性上各有千秋,适用于不同阶段和不同类型的企业。下面我们将逐一深入剖析。
284 7
|
3月前
|
存储 C语言 C++
& 符号的含义和用法
在C语言中,`&`符号常用于取地址,如`scanf`中传递变量地址以存储输入数据。示例中`&a`和`&x`获取变量内存地址,确保数据正确读入。省略会导致未定义行为。此外,`&`还用于指针声明、按位与运算等,是C/C++中的关键操作符之一。
1147 0
|
Web App开发
如何设置谷歌浏览器在新窗口中打开链接?如何设置谷歌浏览器在新标签页中打开链接?
一、快捷键方式:  1、左键单击 ==》 在当前窗口中打开目标网页。  2、Shift + 左键单击 ==》 在新窗口中打开目标网页。  3、Ctrl + 左键单击 ==》 在新标签页中打开目标网页。  4、鼠标中键点击书签即打开新的标签页,在新的标签页中显示指定的网页。
59837 0
|
编解码 算法 PyTorch
超好用!图像去雾算法C2PNet介绍与使用指南
超好用!图像去雾算法C2PNet介绍与使用指南
|
关系型数据库 MySQL 数据库
实时计算 Flink版操作报错合集之在处理PostgreSQL数据库遇到报错。该如何解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
人工智能 程序员 API
通义万相AIGC技术的测试体验
通义万相AIGC技术的测试体验
384 3