0.序言

几个月前,我在 ONNX 运行时使用 Elixir 编写了 OnnxInterp,并使用 YOLOv7 作为示例程序准备了一个对象检测演示。到目前为止一切顺利,但是...
只是出于好奇,我开始阅读原 YOLOv7 论文 [*1]

... MCUNet 和 NanoDet 专注于生产低功耗单芯片并提高边缘 CPU 的推理速度。

一句话引起了我的注意,“什么是 NanoDet?”这个必须添加到 OnnxInterp 示例程序中,试错移植之旅已经开始Elixirで超軽量の物体検出NanoDet plusを動かしてみる

在这篇文章中,我想剪掉粗俗的故事,介绍使用 Livebook + OnnxInterp 操作“NanoDet plus”的过程。

如果您想跳过混乱并立即尝试 NanoDet plus,请检查以下要求并继续第 4 章Elixirで超軽量の物体検出NanoDet plusを動かしてみる

一、要求

截至 2022 年 8 月 1 日,已确认 NanoDet plus 示例程序可在以下环境中运行。

  • Windows WSL2/Ubuntu 20.04.2
  • Elixir 1.13.4(使用 Erlang/OTP 25 编译)
  • Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
  • Livebook 0.6.3 / Phoenix 1.6.6
  • ONNX 运行时 0.11.1

我还使用以下工具来构建和下载 onnx 模型。

  • CMake 版本 3.18 或更高版本
  • wget

- 不幸的是 Windows 和 OSX -

在 Windows 上:
OnnxInterp 的编译工具链 Visual C++ 2019 和 MSBuild 发出的日语消息/警告(CP932、ShiftJIS)与 Livebook Desktop(Unicode)不兼容,因此我目前无法使其工作。当然,与 iex 和 Phoenix 应用程序的组合应该可以正常工作。

在 OSX 上:
我没有 OSX 环境,所以我不知道它是否有效Elixirで超軽量の物体検出NanoDet plusを動かしてみる
至于我的工作 OnnxInterp,在原 ONNX Runtime 的 GitHub 上有一个 OSX 的预编译库,所以我觉得稍微修改一下 CMakeLists.txt 就可以处理。
……招聘帮手

2.什么是“NanoDet plus”?

由 RangiLyu Tanjin 先生开发和维护的目标检测 DNN 模型,

NanoDet 加:
超快速和高精度的轻量级无锚物体检测模型。

这是正确的。

采用最近流行的 FCOS 风格架构,“ShuffleNetV2”用于目标检测主干,“GhostPAN”用于多尺度特征聚合 FPN,“Generalized Focal Loss”用于目标位置等计算头。

我看不懂中文,所以不知道细节,但是当我在DeepL的帮助下按照RangiLyu先生的文献来信时,似乎他对物体检测有着火热的技术诀窍。包括参考资料,请仔细阅读Elixirで超軽量の物体検出NanoDet plusを動かしてみる

Elixirで超軽量の物体検出NanoDet plusを動かしてみる

好吧,技术兴趣是无穷无尽的,但这不是本文的目标。让我们回到移植。一般来说,移植 DNN 程序的基本设计信息是

#“DNN 模型输入/输出的形式和语义规范”

是。

正式的规范,即输入/输出张量的形状,是耐创通过使用很容易找到当然,你也可以查看我的作品 OnnxInterp 附带的 info/1 函数。

NanoDet plus:
inputs[0]: name: data, type: float32[1,3,416,416]
output[0]: name: output, type: float32[1,3598,112]

另一方面,语义规范,即张量中数据的含义,不幸的是在大多数情况下都没有作为文档发布。我将不得不对原始程序进行逆向工程并找出答案。
这是一部极其粗俗和粗制滥造的作品……我们就跳过它,检查结论Elixirで超軽量の物体検出NanoDet plusを動かしてみる

NanoDet plus:
inputs[0]:
name: data, type: float32[1,3,416,416]
- BGRカラー,NCWHレイアウト,416画素☓416画素の画像で、各カラーチャネルを
  (μ,σ): B(103.53,57.375),G(116.28,57.12),R(123.675,58.395)
 で正規化してfloat32化したもの
outputs[0]:
name: output, type: float32[1,3598,112]
- 416☓416の特徴マップを{8,16,32,64}の間隔で格子に分割し、各格子点(3598箇所)で対象物の推定値と
 対象物を囲む bounding box(以下BBOX)の推定値を一つにまとめたレコード(112要素)が 3598個並んだテーブル

レコードの内訳
[  0~ 79] - Cocoデータセットの80種の対象物それぞれに対する推定値(評価値)
[ 80~ 87] - 格子点からBBOX左端までの距離を、8か所の仮BBOX端における確からしさで表した分布表
[ 88~ 95] - BBOX上端までの距離(同上)
[ 96~103] - BBOX右端までの距離(同上)
[104~111] - BBOX下端までの距離(同上)

