AWS活用における便利な小技を簡潔に紹介する連載「AWSチートシート」。今回は、AWSのAI画像/動画分析サービス「Amazon Rekognition」をPythonで利用する方法を紹介する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
「Amazon Web Services」(AWS)活用における便利な小技を簡潔に紹介する連載「AWSチートシート」。今回は、AWSのAI画像/動画分析サービス「Amazon Rekognition」をPythonで利用します。以下、Amazon Rekognitionに用意されているメソッドを概観し、幾つかの使い方を紹介します。
AWSには、事前トレーニング済みのAI(人工知能)を手軽に利用できる「AIサービス」が多数用意されており、その内容はコンピュータビジョンから言語、レコメンデーション、予測と多岐にわたります。
今回紹介するAmazon Rekognition(以下、Rekognition)は、深層学習を利用して画像と動画(一部ストリーミングを含む)を分析するAIクラウドサービスです。分析のタイプは「ラベル検出」「顔検出」「顔検索」「人物の動線追跡」「個人用防護具検出」「有名人検出」「テキスト検出」「不適切なコンテンツ検出」「カスタムラベル検出」の9つに大別されます。
ラベル検出では、物体や人物、テキスト、イベント、シーン、アクティビティーなど(Rekognitionではこれらを「ラベル」と総称)を、画像と動画から検出できます。
顔検出では、画像内の顔や目などの位置、感情や年齢などを予測できます。事前に保存しておいた顔の情報に対して、新たな画像や動画から検出された顔と照合できます。
人物の動線追跡では、保存済み動画内で検出された人物の動線を追跡できます。
個人用防護具検出では、画像で検出された人物が着用するフェースカバー、ハンドカバー、ヘッドカバーを検出できます。
有名人検出では、検出した有名人の名前、顔の特徴や表情、動線などを取得できます。
テキスト検出では、画像内のテキスト(英数字と記号)を検出できます。
不適切なコンテンツの検出では、アダルトコンテンツや暴力的なコンテンツを検出できます。
なお、ここまでの分析はRekognitionの学習済みモデルを使用しますが、カスタムラベル検出では、ユーザーの画像や動画データを基にモデルを追加でトレーニングすることで、ビジネスニーズに固有の物体やシーンを検出できるようになります。
Rekognitionの構成は分析対象とモデルトレーニングの有無によって、画像分析の「Rekognition Image」、動画分析の「Rekognition Video」、カスタムラベル検出の「Rekognition カスタムラベル」に分類されます。本稿では、このうちRekognition Imageを紹介します。
AWSの「AIサービス」はコンソール画面から利用できますが、開発を念頭に置かなくても、慣れてくれば今回のようにAPIを利用する方がより便利で効率的に感じてくるでしょう。本稿がそのように利用するきっかけになれば幸いです。
Rekognitionは従量課金制で、Rekognition ImageではAPIを使用して分析した画像の枚数に応じて毎月課金されます。その際の単価は、累積利用枚数に応じて段階的な割引が適用されるスタイルです。東京リージョンでの料金を画像1000枚当たりで見ると、最初の100万枚は1.30ドル、続く900万枚は1.00ドル、続く9000万枚は0.80ドル、1億枚以降は0.50ドルとなっています。顔メタデータのストレージは、1データ1カ月当たり0.00001ドルで、利用が1カ月未満の場合は日割りで料金が計算されます。
なお、Rekognition Imageは無料利用枠の対象になっており、最初のリクエストから12カ月間は1カ月当たり5000枚の画像分析と1000個の顔メタデータの保存を、無料で利用できます(ただし、無料利用枠を超えた場合には従量課金が適用されます)。
本稿では割愛するRekognition VideoとRekognition カスタムラベルも従量課金制で、無料利用枠も設定されています。詳しくは公式サイトでご確認ください。
本稿では、読者の環境で下記要件が満たされていることを仮定しています。
なおこれは必須ではありませんが、以下のサンプルコードは「Jupyter Notebook」での実行を想定しています。
Rekognition Imageには下記メソッドが用意されています。
メソッド名 | 機能 | 引数 | 戻り値 |
---|---|---|---|
detect_labels | 画像から物体やイベント、テーマなどのラベルを検出する | 画像データ、レスポンスに含めるラベル数の最大値、信頼度の下限 | 辞書 |
detect_faces | 画像から顔を検出する | 画像データ、顔属性要件 | 辞書 |
compare_faces | ソース画像内の最も大きい顔とターゲット画像内の顔を比較する | ソース画像データ、ターゲット画像データ、類似度しきい値、品質フィルター値 | 辞書 |
detect_protective_equipment | 画像から検出された人が着用している個人用防護具(PPE)を検出する | 画像データ、概要属性要件 | 辞書 |
recognize_celebrities | 画像から有名人を検出する | 画像データ | 辞書 |
get_celebrity_info | 有名人情報を取得する | 有名人ID | 辞書 |
detect_moderation_labels | 画像から不適切なコンテンツを検出する | 画像データ、信頼度の下限、HumanLoop構成 | 辞書 |
detect_text | 画像から文字を検出する | 画像データ、フィルター要件 | 辞書 |
create_collection | 特定のリージョンにコレクションを作成する | コレクションに付けるIDとタグ | 辞書 |
list_collections | アカウント内のコレクションID情報を返す | NextToken、レスポンスの最大値 | 辞書 |
describe_collection | 特定のコレクションの情報を返す | コレクションID | 辞書 |
index_faces | 画像から顔を検出して特定のコレクションに追加する | コレクションID、画像データ、外部画像ID、検出属性要件、最大検出数、品質フィルター値 | 辞書 |
list_faces | 特定のコレクションにある顔のメタデータを返す | コレクションID、NextToken、レスポンスの最大値 | 辞書 |
search_faces_by_image | 入力画像内の最も大きい顔と類似する顔をコレクションから検索する | コレクションID、画像データ、検索する顔の数の最大値、信頼度のしきい値、品質フィルター値 | 辞書 |
search_faces | 指定した顔IDと類似する顔をコレクションから検索する | コレクションID、顔ID、検索する顔の数の最大値、信頼度のしきい値 | 辞書 |
delete_faces | 特定のコレクションから顔情報を削除する | コレクションID、フェースID | 辞書 |
delete_collection | 特定のコレクションを削除する | コレクションID | 辞書 |
tag_resource | 特定のリソースにタグを追加する | リソースARN、タグ | 辞書 |
list_tags_for_resource | コレクションとカスタムモデルラベル内のタグリストを返す | リソースARN | 辞書 |
untag_resource | 特定のリソースからタグを削除する | リソースARN、タグのキー | 辞書 |
can_paginate | 各メソッドのページネーション有無を調べる | メソッド名 | 真偽値 |
get_paginator | メソッドに関するページネータを生成する | メソッド名 | ページネータオブジェクト |
generate_presigned_url | 署名済みURLを返す | メソッドとその引数 | 署名済みURL |
幾つか解説します。
Rekognition Imageにおける入力画像のファイル形式としては、JPEGとPNGがサポートされています。API実行時の画像データの渡し方には、バイトデータを直接渡す方法(最大5MB)と、「Amazon S3」バケットに保存されているオブジェクトを指定する方法(最大15MB)があります。本稿では全て前者で実行することにします。
Rekognition ImageのAPIには、入力イメージから検出された情報を保持しない非ストレージ型APIと、特定の顔情報を保持するストレージ型APIの2種類があります。アプリケーションの設計、インデックス構築などの際には、これらの違いを考慮する必要があります。
検出系のメソッドの戻り値には、物体や顔の境界を表すバウンディングボックスの情報が含まれるものがあり、これを基にアイテムの周囲に境界を表示できます。なおこの情報は、ボックスの高さと幅(HightとWidth)に関する数値と、画像内におけるボックス左上かどの位置(LeftとTop)の数値で構成されています。また、これらの数値は元画像のサイズを縦横とも「1」にスケールした尺度で与えられています(下図参照)。
そこで、バウンディングボックスを画像に重ねる際には、ボックスの値に画像のピクセル値をかけてリサイズしています。
# 画像にバウンディングボックスとキャプションを重ねる際に使用する関数 def draw_bbox(img_width, img_height, draw, bbox, caption, color): # バウンディングボックスの値を重ねる画像の大きさにスケーリング left = img_width * bbox['Left'] top = img_height * bbox['Top'] width = img_width * bbox['Width'] height = img_height * bbox['Height'] # 頂点を設定 points = ((left, top), (left + width, top), (left + width, top + height), (left, top + height), (left, top)) # バウンディングボックスを描画 draw.line(points, fill=color, width=3) # キャプションのサイズを取得 text_w, text_h = draw.textsize(caption) # キャプション領域を描画 draw.rectangle((left, top - text_h, left + text_w, top), fill=color) # キャプションを印字 draw.text((left, top - text_h), caption, fill='black')
ここからは、Rekognitionのメソッドの使い方を幾つか紹介します。
detect_labelsメソッドを利用して、画像からラベル(物体や人物、イベント、テーマ)を検出します。
サンプルは「Wikimedia Commons」(以下同様)から、ベトナムのフエ(世界遺産)の街で撮られた画像を使用しました。
from matplotlib.pyplot import get_cmap from PIL import Image, ImageDraw import boto3 # Rekognitionクライアントを作成 rekognition = boto3.client('rekognition') # ラベルを検出する関数 def detect_labels(photo): # ラベル検出を実行 with open(photo, 'rb') as image: response = rekognition.detect_labels( Image={'Bytes': image.read()}, MaxLabels=10) # 画像データの読み出し image = Image.open(photo) # サイズ(ピクセル)情報を取得 img_width, img_height = image.size # 描画のためのインスタンスを生成 draw = ImageDraw.Draw(image) # カラーマップを指定 cmap = get_cmap('Set3') # レスポンスから個々のラベル情報を取得 for i, label in enumerate(response['Labels']): # ラベル名を取得 label_name = label['Name'] # 信頼度を取得 confidence = label['Confidence'] # 描画に使う色をラベルごとに設定 color = cmap(i % len(cmap.colors), bytes=True)[:3] # ラベルの追加情報を取得 for instance in label['Instances']: # バウンディングボックス情報を取得 bbox = instance['BoundingBox'] # キャプションを設定 caption = f'{label_name} ({confidence:.2f})' # バウンディングボックスを描画 draw_bbox(img_width, img_height, draw, bbox, caption, color) # 画像を出力 display(image) # 入力画像のパス input_file = '1024px-Hue_Vietnam_Citadel-of-Huế-21.jpeg' # ラベルを検出 detect_labels(input_file)
キャプションにも表示されている通り、画像からは「自転車」「人物」「車輪」(信頼度順)が検出されました。ラベルごとのバウンディングボックスを表示したところ、(人物の左足首から先を除いて)こちらも正しく検出されていることが確認できました。
なおここでは出力していませんが、レスポンスの中には「自転車」の親ラベルの「乗り物」と、その親ラベルの「移動手段」など、ラベルの祖先情報も含まれています。興味のある方はレスポンス全体を出力してみてください。
recognize_celebritiesメソッドを利用して、画像から有名人を検出します。画像は、2020年末で活動を休止した「国民的グループ」の「嵐」の集合写真を使いました。
# 有名人を検出する関数 def recognize_celebrities(photo): # 有名人検出を実行 with open(photo, 'rb') as image: response = rekognition.recognize_celebrities( Image={'Bytes': image.read()}) # 画像データの読み出し image = Image.open(photo) # サイズ(ピクセル)情報を取得 img_width, img_height = image.size # 描画のためのインスタンスを生成 draw = ImageDraw.Draw(image) # カラーマップを指定 cmap = get_cmap('Set3') # レスポンスから個々の有名人情報を取得 for i, celebrityFace in enumerate(response['CelebrityFaces']): # バウンディングボックス情報を取得 bbox = celebrityFace['Face']['BoundingBox'] # 有名人名を取得 name = celebrityFace['Name'] # 信頼度を取得 confidence = celebrityFace['MatchConfidence'] # キャプションを設定 caption = f'{name} ({confidence:.2f})' # 描画に使う色を有名人ごとに設定 color = cmap(i % len(cmap.colors), bytes=True)[:3] # バウンディングボックスを描画 draw_bbox(img_width, img_height, draw, bbox, caption, color) # 画像を出力 display(image) # 入力画像のパス input_file = 'ARASHI_mengguncang_Jakarta!_1m_5s.jpeg' # 有名人を検出 recognize_celebrities(input_file)
日本人の有名人をしっかり検出できました。他の国の有名人ではどうでしょうか。
次の画像では、『TIME』誌の2020年の「Entertainer of the Year」に選ばれた、韓国の世界的音楽グループ「BTS」の集合写真を使いました。
# 入力画像のパス input_file = ('1280px-BTS_for_Dispatch_White_Day_' 'Special,_27_February_2019_01.jpeg') # 有名人を検出 recognize_celebrities(input_file)
筆者はなかなか覚えられませんが、資料によれば正しいメンバー名は左から、「Jin」「RM」「Jungkook」「J-Hope」「Suga」「V」「Jimin」とのことです。これに対して、Rekognitionは4人をJungkookに、1人を台湾の俳優Derek Changに見間違えてしまいました。
このような誤った検出を最終結果に反映させないためには、戻り値の「MatchConfidence」値に対するしきい値の設定や、人によるレビューが必要なようです。
コレクション機能を利用して顔情報を検索します。Rekognitionでは、顔に関する情報を「コレクション」という、サーバにあるコンテナに保存しておくことで、新たな画像と動画の中に、コレクションに保存されている既知の顔があるかどうかを調べることができます。今回は「ソルベー会議」という、1911年から続く物理と化学に関する歴史的な国際会議の集合写真を題材に、これらの機能を利用します。
手順は次の通りです。初めに、会議参加者の顔情報を保存するために、顔コレクションを作成します。次に、【1】1927年と【2】1933年のソルベー会議の集合写真から、イメージ内の顔を検出し、検出された顔情報をコレクションに保存します(ちなみに、保存される顔情報は顔のイメージそのものではなく、顔の特徴を抽出した特徴ベクトルです)。
準備ができたら、【3】アルバート・アインシュタイン博士と【4】マリ・キュリー博士(キュリー夫人)の肖像を用いて、2人がこれら集合写真に写っているかどうかを調べてみます。なお、アインシュタイン博士は1927年のみ写っているのに対して、キュリー博士は両年で写っているのが正解です。
まずコレクションを作成します。
# コレクションID collection_id = 'solvay_participants' # コレクションを作成 rekognition.create_collection(CollectionId=collection_id)
次に、作成したコレクションに【1】と【2】から検出される顔情報を登録
します。
# 顔情報を登録する関数 def index_faces(photo): # 顔情報を登録 with open(photo, 'rb') as image: response = rekognition.index_faces(CollectionId=collection_id, Image={'Bytes': image.read()}, ExternalImageId=photo) # 画像データの読み出し image = Image.open(photo).convert('RGB') # サイズ(ピクセル)情報を取得 img_width, img_height = image.size # 描画のためのインスタンスを生成 draw = ImageDraw.Draw(image) # カラーを指定 color = '#00d400' # レスポンスから登録された顔情報を取得 for faceRecord in response['FaceRecords']: # 顔IDを取得 faceid = faceRecord['Face']['FaceId'] # バウンディングボックス情報を取得 bbox = faceRecord['Face']['BoundingBox'] # キャプションを設定 caption = faceid # バウンディングボックスを描画 draw_bbox(img_width, img_height, draw, bbox, caption, color) # 画像を出力 display(image) # 1927年の集合写真の顔情報を登録 input_file = '1280px-Solvay_conference_1927.jpeg' index_faces(input_file) # 1933年の集合写真の顔情報を登録 input_file = '1179px-Solvay1933Large.jpeg' index_faces(input_file)
以上で準備ができました。それでは、【3】と【4】を用いてコレクションからアインシュタイン博士とキュリー博士が写っている写真を正しく探し出せるかどうか、試してみます。ちなみに、下記2点が【3】と【4】の画像です。
# 顔を検索する関数 def search_faces_by_image(photo): # 入力画像内の顔と類似する顔をコレクションから検索 with open(photo, 'rb') as image: response = rekognition.search_faces_by_image( CollectionId=collection_id, Image={'Bytes': image.read()}) # レスポンスから類似の顔情報を取得 for faceMatch in response['FaceMatches']: # 一致する顔が存在するイメージIDを取得 externalImageId = faceMatch['Face']['ExternalImageId'] # 画像データの読み出し image = Image.open(externalImageId).convert('RGB') # サイズ(ピクセル)情報を取得 img_width, img_height = image.size # 描画のためのインスタンスを生成 draw = ImageDraw.Draw(image) # カラーを指定 color = '#00d400' # バウンディングボックス情報を取得 bbox = faceMatch['Face']['BoundingBox'] # 類似度を取得 similarity = faceMatch['Similarity'] # キャプションを設定 caption = f'{similarity:.2f}' # バウンディングボックスを描画 draw_bbox(img_width, img_height, draw, bbox, caption, color) # 画像を出力 display(image) # 入力画像内の顔と類似する顔をコレクションから検索 input_file = '585px-Albert_Einstein_1921_by_F_Schmutzer.jpeg' search_faces_by_image(input_file) # 入力画像内の顔と類似する顔をコレクションから検索 input_file = '558px-Marie_Curie_c._1920s.jpeg' search_faces_by_image(input_file)
Rekognitionでも正しい結果を得ることができました。最後にコレクションを削除して、後片付けをしておいてください(この操作ではコレクション内に保存された顔情報も全て削除されます)。
# コレクションを削除 rekognition.delete_collection(CollectionId=collection_id)
なお、Rekognitionのモデルは定期的にアップデートされており、新しいコレクションを作成した際は最新バージョンのモデルにコレクションが関連付けられます。ここで、精度を向上させるにはモデルの定期的な更新が推奨される一方で、既存のコレクションは最新バージョンのモデルに更新されない点に注意してください。特に、「コレクションにソースイメージのバイトが保存されていない」「既存の顔ベクトルは、それ以降のバージョンに更新できない」「各バージョンのモデルに互換性がない」という3点に留意してください。
Rekognitionは、このような簡単なサンプルでも十分楽しめますが、識別結果に基づくインデックスを作成してコンテンツベースの詳細なクエリを行うといった実用的なユースケースが考えられます。興味を持った方は公式ドキュメントを参考にしながら、いろいろと試してみてはいかがでしょうか。
東京ITスクールでJava研修の講師、IT専門学校の教材、カリキュラム開発、一般社団法人とのプログラミング教育を通じた国際貢献事業などを担当。AWS認定資格は「機械学習 - 専門知識」など
Copyright © ITmedia, Inc. All Rights Reserved.