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

Pose Estimation: MoveNet

demo_movenet/MoveNet.livemd

Pose Estimation: MoveNet

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

Mix.install([
  {:onnx_interp, path: ".."},
  {:nx, "~> 0.4.0"},
  {:cimg, "~> 0.1.15"},
  {:exprintf, "~> 0.2.1"},
  {:kino, "~> 0.7.0"}
])

0.Original work

Thanks a lot!!!


Implementation with OnnxInterp in Elixir

1.Defining the inference module: MoveNet

  • Model
    movenet_singlepose.onnx

  • Pre-processing
    Resize the input image to the size {192,192}, 32bit integer to {0,255} and keep aspect ratio.

  • Post-processing
    output[0] - 17 keypoints (in the order of: [nose, left eye, right eye, left ear, right ear, left shoulder, right shoulder, left elbow, right elbow, left wrist, right wrist, left hip, right hip, left knee, right knee, left ankle, right ankle]). Make pairs of keypoints as “bone”

defmodule Movenet do
  @width 192
  @height 192

  alias OnnxInterp, as: NNInterp

  use NNInterp,
    model: "./model/movenet_singlepose.onnx",
    url: "https://github.com/shoz-f/onnx_interp/releases/download/models/movenet_singlepose.onnx",
    inputs: [i32: {1, @height, @width, 3}],
    outputs: [f32: {1, 1, 17, 3}]

  @prepro CImg.builder()
          |> CImg.resize({@width, @height}, :ul, 0)
          |> CImg.to_binary(dtype: " NNInterp.set_input_tensor(0, input0)
      |> NNInterp.invoke()
      |> NNInterp.get_output_tensor(0)
      |> Nx.from_binary(:f32)
      |> Nx.reshape({17, 3})

    # postprocess
    {inv_w, inv_h} = inv_aspect(img)
    joints = Nx.multiply(output0, Nx.tensor([inv_h, inv_w, 1.0]))

    {:ok, to_bones(joints)}
  end

  @bones [
    {0, 1, :fuchsia},
    {0, 2, :aqua},
    {1, 3, :fuchsia},
    {2, 4, :aqua},
    {0, 5, :fuchsia},
    {0, 6, :aqua},
    {5, 7, :fuchsia},
    {7, 9, :fuchsia},
    {6, 8, :aqua},
    {8, 10, :aqua},
    {5, 6, :yellow},
    {5, 11, :fuchsia},
    {6, 12, :aqua},
    {11, 12, :yellow},
    {11, 13, :fuchsia},
    {13, 15, :fuchsia},
    {12, 14, :aqua},
    {14, 16, :aqua}
  ]

  def to_bones(t, threshold \\ 0.11) do
    Enum.flat_map(@bones, fn {p1, p2, color} ->
      [y1, x1, score1] = Nx.to_flat_list(t[p1])
      [y2, x2, score2] = Nx.to_flat_list(t[p2])

      if score1 > threshold && score2 > threshold do
        [{x1, y1, x2, y2, color}]
      else
        []
      end
    end)
  end

  defp inv_aspect(img) do
    {w, h, _, _} = CImg.shape(img)
    if w > h, do: {1.0, w / h}, else: {h / w, 1.0}
  end
end

Launch Movenet.

Movenet.start_link([])

Display the properties of the Movenet model.

OnnxInterp.info(Movenet)

2.Defining execution module DemoMovenet

defmodule LiveMovenet do
  def img_list(src, range) do
    Enum.map(range, fn i ->
      name = ExPrintf.sprintf("%03d.jpg", [i])
      path = Path.join(src, name)
      CImg.load(path)
    end)
  end

  def animate(img_list) do
    Kino.animate(img_list, fn img ->
      draw_movenet(img)
      |> CImg.display_kino(:jpeg)
    end)
  end

  def run(path) do
    CImg.load(path)
    |> draw_movenet()
    |> CImg.display_kino(:jpeg)
  end

  defp draw_movenet(img) do
    with {:ok, res} <- Movenet.apply(img) do
      Enum.reduce(res, CImg.builder(img), fn {x1, y1, x2, y2, color}, canvas ->
        CImg.draw_line(canvas, x1, y1, x2, y2, color, thick: 5)
      end)
    end
  end
end

3.Let’s try it

img_list = LiveMovenet.img_list("girl-dancing", 1..200)
LiveMovenet.animate(img_list)

4.TIL ;-)