C++ YOLOv3推理 第三讲: 模型推理

在上一章节中,我们讨论了模型参数的加载,本文主要讨论,如何基于加载的模型实现模型的推理。

如果对于yolov3的模型结构感兴趣,那么你可能需要至少看三篇论文 【4】【5】【6】

模型架构可视化

首先,梳理一下YOLOV3Tiny的模型结构,从模型结构可以看出,yolov3TIny的模型结构非常简洁。

Backbone包含卷积层,pooling层,残差链接,以及上采样层。没有其他的多余操作。

模型推理代码解读

模型推理主要调用的函数是detect(),所以,虽然这个函数很长,我们也把它展示在最后面。

    const int64_t t_start_ms = ggml_time_ms();
    detect(img, model, params.thresh, labels, alphabet);
    const int64_t t_detect_ms = ggml_time_ms() - t_start_ms;
    if (!save_image(img, params.fname_out.c_str(), 80)) {
        fprintf(stderr, "%s: failed to save image to '%s'\n", __func__, params.fname_out.c_str());
        return 1;
    }
    printf("Detected objects saved in '%s' (time: %f sec.)\n", params.fname_out.c_str(), t_detect_ms / 1000.0f);
    ggml_free(model.ctx);
    return 0;

在函数detect中,可以看到,通过ggml_init() 函数的调用,首先进行了heap上buffer的分配。然后,通过ggml_init的调用,我们进行内存的分配,并且,我们可以根据编译选项,选择是否基于cublas或者clblast或者metal进行加速。所以,目前看来,ggml_init可以实现内存的获取,以及cublas等加速器的初始化。

接下来,调用的是ggml_new_graph()。 这里从名字就可以看出,涉及到图(graph)的生成,而ggml_cgraph的声明如下,网络中重要的节点会放置在其中。

在get_yolo_detections中,可以看到获得candidates 的objectness等参数的过程。

    // computation graph
    struct ggml_cgraph {
        int size;
        int n_nodes;
        int n_leafs;

        struct ggml_tensor ** nodes;
        struct ggml_tensor ** grads;
        struct ggml_tensor ** leafs;

        struct ggml_hash_set visited_hash_table;

        enum ggml_cgraph_eval_order order;

        // performance
        int     perf_runs;
        int64_t perf_cycles;
        int64_t perf_time_us;
    };

基于letterbox,我们可以对于图像进行大小的缩放,来满足模型对于输入的要求,然后成为“input”。如果仔细阅读下列代码和上图,可以发现很容易找到层之间的对应关系。

在最后,基于先验的锚点和mask信息,我们进一步处理。对应的python代码[3]可表达为:


yolo_tiny_anchors = np.array([(10, 14), (23, 27), (37, 58),
                              (81, 82), (135, 169),  (344, 319)],
                             np.float32) / 416
yolo_tiny_anchor_masks = np.array([[3, 4, 5], [0, 1, 2]])

Mask和Anchor的作用,可以参考[4],它是基于作者提供的先验box大小。最后,经过NMS

和可视化的操作,我们完成了模型推理的全流程。

void detect(yolo_image & img, const yolo_model & model, float thresh, const std::vector<std::string> & labels, const std::vector<yolo_image> & alphabet)
{
    static size_t buf_size = 20000000 * sizeof(float) * 4;
    static void * buf = malloc(buf_size);

    struct ggml_init_params params = {
        /*.mem_size   =*/ buf_size,
        /*.mem_buffer =*/ buf,
        /*.no_alloc   =*/ false,
    };

    struct ggml_context * ctx0 = ggml_init(params);
    struct ggml_cgraph * gf = ggml_new_graph(ctx0);
    std::vector<detection> detections;

    yolo_image sized = letterbox_image(img, model.width, model.height);
    struct ggml_tensor * input = ggml_new_tensor_4d(ctx0, GGML_TYPE_F32, model.width, model.height, 3, 1);
    std::memcpy(input->data, sized.data.data(), ggml_nbytes(input));
    ggml_set_name(input, "input");

    struct ggml_tensor * result = apply_conv2d(ctx0, input, model.conv2d_layers[0]);
    print_shape(0, result);
    result = ggml_pool_2d(ctx0, result, GGML_OP_POOL_MAX, 2, 2, 2, 2, 0, 0);
    print_shape(1, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[1]);
    print_shape(2, result);
    result = ggml_pool_2d(ctx0, result, GGML_OP_POOL_MAX, 2, 2, 2, 2, 0, 0);
    print_shape(3, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[2]);
    print_shape(4, result);
    result = ggml_pool_2d(ctx0, result, GGML_OP_POOL_MAX, 2, 2, 2, 2, 0, 0);
    print_shape(5, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[3]);
    print_shape(6, result);
    result = ggml_pool_2d(ctx0, result, GGML_OP_POOL_MAX, 2, 2, 2, 2, 0, 0);
    print_shape(7, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[4]);
    struct ggml_tensor * layer_8 = result;
    print_shape(8, result);
    result = ggml_pool_2d(ctx0, result, GGML_OP_POOL_MAX, 2, 2, 2, 2, 0, 0);
    print_shape(9, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[5]);
    print_shape(10, result);
    result = ggml_pool_2d(ctx0, result, GGML_OP_POOL_MAX, 2, 2, 1, 1, 0.5, 0.5);
    print_shape(11, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[6]);
    print_shape(12, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[7]);
    struct ggml_tensor * layer_13 = result;
    print_shape(13, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[8]);
    print_shape(14, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[9]);
    struct ggml_tensor * layer_15 = result;
    print_shape(15, result);
    result = apply_conv2d(ctx0, layer_13, model.conv2d_layers[10]);
    print_shape(18, result);
    result = ggml_upscale(ctx0, result, 2);
    print_shape(19, result);
    result = ggml_concat(ctx0, result, layer_8);
    print_shape(20, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[11]);
    print_shape(21, result);
    result = apply_conv2d(ctx0, result, model.conv2d_layers[12]);
    struct ggml_tensor * layer_22 = result;
    print_shape(22, result);

//将layer15, layer22存入图中

    ggml_build_forward_expand(gf, layer_15);
    ggml_build_forward_expand(gf, layer_22);
    ggml_graph_compute_with_ctx(ctx0, gf, 1);

    yolo_layer yolo16{ 80, {3, 4, 5}, {10, 14, 23, 27, 37,58, 81, 82, 135, 169, 344, 319}, layer_15};
    apply_yolo(yolo16);
    get_yolo_detections(yolo16, detections, img.w, img.h, model.width, model.height, thresh);

    yolo_layer yolo23{ 80, {0, 1, 2}, {10, 14, 23, 27, 37,58, 81, 82, 135, 169, 344, 319}, layer_22};
    apply_yolo(yolo23);
    get_yolo_detections(yolo23, detections, img.w, img.h, model.width, model.height, thresh);

    do_nms_sort(detections, yolo23.classes, .45);
    draw_detections(img, detections, thresh, labels, alphabet);
    ggml_free(ctx0);
}

