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

Face Recognition

face_recognition.livemd

Face Recognition

Mix.install(
  [
    {:kino, "~> 0.12"},
    {:evision, github: "cocoa-xu/evision", branch: "main"}
  ],
  system_env: [
    {"EVISION_PREFER_PRECOMPILED", "false"}
  ]
)

Smart Cell

recognizer =
  Evision.Zoo.FaceRecognition.SFace.init(:default_model,
    backend: Evision.Constant.cv_DNN_BACKEND_OPENCV(),
    target: Evision.Constant.cv_DNN_TARGET_CPU(),
    distance_type: :cosine_similarity,
    cosine_threshold: 0.5,
    l2_norm_threshold: 1.128
  )

detector =
  Evision.Zoo.FaceDetection.YuNet.init(:default_model,
    backend: Evision.Constant.cv_DNN_BACKEND_OPENCV(),
    target: Evision.Constant.cv_DNN_TARGET_CPU(),
    nms_threshold: 0.3,
    conf_threshold: 0.5,
    top_k: 5
  )

original_input = Kino.Input.image("Original")
comparison_input = Kino.Input.image("Comparison")

form =
  Kino.Control.form([original: original_input, comparison: comparison_input],
    submit: "Run"
  )

frame = Kino.Frame.new()

form
|> Kino.Control.stream()
|> Stream.filter(&(&1.data.original != nil or &1.data.comparison != nil))
|> Kino.listen(fn %{data: %{original: original_image, comparison: comparison_image}} ->
  Kino.Frame.render(frame, Kino.Markdown.new("Running..."))

  original_image =
    Evision.Mat.from_binary(
      original_image.data,
      {:u, 8},
      original_image.height,
      original_image.width,
      3
    )

  comparison_image =
    Evision.Mat.from_binary(
      comparison_image.data,
      {:u, 8},
      comparison_image.height,
      comparison_image.width,
      3
    )

  original_results = Evision.Zoo.FaceDetection.YuNet.infer(detector, original_image)
  comparison_results = Evision.Zoo.FaceDetection.YuNet.infer(detector, comparison_image)

  case {original_results, comparison_results} do
    {%Evision.Mat{}, %Evision.Mat{}} ->
      original_bbox = Evision.Mat.to_nx(original_results, Nx.BinaryBackend)[0][0..-2//1]

      comparison_bbox =
        Evision.Mat.to_nx(comparison_results, Nx.BinaryBackend)[0][0..-2//1]

      original_blob =
        Evision.FaceRecognizerSF.alignCrop(recognizer, original_image, original_bbox)

      original_feature =
        Evision.FaceRecognizerSF.feature(recognizer, original_blob)
        |> Evision.Mat.to_nx()
        |> Evision.Mat.from_nx()

      comparison_blob =
        Evision.FaceRecognizerSF.alignCrop(recognizer, comparison_image, comparison_bbox)

      comparison_feature =
        Evision.FaceRecognizerSF.feature(recognizer, comparison_blob)
        |> Evision.Mat.to_nx()
        |> Evision.Mat.from_nx()

      %{matched: matched, retval: val, measure: measure} =
        Evision.Zoo.FaceRecognition.SFace.match_feature(
          recognizer,
          original_feature,
          comparison_feature
        )

      original_image =
        Evision.cvtColor(original_image, Evision.Constant.cv_COLOR_RGB2BGR())

      comparison_image =
        Evision.cvtColor(comparison_image, Evision.Constant.cv_COLOR_RGB2BGR())

      vis_original =
        Evision.Zoo.FaceDetection.YuNet.visualize(original_image, original_results[0])

      vis_comparison =
        Evision.Zoo.FaceDetection.YuNet.visualize(comparison_image, comparison_results[0])

      vis = [
        Kino.Image.new(Evision.imencode(".png", vis_original), :png),
        Kino.Image.new(Evision.imencode(".png", vis_comparison), :png)
      ]

      Kino.Frame.render(frame, Kino.Layout.grid(vis, columns: 2))

      Kino.Frame.append(
        frame,
        Kino.Markdown.new("Result: #{matched}, #{measure}: #{val}")
      )

    {{:error, _}, %Evision.Mat{}} ->
      Kino.Frame.render(
        frame,
        Kino.Markdown.new("Cannot detect any face in the original image")
      )

    {%Evision.Mat{}, _} ->
      Kino.Frame.render(
        frame,
        Kino.Markdown.new("Cannot detect any face in the comparison image")
      )

    {_, _} ->
      Kino.Frame.render(
        frame,
        Kino.Markdown.new("Cannot detect any face in both original and comparison images")
      )
  end
end)

Kino.Layout.grid([form, frame], boxed: true, gap: 16)