Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Ultra-lightweight face detection

demo_ultraface/UltraFace.livemd

Ultra-lightweight face detection

File.cd!(__DIR__)
# for windows JP
System.shell("chcp 65001")

Mix.install(
  [
    {:axon_interp, "~> 0.1.0"},
    {:cimg, "~> 0.1.14"},
    {:postdnn, "~> 0.1.5"},
    {:kino, "~> 0.7.0"}
  ],
  config: [
    nx: [default_defn_options: [compiler: EXLA]]
  ]
  # system_env: [{"NNINTERP", "Axon"}]
)

1.Implementation with OnnxInterp in Elixir

defmodule UltraFace do
  @width 320
  @height 240

  alias AxonInterp, as: NNInterp

  use NNInterp,
    model: "./model/version-slim-320_simplified.onnx",
    url:
      "https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB/raw/master/models/onnx/version-slim-320_simplified.onnx",
    inputs: [f32: {1, 3, @height, @width}],
    outputs: [f32: {1, 4420, 2}, f32: {1, 4420, 4}]

  @width 320
  @height 240

  def apply(img) do
    # preprocess
    input0 =
      img
      |> CImg.resize({@width, @height})
      |> CImg.to_binary([{:range, {-1.0, 1.0}}, :nchw])

    # prediction
    outputs =
      session()
      |> NNInterp.set_input_tensor(0, input0, [:binary])
      |> NNInterp.invoke()

    conf = NNInterp.get_output_tensor(outputs, 0) |> Nx.reshape({:auto, 2})
    loc = NNInterp.get_output_tensor(outputs, 1) |> Nx.reshape({:auto, 4})

    # postprocess
    scores = decode_scores(conf)
    boxes = decode_boxes(loc)

    PostDNN.non_max_suppression_multi_class(
      Nx.shape(scores),
      Nx.to_binary(boxes),
      Nx.to_binary(scores),
      iou_threshold: 0.3,
      score_threshold: 0.7,
      boxrepr: :corner
    )

    #    |> PostDNN.adjust2letterbox(CImg.Util.aspect(img))
  end

  @priorbox PostDNN.priorbox(
              {@width, @height},
              [{8, [10, 16, 24]}, {16, [32, 48]}, {32, [64, 96]}, {64, [128, 192, 256]}],
              [:transpose, :normalize]
            )
  @variance Nx.tensor([0.1, 0.1, 0.2, 0.2], type: :f32) |> Nx.reshape({4, 1})

  defp decode_boxes(loc) do
    loc = Nx.transpose(loc)

    # decode box center coordinate on {1.0, 1.0}
    center =
      loc[0..1]
      |> Nx.multiply(@variance[0..1])
      # * prior_size(x,y)
      |> Nx.multiply(@priorbox[2..3])
      # + grid(x,y)
      |> Nx.add(@priorbox[0..1])

    # decode box half size
    half_size =
      loc[2..3]
      |> Nx.multiply(@variance[2..3])
      |> Nx.exp()
      # * prior_size(x,y)
      |> Nx.multiply(@priorbox[2..3])
      |> Nx.divide(2.0)

    # decode boxes
    [Nx.subtract(center, half_size), Nx.add(center, half_size)]
    |> Nx.concatenate()
    |> PostDNN.clamp({0.0, 1.0})
    |> Nx.transpose()
  end

  defp decode_scores(conf) do
    Nx.slice_along_axis(conf, 1, 1, axis: 1)
  end
end
defmodule DemoUltraFace do
  def run(path) do
    img = CImg.load(path)

    with {:ok, res} = UltraFace.apply(img) do
      res["0"]
      |> Enum.take(8)
      |> draw_item(CImg.builder(img), {255, 255, 0})
      |> CImg.display_kino(:jpeg)
    end
  end

  defp draw_item(boxes, canvas, color \\ {255, 255, 255}) do
    Enum.reduce(boxes, canvas, fn [_score, x1, y1, x2, y2, _index], canvas ->
      [x1, y1, x2, y2] = PostDNN.clamp([x1, y1, x2, y2], {0.0, 1.0})

      CImg.fill_rect(canvas, x1, y1, x2, y2, color, 0.3)
    end)
  end
end
# AxonInterp.stop(UltraFace)
UltraFace.start_link([])

2.Let’s try it

DemoUltraFace.run("./10.jpg")

3.TIL ;-)

Appendix