总结

经过上述的介绍,可以看到,基于c++的yolov3tiny代码和yolov3tiny的网络结构,可以很容易的寻找到一一对应的关系,代码中的超参也是yolov3原始实现中比不可少的一部分。

除此之外,因为代码基于cpp,项目中包含一些代码,用于网络的构建,管理以及销毁,并且没有详细的文档进行解释,因此只能靠自己啃。

总的来说,基于yolov3tiny的推理代码,不难写出其他的基于cnn的推理代码。但是,涉及到其他更复杂的网络结构(比如transformer)时,可能仅仅参考yolov3的推理实现是不够的。

参考链接

[1] YOLOv3及YOLOv3-Tiny | Yuan

​​​​​​[2] machine learning - One stage vs two stage object detection - Stack Overflow

[3] https://github.com/zzh8829/yolov3-tf2/tree/master

[4] https://arxiv.org/pdf/1612.08242.pdf

[5] https://arxiv.org/abs/1506.02640

[6] https://arxiv.org/abs/1804.02767

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在C++中进行Yolov5推理,您需要下载Yolov5的预训练模型和相应的C++推理库。以下是一个简单的步骤: 1. 下载Yolov5预训练模型 您可以从Yolov5官方GitHub存储库(https://github.com/ultralytics/yolov5)下载预训练模型。选择您需要的模型并将其下载到本地计算机。 2. 安装C++推理库 有许多C++推理库可供选择,例如OpenCV、TensorRT、ONNX等。在这里,我们将使用OpenCV深度学习模块,因为它易于安装和使用。 要使用OpenCV中的深度学习模块,您需要安装OpenCV并启用DNN模块。可以在以下链接中找到有关如何安装OpenCV的信息:https://opencv.org/releases/。 3. 加载模型 使用OpenCV的dnn模块,您可以轻松加载Yolov5模型。以下是示例代码: ```cpp cv::dnn::Net net = cv::dnn::readNet("path/to/your/yolov5/model", "path/to/your/yolov5/config"); ``` 4. 进行推理 一旦模型被加载,您可以使用它来进行推理。以下是一个示例代码,演示如何使用OpenCV进行Yolov5推理: ```cpp cv::Mat image = cv::imread("path/to/your/image"); cv::Mat blob = cv::dnn::blobFromImage(image, 1/255.0, cv::Size(640, 640), cv::Scalar(0, 0, 0), true, false); net.setInput(blob); std::vector<cv::Mat> outs; net.forward(outs, net.getUnconnectedOutLayersNames()); for (cv::Mat& out : outs) { // 处理输出结果 } ``` 在上面的代码中,我们首先读取要进行推理的图像,然后使用OpenCV的blobFromImage函数将其转换为网络输入。接下来,我们将输入设置为网络的输入,然后调用前向方法来获得输出。最后,我们可以通过处理输出来获得检测结果。 请注意,在处理输出时,您需要将输出转换为可读的格式。具体来说,您需要将它们转换为边界框,并且通过应用非最大抑制算法来过滤掉重叠的检测结果。 这是一个简单的步骤来使用C++进行Yolov5推理,但要进行更深入的推理,您需要深入了解底层库和算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值