14
0

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.

ElixirAdvent Calendar 2022

Day 20

Livebook から Amazon Rekognition の画像 AI を使って物体検出や顔検出、感情分析を行う

Last updated at Posted at 2022-12-06

はじめに

Livebook から AWS のサービスを操作するシリーズです

今回は Amazon Rekognition を操作します

Amazon Rekognition は画像 AI サービスで、物体検出や顔識別などができます

今回は物体検出と顔検出、環状などの顔情報の分析を行います

実装したノートブックはこちら

事前作業

AWS のアカンウトと、 Rekognition の権限を持った IAM ユーザーと、その認証情報(ACCESS_KEY_ID と SECRET_ACCESS_KEY)が必要です

実行環境

Livebook 0.7.2 の Docker イメージを元にしたコンテナで動かしました

コンテナ定義はこちらを参照

セットアップ

ex_aws_rekognition を中心に、必要なモジュールをインストールします

また、データ分析のために Explorer 、画像処理のために Evision などもインストールします

Mix.install([
  {:ex_aws, "~> 2.0"},
  {:ex_aws_rekognition, "~> 0.6"},
  {:poison, "~> 5.0"},
  {:hackney, "~> 1.18"},
  {:sweet_xml, "~> 0.7"},
  {:explorer, "~> 0.4"},
  {:evision, "~> 0.1"},
  {:download, "~> 0.0.4"},
  {:kino, "~> 0.8"}
])

エイリアス等の準備をします

alias ExAws.Rekognition
alias Explorer.DataFrame
alias Explorer.Series
require Explorer.DataFrame

認証

入力エリアを用意し、そこに IAM ユーザーの認証情報を入力します

ACCESS_KEY_ID と SECRET_ACCESS_KEY は秘密情報なので、値が見えないように Kino.Input.password を使います

access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")

リージョンもここで入力しておきましょう

region_input = Kino.Input.text("REGION")

スクリーンショット 2022-12-03 0.01.54.png

各認証情報を ExAws に渡すためにまとめておきます

秘密情報が実行結果に現れないよう、セルの最後には "dummy" を入れておきましょう

auth_config = [
  access_key_id: Kino.Input.read(access_key_id_input),
  secret_access_key: Kino.Input.read(secret_access_key_input),
  region: Kino.Input.read(region_input)
]

"dummy"

物体検出

画像内にある物体を検出し、それが何であるか、画像内のどこにあるのかを取得します

いつもお世話になっている Lenna さんの画像を使います

File.rm("Lenna_%28test_image%29.png")

lenna =
  Download.from("https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png")
  |> elem(1)

いつものです

lenna_mat = Evision.imread(lenna)

スクリーンショット 2022-12-06 17.36.24.png

さあ、 Rekognition.detect_labels で Rekognition に物体検出させましょう

rek_res =
  Evision.imencode(".png", lenna_mat)
  |> Rekognition.detect_labels()
  |> ExAws.request!(auth_config)

スクリーンショット 2022-12-06 17.37.31.png

色々返ってきてはいますが、これだと見にくいので整形しましょう

detections_df =
  rek_res["Labels"]
  # 位置情報がないものは除外する
  |> Enum.filter(&(Enum.count(&1["Instances"]) > 0))
  |> Enum.flat_map(fn detection ->
    detection["Instances"]
    |> Enum.map(fn instance ->
      [
        label: detection["Name"],
        left: instance["BoundingBox"]["Left"],
        top: instance["BoundingBox"]["Top"],
        width: instance["BoundingBox"]["Width"],
        height: instance["BoundingBox"]["Height"],
        score: instance["Confidence"]
      ]
    end)
  end)
  |> DataFrame.new()

detections_df
|> Kino.DataTable.new()

スクリーンショット 2022-12-06 17.38.33.png

一覧にしてみると、どうも無駄に同じ位置で PersonWomanAdultFemale が検出されています

確かに Lenna さんは PersonWomanAdultFemale ですが、重なると見にくいので一つに絞ります

detections_df =
  detections_df
  |> DataFrame.distinct(["left", "top", "width", "height"], keep_all: true)

detections_df
|> Kino.DataTable.new()

スクリーンショット 2022-12-06 17.40.59.png

これで人物二人になりました