关于inputs[0],是图像识别DNN中常见的输入规范。在原程序中,每个通道都被(μ,σ)严重归一化,但是如果将所有通道简单地转换为{-2.2到2.7}的范围就没有问题。

另一方面,关于output[0],前半部分的多尺度网格划分,以及目标对象在每个网格点的80种估计值的返回都是Yolo等中熟悉的内容,但是BBOX的大小是概率分布这是我第一次看到返回表格的设计(注:我想知道是否可以这样称呼它Elixirで超軽量の物体検出NanoDet plusを動かしてみる...我想知道它是否真的是Focal Loss) .让我们更详细地研究这部分。

下图是BBOX右端红圈格点处概率分布表dist的摘录。在 NanoDet plus 中,分布表 dist 中的元素个数固定为 8,每个值表示临时 BBOX(包括格点)的边缘与格点等间隔(pitch)分开的概率为对象的 BBOX. 右边缘表示。在现实中,模型将正值和负值作为概率返回,因此我们将它们乘以 softmax,以便可以将它们视为概率分布。在靠近真实 BBOX(自行车车轮)右边缘的临时 BBOX 边缘的概率接近 1.0,否则几乎为 0.0。

我明白了,到目前为止,如何将概率分布表 dist 解码为距离翼到 BBOX 末尾的距离已经很清楚了。您所要做的就是找到分布表 dist 的平均值(重心)。

格子点からBBOX端までの距離 wing = pitch*\frac{\sum_{i}(i*e^{dist[i]})}{\sum_{i} e^{dist[i]}}

Elixirで超軽量の物体検出NanoDet plusを動かしてみる

现在您拥有所需的所有信息。让我们继续Elixirで超軽量の物体検出NanoDet plusを動かしてみる

3.实施...你知道Livebook,不是吗?

