20
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめに

Elixir の Livebook を使うと、ほぼノーコードで顔認証が実装できます

スクリーンショット 2023-07-10 11.43.40.png

書くのも依存モジュールのインストールだけなので、ロジックは不要です

実行環境

  • Elixir: 1.14.2
  • OTP: 24
  • Livebook: 0.9.3

こちらの @nako_sleep_9h さんの記事に Livebook のはじめ方を書いているので、是非使ってみてください

evision をコードからビルドする関係で、実行環境によっては動かない可能性があります

私の使っているコンテナであれば確実に動きます

https://github.com/RyoWakabayashi/elixir-learning

また、 localhost で実行している場合(https 通信になっていない場合)、ブラウザによってカメラが起動しないことがあります

その場合は ngrok 等を使い、 https 通信で Livebook にアクセスしてください

セットアップ

Livebook を開いたら、右上 "+ New notebook" ボタンをクリックし、新しいノートブックを開きます

スクリーンショット 2023-07-06 13.29.59.png

"Notebook dependencies and setup" と書かれている枠(セル)の中をクリックすると、セルの中が黒くなります

setup_cell.gif

そこに以下のコードを入力します

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

書くコードはこれだけです

セルの上に出ている "Setup" ボタンをクリックし、セットアップを実行します

モジュールのインストールが開始され、しばらくすると ":ok" と表示されます

2023/7/10 現在

evision にいくつかの PR を出し、GitHub 上の main ブランチでは解消されていますが、未リリース状態です

https://github.com/cocoa-xu/evision/pull/204
https://github.com/cocoa-xu/evision/pull/205
https://github.com/cocoa-xu/evision/pull/207
https://github.com/cocoa-xu/evision/pull/208

そのため、この記事ではコードからビルドしています(なので、インストールにすごく時間がかかります)
リリースされた場合は Hex からのインストールに変更します

スマートセルの追加

"Section" の文字と、その下のセルの間、左右中央あたりにマウスカーソルを持っていくと、 "+ Code" "+ Block" "+ Smart" の3つのボタンが表示されます

"+ Smart" のボタンをクリックします

出てくるドロップダウンの中から "Evision: OpenCV Model Zoo" をクリックします

smart_cell.gif

表示されたフォーム内左上の TASK をクリックすると、選択肢が表示されます

スクリーンショット 2023-07-05 21.06.43.png

選択肢から "Face recognition" をクリックします

以下のようにフォームが変化するので、左上の "Evaluate" をクリックします

スクリーンショット 2023-07-06 11.40.46.png

少しすると、画像アップロード用のエリアが2つ表示されます

スクリーンショット 2023-07-05 21.07.09.png

これで顔認証の準備ができました

上で選んだ画像に映る顔と、下で選んだ画像に映る顔を比べ、同じ顔かどうかを判定してくれます

顔認証の実行

上画像には認証したい個人の顔写真をアップロードします

"Upload" ボタンから画像ファイルを選択、もしくは画像ファイルをドラッグ&ドロップでアップロード可能です

スクリーンショット 2023-07-06 11.45.40.png

下画像には、上画像の個人と同じ人物かどうか判定したい写真を指定します

今回はカメラから写真を撮影するので、 "Open camera" をクリックします

ブラウザから以下のように許可を求めるメッセージが表示されたら「許可する」などをクリックし、カメラの使用を許可します

スクリーンショット 2023-07-05 21.09.55.png

どのカメラを使うか選択肢が出るので、使いたいカメラ(基本的に「System Default」)をクリックします

スクリーンショット 2023-07-05 21.10.05.png

カメラ映像が表示されたら、任意のタイミングで "Take photo" をクリックします

カメラ映像が消えて少し待つと、クリックした瞬間の静止画が表示されます

この状態で "Run" ボタンをクリックすると、顔認証が実行されます

recognition.gif

上画像と同じ人物だった場合、以下のような結果が表示されます

Result: true, cosine_score: 0.6452961420286556

Result: true なので、同一人物と判定しています

上画像を別の顔(AI に生成させた適当な人)に変えてみます

スクリーンショット 2023-07-10 11.53.48.png

Result: false で別人として判定できました

タネ明かし

スマートセルをコードに変換すると、以下のようになります

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.9,
    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)

Evision が YuNet で顔を検出し、 SFace で顔の特徴量を取得しています

2つの顔のコサイン類似度を求めることで、同一人物かどうかの判定を行なっています

まとめ

使っている技術自体は OpenCV なのでそんなに新しくはないのですが、 SmartCell にすることでノーコード化され、誰でもすぐに顔認証が実装できるようになりました

20
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?