ではこの位置情報を画像にプロットしましょう

Rekognition から返ってくる Let や Top の値は画像全体の幅、高さを 1.0 としたときの比なので、元の画像サイズをかける必要があります

{img_height, img_width, _} = Evision.Mat.shape(lenna_mat)

検出した位置情報毎に四角形とラベル文字を出力します

detections_df
|> DataFrame.to_rows()
|> Enum.reduce(lenna_mat, fn detection, drawed ->
  label = detection["label"]
  left = img_width * detection["left"] |> trunc()
  top = img_height * detection["top"] |> trunc()
  right = left + img_width * detection["width"] |> trunc()
  bottom = top + img_height * detection["height"] |> trunc()

  drawed
  |> Evision.rectangle(
    {left, top},
    {right, bottom},
    {255, 0, 0},
    thickness: 4
  )
  |> Evision.putText(
    label,
    {left + 6, top + 26},
    Evision.Constant.cv_FONT_HERSHEY_SIMPLEX(),
    0.8,
    {0, 0, 255},
    thickness: 2
  )
end)

スクリーンショット 2022-12-06 17.43.31.png

さすが Rekognition です

手前の Lenna さんはもちろん、後ろの鏡に映った Lenna さんも検出できています

せっかくなので、一連の処理を関数化しましょう

show_objects = fn input_mat ->
  Evision.imencode(".jpg", input_mat)
  |> Rekognition.detect_labels()
  |> ExAws.request!(auth_config)
  |> then(&(&1["Labels"]))
  |> Enum.filter(&(Enum.count(&1["Instances"]) > 0))
  |> Enum.flat_map(fn detection ->
    detection["Instances"]
    |> Enum.map(fn instance ->
      [
        label: detection["Name"],
        left: instance["BoundingBox"]["Left"],
        top: instance["BoundingBox"]["Top"],
        width: instance["BoundingBox"]["Width"],
        height: instance["BoundingBox"]["Height"],
        score: instance["Confidence"]
      ]
    end)
  end)
  |> DataFrame.new()
  |> DataFrame.distinct(["left", "top", "width", "height"], keep_all: true)
  |> DataFrame.to_rows()
  |> Enum.reduce(input_mat, fn detection, drawed ->
    {img_height, img_width, _} = Evision.Mat.shape(input_mat)

    label = detection["label"]
    left = img_width * detection["left"] |> trunc()
    top = img_height * detection["top"] |> trunc()
    right = left + img_width * detection["width"] |> trunc()
    bottom = top + img_height * detection["height"] |> trunc()

    drawed
    |> Evision.rectangle(
      {left, top},
      {right, bottom},
      {255, 0, 0},
      thickness: 4
    )
    |> Evision.putText(
      label,
      {left + 6, top + 26},
      Evision.Constant.cv_FONT_HERSHEY_SIMPLEX(),
      0.8,
      {0, 0, 255},
      thickness: 2
    )
  end)
end

この関数を使って、今度は例の犬画像から物体を検出します

File.rm("dog.jpg")

dog =
  Download.from("https://raw.githubusercontent.com/pjreddie/darknet/master/data/dog.jpg")
  |> elem(1)
dog_mat = Evision.imread(dog)

スクリーンショット 2022-12-06 17.45.35.png

show_objects.(dog_mat)

スクリーンショット 2022-12-06 17.46.14.png

木の枝に隠れているバイクまで見逃していません

顔検出

続いて顔検出です

Rekognition.detect_faces で顔検出ができます

attributes: ["ALL"] を指定すると、顔の位置情報だけでなく、感情などの情報も併せて取得できます

rek_res =
  Evision.imencode(".png", lenna_mat)
  |> Rekognition.detect_faces(attributes: ["ALL"])
  |> ExAws.request!(auth_config)

スクリーンショット 2022-12-06 17.48.12.png

Lenna さんの顔情報をとっておきます

lenna_face =
  rek_res["FaceDetails"]
  |> Enum.at(0)

スクリーンショット 2022-12-06 17.49.13.png

この顔情報からさまざまなことが分かります

顔の位置

物体検出と同じように顔の位置をプロットします

{img_height, img_width, _} = Evision.Mat.shape(lenna_mat)

box = lenna_face["BoundingBox"]

