はじめに
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")
各認証情報を 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)
さあ、 Rekognition.detect_labels
で Rekognition に物体検出させましょう
rek_res =
Evision.imencode(".png", lenna_mat)
|> Rekognition.detect_labels()
|> ExAws.request!(auth_config)
色々返ってきてはいますが、これだと見にくいので整形しましょう
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()
一覧にしてみると、どうも無駄に同じ位置で Person
や Woman
や Adult
、 Female
が検出されています
確かに Lenna さんは Person
で Woman
で Adult
で Female
ですが、重なると見にくいので一つに絞ります
detections_df =
detections_df
|> DataFrame.distinct(["left", "top", "width", "height"], keep_all: true)
detections_df
|> Kino.DataTable.new()
これで人物二人になりました
ではこの位置情報を画像にプロットしましょう
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)
さすが 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)
show_objects.(dog_mat)
木の枝に隠れているバイクまで見逃していません
顔検出
続いて顔検出です
Rekognition.detect_faces
で顔検出ができます
attributes: ["ALL"]
を指定すると、顔の位置情報だけでなく、感情などの情報も併せて取得できます
rek_res =
Evision.imencode(".png", lenna_mat)
|> Rekognition.detect_faces(attributes: ["ALL"])
|> ExAws.request!(auth_config)
Lenna さんの顔情報をとっておきます
lenna_face =
rek_res["FaceDetails"]
|> Enum.at(0)
この顔情報からさまざまなことが分かります
顔の位置
物体検出と同じように顔の位置をプロットします
{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
)
顔のパーツ
目や鼻の位置も取得できます
{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)
目尻、眉尻、顎先、鼻先など、かなり細かく取得できますね
顔の向き
顔の向きまで分かります
lenna_face["Pose"]
|> then(&%{
"どれくらい首を傾げているか" => Float.round(&1["Roll"]),
"どれくらい見上げたり見下ろしたりしているか" => Float.round(&1["Pitch"]),
"どれくらい左右に向いているか" => Float.round(&1["Yaw"]),
})
値の単位は「度」で -180 〜 180 の範囲です
これを使えば「正面を見ているか」「上を見上げているか」といったことが判定できます
写りの良さ
顔がちゃんと写っているか判定できます
明るさを見ると、暗くて見えづらい顔を判別できます
lenna_face["Quality"]["Brightness"]
|> Float.round()
|> then(&"明るさ #{&1}%")
鮮明さで顔にフォーカスが合っているかを判定できます
lenna_face["Quality"]["Sharpness"]
|> Float.round()
|> then(&"鮮明さ #{&1}%")
感情
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()
Lenna さんは CARM 86% なので、ほぼ感情がない(表れていない)状態です
年齢
年齢までバレます
lenna_face["AgeRange"]
|> then(&"#{&1["Low"]}歳 〜 #{&1["High"]}歳")
性別
性別も分かります
lenna_face["Gender"]
|> then(fn attr ->
"#{Float.round(attr["Confidence"])}% " <>
case attr["Value"] do
"Female" ->
"女性"
"Male" ->
"男性"
end
end)
その他の情報
他にも色々判定してくれます
そうであるか、そうでないかを日本語に置き換えるための関数を用意します
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)
まとめ
画像 AI サービスの Rekognition は Livebook と非常に相性がいいですね
色々な写真や動画を分析できそうです