让我们开始将 NanoDet plus 移植到 Elixir。下面大致列出了必要的工作。

  1. 为示例程序创建 Elixir 项目
  2. 将原 NanoDet plus 的训练好的 Pytorch 模型转换为 ONNX 模型
  3. 根据模型输入规范准备预处理
  4. 根据模型的输出规范准备后处理
  5. 拥有显示推理结果供人类查看的例程或应用程序

    工作2到总部E x 端口 _ 在 x 上。 py提供了一个 Python 脚本,因此您可以通过在 Google Colaboratory 周围运行它来轻松清理它。在本文中,我们借用了训练好的模型 NanoDet-Plus-m-416。

    任务3是满足输入规范的图像处理,如果你用我的CImg,2行就够了。

    任务 4 从 DNN 模型的输出中提取对象类型估计分数和对象 BBOX 估计框,并将它们乘以非最大抑制。这种模式在对象检测系统中很常见。嗯,这次讲的是BBOX解码,后面再看代码。

    任务5不是必不可少的部分,但它是最麻烦的。无论你是用 Phoenix 做 UI 还是普通 Plug,即使你最多只显示一张图片,你也得写很多代码。

    顺便一提,活本你知道吗
    是的,在 Livebook 和 Kino 模块的帮助下,NanoDet plus 的推理结果很容易在浏览器上显示出来。这就是我在 Livebook 中为 NanoDet plus 编写示例程序的原因。笔记本的位置参考第四章Elixirで超軽量の物体検出NanoDet plusを動かしてみる


    现在回到正题,让我们快速看一下模块 NanoDetElixirで超軽量の物体検出NanoDet plusを動かしてみる 的代码

    下面的代码是 NanoDet 推理的主要功能。
    从 DNN 模型的预处理到推理,代码看起来和往常一样。

    在后处理中,我们首先将网格点坐标和间距列表添加到 DNN 模型的输出(PostDNN.mesh_grid/3),然后进行过滤,以便只留下对象类型估计为 0.25 或更高的记录。是( PostDNN.sieve/2)。这是早期筛分的第一次尝试。 Elixirで超軽量の物体検出NanoDet plusを動かしてみる987654332@

    之后,筛子中剩余的记录经过第 2 章检查的 BBOX 解码(decode_boxes/2),最后通过 NMS。
    就是这样Elixirで超軽量の物体検出NanoDet plusを動かしてみる

    defmodule NanoDet do
      use OnnxInterp, model: "./NanoDet-Plus-m-416.onnx", label: "./coco.label"
    
      @nanodet_shape {416, 416}
    
      def apply(img) do
        # preprocess
        bin = img
          |> CImg.resize(@nanodet_shape)
          |> CImg.to_binary([{:range, {-2.2, 2.7}}, :nchw, :bgr])
    
        # prediction
        outputs = __MODULE__
          |> OnnxInterp.set_input_tensor(0, bin)
          |> OnnxInterp.invoke()
          |> OnnxInterp.get_output_tensor(0)
          |> Nx.from_binary({:f, 32}) |> Nx.reshape({:auto, 112})
    
        # postprocess
        {scores, boxes} =
          Nx.concatenate([outputs, PostDNN.mesh_grid(@nanodet_shape, [8, 16, 32, 64])], axis: 1)
          |> PostDNN.sieve(fn tensor ->
            Nx.slice_along_axis(tensor, 0, 80, axis: 1)
            |> Nx.reduce_max(axes: [1])
            |> Nx.greater_equal(0.25)
          end)
          |> (&{Nx.slice_along_axis(&1, 0, 80, axis: 1), Nx.slice_along_axis(&1, 80, 35, axis: 1)}).()
    
        {width, height, _, _} = CImg.shape(img)
        boxes = decode_boxes(boxes, {width, height})
    
        OnnxInterp.non_max_suppression_multi_class(__MODULE__,
          Nx.shape(scores), Nx.to_binary(boxes), Nx.to_binary(scores), boxrepr: :corner
        )
      end
      :
    

    BBOX 解码decode_box/2 实现如下。基本上如第 2 章所述。
    以匿名函数的形式实现的函数wing/1,它在心脏处找到“从网格点到BBOX末端的距离”。

    另外,要放大或缩小 BBOX 以匹配原始图像的大小(scale/1),或者限制 BBOX 使其不突出原始图像(keep_within/2),我得到的论点世界图像的宽度和高度。

      def decode_boxes(tensor, world \\ {}) do
        max_index = Nx.axis_size(tensor, 0) - 1
    
        for(i <- 0..max_index, do: decode_box(tensor[i], world))
        |> Nx.stack()
      end
    
      def decode_box(tensor, world \\ {}) do
        grid_x = Nx.to_number(tensor[-3])
        grid_y = Nx.to_number(tensor[-2])
        arm    = Nx.iota({8}) |> Nx.multiply(tensor[-1])  # [0, pitch, 2*pitch, ... 7*pitch]
    
        # private func: decode probability table to wing.
        wing = fn t ->
          max = Nx.reduce_max(t)
    
          {weight, sum} =
            Nx.subtract(t, max)  # prevent exp from becoming too big
            |> Nx.exp()
            |> (&{&1, Nx.sum(&1)}).()
    
          # mean of probability list
          Nx.dot(weight, arm) |> Nx.divide(sum) |> Nx.to_number()
        end
    
        {scale_w, scale_h} = scale(world)
    
        [
          scale_w * (grid_x - wing.(tensor[0..7])),
          scale_h * (grid_y - wing.(tensor[8..15])),
          scale_w * (grid_x + wing.(tensor[16..23])),
          scale_h * (grid_y + wing.(tensor[24..31]))
        ]
        |> keep_within(world)  # keep coners of the box within the original photo.
        |> Nx.stack()
      end
    
    

    4.来玩吧!

    如第 3 章所述,NanoDet plus 的示例程序是用 Livebook 编写的,下面对公众开放。

    GitHub:NanoDet_plus.livemd

    您可以通过 (A) 将笔记本从上述站点下载到适当的目录并使用 Livebook 文件管理器 (?) 打开它,或者 (B) 直接将其导入 LivebookElixirで超軽量の物体検出NanoDet plusを動かしてみる 来立即尝试

    (A) 在 Livebook 文件管理器中打开:
    Elixirで超軽量の物体検出NanoDet plusを動かしてみる

    (B) 直接导入 Livebook:
    Elixirで超軽量の物体検出NanoDet plusを動かしてみる

    notobook 的第一个单元描述了运行 NanoDet plus 示例程序所需的模块的安装以及 ONNX 模型文件和测试图像的下载(存储在 Livebook 的主页中)。空手而来Elixirで超軽量の物体検出NanoDet plusを動かしてみる

    Elixirで超軽量の物体検出NanoDet plusを動かしてみる

    如您所知,在 Livebook 中,当您在最后一个代码单元中按下运行按钮时,到该点的所有代码单元都会自动执行。您可以尝试按下按钮Elixirで超軽量の物体検出NanoDet plusを動かしてみる 移动 NanoDet plus

    Elixirで超軽量の物体検出NanoDet plusを動かしてみる

    ……写别的太容易了Elixirで超軽量の物体検出NanoDet plusを動かしてみる

    5.结语

    结束了。嗯,还是一篇空文章orz
    无论如何,这足以表明您可以意外且轻松地尝试 DNN 推理。
    当我开始写这篇文章的时候,我正在考虑写一个专注于未完善的工作的[B-side],但是……我不需要它。

    参考

    1. RangiLyu:
      GitHub:“NanoDet-Plus”
      “超级简单好用的模型模块加速,加速,精度大大提高!移动端的NanoDet增加版NanoDet-Plus!”
    2. 李向、王文海、吴立军、陈硕、胡晓琳、李军、唐金辉和杨健:
      “Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes for Dense Object Detection”

原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308622242.html

相关文章: