はじめに
Elixir の Livebook を使うと、ほぼノーコードで顔認証が実装できます
書くのも依存モジュールのインストールだけなので、ロジックは不要です
実行環境
- 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" ボタンをクリックし、新しいノートブックを開きます
"Notebook dependencies and setup" と書かれている枠(セル)の中をクリックすると、セルの中が黒くなります
そこに以下のコードを入力します
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" をクリックします
表示されたフォーム内左上の TASK をクリックすると、選択肢が表示されます
選択肢から "Face recognition" をクリックします
以下のようにフォームが変化するので、左上の "Evaluate" をクリックします
少しすると、画像アップロード用のエリアが2つ表示されます
これで顔認証の準備ができました
上で選んだ画像に映る顔と、下で選んだ画像に映る顔を比べ、同じ顔かどうかを判定してくれます
顔認証の実行
上画像には認証したい個人の顔写真をアップロードします
"Upload" ボタンから画像ファイルを選択、もしくは画像ファイルをドラッグ&ドロップでアップロード可能です
下画像には、上画像の個人と同じ人物かどうか判定したい写真を指定します
今回はカメラから写真を撮影するので、 "Open camera" をクリックします
ブラウザから以下のように許可を求めるメッセージが表示されたら「許可する」などをクリックし、カメラの使用を許可します
どのカメラを使うか選択肢が出るので、使いたいカメラ(基本的に「System Default」)をクリックします
カメラ映像が表示されたら、任意のタイミングで "Take photo" をクリックします
カメラ映像が消えて少し待つと、クリックした瞬間の静止画が表示されます
この状態で "Run" ボタンをクリックすると、顔認証が実行されます
上画像と同じ人物だった場合、以下のような結果が表示されます
Result: true, cosine_score: 0.6452961420286556
Result: true
なので、同一人物と判定しています
上画像を別の顔(AI に生成させた適当な人)に変えてみます
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 にすることでノーコード化され、誰でもすぐに顔認証が実装できるようになりました