left = img_width * box["Left"] |> trunc()
top = img_height * box["Top"] |> trunc()
right = left + img_width * box["Width"] |> trunc()
bottom = top + img_height * box["Height"] |> trunc()

lenna_mat
|> Evision.rectangle(
  {left, top},
  {right, bottom},
  {255, 0, 0},
  thickness: 4
)

スクリーンショット 2022-12-06 17.50.28.png

顔のパーツ

目や鼻の位置も取得できます

{img_height, img_width, _} = Evision.Mat.shape(lenna_mat)

lenna_face["Landmarks"]
|> Enum.reduce(lenna_mat, fn detection, drawed ->

  x = img_width * detection["X"] |> trunc()
  y = img_height * detection["Y"] |> trunc()

  drawed
  |> Evision.circle(
    {x, y},
    4,
    {255, 0, 0},
    thickness: -1
  )
end)

スクリーンショット 2022-12-06 17.51.12.png

目尻、眉尻、顎先、鼻先など、かなり細かく取得できますね

顔の向き

顔の向きまで分かります

lenna_face["Pose"]
|> then(&%{
  "どれくらい首を傾げているか" => Float.round(&1["Roll"]),
  "どれくらい見上げたり見下ろしたりしているか" => Float.round(&1["Pitch"]),
  "どれくらい左右に向いているか" => Float.round(&1["Yaw"]),
})

スクリーンショット 2022-12-06 17.52.34.png

値の単位は「度」で -180 〜 180 の範囲です

これを使えば「正面を見ているか」「上を見上げているか」といったことが判定できます

写りの良さ

顔がちゃんと写っているか判定できます

明るさを見ると、暗くて見えづらい顔を判別できます

lenna_face["Quality"]["Brightness"]
|> Float.round()
|> then(&"明るさ #{&1}%")

スクリーンショット 2022-12-06 17.54.20.png

鮮明さで顔にフォーカスが合っているかを判定できます

lenna_face["Quality"]["Sharpness"]
|> Float.round()
|> then(&"鮮明さ #{&1}%")

スクリーンショット 2022-12-06 17.55.16.png

感情

8種類の感情について判定できます

lenna_face["Emotions"]
|> Enum.map(fn emotion ->
  %{
    "confidence" => Float.round(emotion["Confidence"]),
    "emotion" => emotion["Type"]
  }
end)
|> DataFrame.new()
|> DataFrame.select(["emotion", "confidence"])
|> Kino.DataTable.new()

スクリーンショット 2022-12-06 17.56.36.png

Lenna さんは CARM 86% なので、ほぼ感情がない(表れていない)状態です

年齢

年齢までバレます

lenna_face["AgeRange"]
|> then(&"#{&1["Low"]}歳 〜 #{&1["High"]}歳")

スクリーンショット 2022-12-06 17.58.48.png

性別

性別も分かります

lenna_face["Gender"]
|> then(fn attr ->
  "#{Float.round(attr["Confidence"])}% " <>
    case attr["Value"] do
      "Female" ->
        "女性"
      "Male" ->
        "男性"
    end
end)

スクリーンショット 2022-12-06 17.59.26.png

その他の情報

他にも色々判定してくれます

そうであるか、そうでないかを日本語に置き換えるための関数を用意します

to_jp = fn bool ->
  case bool do
    true ->
      "る"
    false ->
      "ない"
  end
end

判定する項目の一覧を用意します

attributes =
  [
    {"Smile", "微笑んで"},
    {"EyesOpen", "目を開けて"},
    {"MouthOpen", "口を開けて"},
    {"Eyeglasses", "メガネをかけて"},
    {"Sunglasses", "サングラスをかけて"},
    {"Mustache", "髭が生えて"},
    {"Beard", "ハゲて"},
  ]

それぞれについて判定結果を表示します

attributes
|> Enum.map(fn {en, jp} ->
  lenna_face[en]
  |> then(fn attr ->
    "#{Float.round(attr["Confidence"])}% #{jp}#{to_jp.(attr["Value"])}"
  end)
end)

スクリーンショット 2022-12-06 18.01.16.png

 まとめ

画像 AI サービスの Rekognition は Livebook と非常に相性がいいですね

色々な写真や動画を分析できそうです

14
0
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
14
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?