【Amazon Rekognition後編】AWSで有名人認識アプリを開発してみた!
2021.08.31
目次
【後編】有名人認識アプリの開発に挑戦!
さて、有名人画像認識アプリを開発するシリーズの「Amazon Rekognition 後編」を、今回は紹介していきたいと思います。
前回は、「Amazon Rekognition 前編」として、Amazon Rekognition の概要や、GUI 部分の大枠を実装していきましたね!
具体的に今回は、引き続き Python を使って、画像の解析結果を出力するなどの機能実装をしていきます。
では、はじめていきましょう!
前回の記事
こちらの記事もオススメ!
前回完成した有名人認識アプリの大枠
まずは、前回作成した GUI の画面部分がどうなっていたかを、以下の画像でサッとおさらいです。
今回は、この大枠への部品設置と、各種機能の実装を進めていきます。
では早速、有名人認識アプリの大枠に、部品を設置していきましょう!
有名人認識アプリの大枠に部品を設置する
実装イメージの確認
まず、Button や Canvas などの部品を、設置していきます。
以下の画像が、部品設置の実装イメージです。
部品設置を実装
では、実装イメージをもとに、コードを記述していきましょう!
部品設置の実装コードは、次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | class GuiApp(object): # 以下が追記部分 def make_app(self, app=None): self.frame1_setting() self.frame2_setting() self.frame3_setting() """ Frame1 Setting '画像ファイルを選択してください' """ def frame1_setting(self): # Widget Setting self.fstring = tk.StringVar() # Label Setting filename_label = ttk.Label(self.frame1, text='File名') filename_label.pack(side='left', anchor='center', expand=1) # textvariableにWidget用の変数を定義することで変数の値が変わるとテキストも動的に変わる self.filename_entry = ttk.Entry(self.frame1, text='', textvariable=self.fstring) self.filename_entry.pack(side='left', anchor='center', expand=1) # openFileFialog用のボタン button1 = ttk.Button(self.frame1, text='OPEN', command=self.open_file_dialog) button1.pack(side='left', anchor='center', expand=1) return True """ Frame2 Setting 'Rekognition' """ def frame2_setting(self): # Search用のボタン button2 = ttk.Button(self.buttonframe, text='SEARCH', command=self.image_analysis) button2.pack(side='left', anchor='center', expand=1) return True def mouse_y_scroll(self, event): if event.delta > 0: self.canvas.yview_scroll(-1, 'units') elif event.delta < 0: self.canvas.yview_scroll(1, 'units') return True """ Frame3 Setting 'Picture' """ def frame3_setting(self): self.file_path = tk.StringVar(value='') # Label Setting label_path = ttk.Label(self.frame3, textvariable=self.file_path) label_path.grid(row=0, column=0, sticky=tk.W) # Canvas Settting self.canvas = tk.Canvas(self.frame3, width=750, height=450, scrollregion=(0, 0, 0, 0), bg='white') # Scroll bar Setting xscroll = tk.Scrollbar( self.frame3, orient=tk.HORIZONTAL, command=self.canvas.xview) yscroll = tk.Scrollbar( self.frame3, orient=tk.VERTICAL, command=self.canvas.yview) # Canvas Setting self.canvas.config(xscrollcommand=xscroll.set, yscrollcommand=yscroll.set) self.canvas.bind("<ButtonPress-1>", lambda e: self.canvas.scan_mark(e.x, e.y)) self.canvas.bind("<B1-Motion>", lambda e: self.canvas.scan_dragto(e.x, e.y, gain=1)) self.canvas.bind("<MouseWheel>", self.mouse_y_scroll) # Layout Setting xscroll.grid(row=2, column=0, sticky=tk.E+tk.W) yscroll.grid(row=1, column=1, sticky=tk.N+tk.S) self.canvas.grid(row=1, column=0, sticky=tk.N+tk.E+tk.W+tk.S) return True # OPENボタン押下時の処理 def open_file_dialog(self): pass # SEARCHボタン押下時の処理 def image_analysis(self): pass |
ここまでで、解析前と解析後の画像出力するための、以下のような部品を作成することができました。
- OPEN ボタン
- SEARCH ボタン
- Picture フレームに Canvas
これからは、これら部品に、それぞれの機能を実装していきます。
OPENボタンの機能を実装する
OPENボタンの実装の流れとイメージ
まず、OPEN ボタンを押したときの機能を、実装してみましょう。
流れとしては、
(1)OPEN ボタンを押す
(2)フォルダから画像を選択
(3)Canvas に出力
といったケースを想定しています。
以下の画像が、OPEN ボタンの機能の実装例のイメージです。
OPENボタンで画像出力する機能の実装
では、先ほどの流れ通りに、OPEN ボタンの機能を実装していきましょう。
具体的な実装コードは、次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | # 有名人画像認識アプリ import os from tkinter import messagebox import tempfile from PIL import Image, ImageTk import numpy as np import cv2 FILE_EXT = ['.jpg', '.png', '.jpeg', '.jpe', '.jfif', '.gif', '.tif', '.tiff', '.bmp', '.dib', '.webp'] # 拡張子の制限 IMAGE_MAX = 1080 # resize処理最大の制限 IMAGE_REG = 400 # resize処理中間に拡大 IMAGE_MIN = 250 # resize処理最小の制限 # class変数初期化 class AnalysisData(object): def __init__(self): self.celeb = [] # 検出した有名人物情報リスト self.unknown = [] # 検出したUnknownリスト self.object = [] # 検出した物体情報リスト self.img = '' # 画像データ(解析前/解析後) self.celeb_names = '' # 検出した有名人の名前 self.object_names = '' # 検出した物体の名前 self.celeb_cnt = 0 # 検出した有名人の人数 self.unknown_cnt = 0 # Unknownの人数 self.object_namelist = [] # 検出した物体の名前のリスト self.rate = 0 # 画像をリサイズするための比率 self.filename = '' # イメージのパス class GuiApp(object): def make_app(self, app=None): self.load_open = True #追加 # 選択された画像に切り替える def chenge_before_img(self): tmpimgdir = tempfile.TemporaryDirectory() # 画像サイズのチェック if check_img(tmpimgdir, self.ans_data): # pillow input self.img = Image.open(open(self.ans_data.filename, 'rb')) self.width, self.height = self.img.size self.img = ImageTk.PhotoImage(self.img) self.canvas.configure(scrollregion=(0, 0, self.width, self.height)) self.image_on_canvas = self.canvas.create_image( 0, # x座標 0, # y座標 image=self.img, # 配置するイメージオブジェクトを指定 tag="illust", # タグで引数を追加する。 anchor=tk.NW ) if self.filename_entry.get() != '': self.fstring.set('') else: self.fstring.set('') tmpimgdir.cleanup() self.load_open = True return True # label用のファイルチェック def check_file_path(self): if len(self.ans_data.filename.encode()) > 80: name = '.....' + self.ans_data.filename[-80:] else: name = self.ans_data.filename return name # Fileネームのチェック def check_filename(self): if self.ans_data.filename == '': # ファイル選択しなかった場合はメイン画面へ戻る self.load_open = True messagebox.showerror('エラー', 'ファイルをもう一度選択してください') else: # ファイル名をチェックし、画像ファイルなら解析処理へ basename = os.path.basename(self.ans_data.filename) # パス付きファイル名からファイル名のみ抽出 ext = os.path.splitext(basename)[1] # ファイル名から拡張子を抽出 if ext in FILE_EXT: self.canvas.delete('textcanvas') self.canvas.delete('illust') name = self.check_file_path() self.file_path.set(name) self.frame3.update() if ext in FILE_EXT: # ファイルの拡張子チェック self.chenge_before_img() # 画像ファイルであれば解析処理へ else: messagebox.showerror('エラー', '画像ファイルではありません') self.fstring.set('') return True def open_file_dialog(self): self.ans_data = AnalysisData() # 解析データクラス生成 if self.filename_entry.get() == '': # ファイル選択ダイアログの表示 ftyp = [ ('画像ファイル', '*.jpg;*.png;*.jpeg;*.jpe;*.jfif;*.gif:*.tif;*.tiff;*.bmp;*.dib;*.webp'), ('テキストファイル', '*.txt'), ('全てのファイル', '*.*')] idir = '' self.ans_data.filename = filedialog.askopenfilename(filetypes=ftyp, initialdir=idir) self.fstring.set(self.ans_data.filename) else: self.ans_data.filename = self.filename_entry.get() self.check_filename() self.analysis_result = True return True # 入力画像のリサイズ処理 def image_resize_check(ans_data): img_height, img_width = ans_data.img.shape[:2] # 解析対象画像の高さと幅を取得 max_size = max(img_height, img_width) if max_size > IMAGE_MAX: ans_data.rate = IMAGE_MAX / max_size ans_data.img = cv2.resize(ans_data.img, \ (int(img_width * ans_data.rate), \ int(img_height * ans_data.rate))) if max_size < IMAGE_MIN: ans_data.rate = IMAGE_REG / max_size ans_data.img = cv2.resize(ans_data.img, \ (int(img_width * ans_data.rate), \ int(img_height * ans_data.rate))) return True # 日本語ファイル名 def imread(filename, flags=cv2.IMREAD_COLOR, dtype=np.uint8): try: n = np.fromfile(filename, dtype) img = cv2.imdecode(n, flags) return img except PermissionError as e: print(e) print("Can't open the file") messagebox.showerror('Error', "PermissionError: Can't open the file") return None except Exception as e: print(e) print("Can't open the file") messagebox.showerror('Error', "Exception: Can't open the file") return None # 画像サイズのチェック def check_img(tmpimgdir, ans_data): # 画像ファイルの表示 img = imread(ans_data.filename) if img is not None: ans_data.img = img # 解析対象画像のイメージデータを格納 # 画像が制限を超えていないかどうか image_resize_check(ans_data) # 画像が制限を超えているとき if ans_data.rate > 0: tmpfilename = tmpimgdir.name + '/before_img.jpg' cv2.imwrite(tmpfilename, ans_data.img) ans_data.filename = tmpfilename return True |
ここまでできたら、一度動作の確認をしていきましょう。
OPEN ボタンを押すことで、犬の画像を出力できれば成功です!
SEARCHボタンで画像解析する機能を実装する
SEARCHボタンの実装の流れ
では次は、SEARCH ボタンを押したときの機能も、実装してみましょう!
流れとしては、
(1)SEARCH ボタンを押す
(2)出力した画像を解析
(3)解析結果を描画・出力
といったケースを、想定しています。
SEARCHボタンの画像解析機能を実装
では早速、SEARCH ボタンの実装コードを、記述していきましょう。
具体的な実装コードは、次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | import sys import boto3 from botocore.exceptions import BotoCoreError, ClientError CONFIDENCE = 75.0 # 目安のConfidence ERROR_CDRAW = 20 # celeb_name描画時の制限 ERROR_ODRAW = 25 # object_name描画時の制限 FUNTTTF = 'msgothic' # Fontの設定 class GuiApp(object): # 解析後の画像に切り替える def change_after_img(self): tmpimgdir = tempfile.TemporaryDirectory() tmpfilename = tmpimgdir.name + '/after_img.jpg' cv2.imwrite(tmpfilename, self.ans_data.img) self.ans_data.filename = tmpfilename # pillow input self.img = Image.open(open(self.ans_data.filename, 'rb')) self.width, self.height = self.img.size self.img = ImageTk.PhotoImage(self.img) self.canvas.configure(scrollregion=(0, 0, self.width, self.height)) self.canvas.itemconfig( self.image_on_canvas, image=self.img ) tmpimgdir.cleanup() self.load_open = True return True def image_analysis(self): # 画像解析を行う analysis_rekogniton(self.ans_data) if self.analysis_result: # メイン処理 main_proc(self.ans_data) # 解析後の画像に切り替える if self.change_after_img(): self.app.update() self.analysis_result = False return True # get def get_font_rate(ans_data): # フォントの比率を設定 if int(8 * ans_data.rate) > 12: font_rate = int(8 * ans_data.rate) # フォントの比率を設定 else: font_rate = 12 return font_rate # get def get_img_rate(ans_data, r1, r2): # ボックスの比率を設定 if int(r1 * ans_data.rate) > r2: img_rate = int(r1 * ans_data.rate) # ボックスの比率を設定 else: img_rate = r2 return img_rate # get def get_cname(celeb): if celeb.confidence >= CONFIDENCE: # 一致信頼度が75%以上のものを認識結果として採用する name = celeb.name else: name = 'Unknown' # The text string can be a maximum of 20 bytes long. if len(name.encode()) > ERROR_CDRAW: name = name[:17] + '...' return name # get def get_oname(name): # The text string can be a maximum of 25 bytes long. if len(name.encode()) > ERROR_ODRAW: name = name[:22] + '...' return name # get def get_top(human, img_rate): # put box on image in position (0, 0) if (human.top - img_rate) > 0: top = human.top - img_rate else: top = 0 return top # 有名人の顔枠表示 def displaying_face_boxes(ans_data): img = ans_data.img font_rate = get_font_rate(ans_data) img_rate = get_img_rate(ans_data, 15, 20) # 有名人の顔枠 for celeb in ans_data.celeb: img = cv2.rectangle(img, (celeb.left, celeb.top), (celeb.left + celeb.width, celeb.top + celeb.height), (0, 0, 255), thickness=1) img = cv2.rectangle(img, (celeb.left + 4, celeb.top + 4), (celeb.left + celeb.width - 4, celeb.top + celeb.height - 4), (255, 0, 0), thickness=1) # Unknownの顔枠 for unknown in ans_data.unknown: img = cv2.rectangle(img, (unknown.left, unknown.top), (unknown.left + unknown.width, unknown.top + unknown.height), (0, 0, 255), thickness=1) img = cv2.rectangle(img, (unknown.left + 4, unknown.top + 4), (unknown.left + unknown.width - 4, unknown.top + unknown.height - 4), (255, 0, 0), thickness=1) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) image = Image.fromarray(img) font = ImageFont.truetype(FUNTTTF, font_rate) # 有名人の名前表示 for celeb in ans_data.celeb: name = get_cname(celeb) # get text size text_size = font.getsize(name) # set box size + 5px margins box_size = (text_size[0] + 2, text_size[1] + 2) # create image with correct size and #90ee90 background box_img = Image.new('RGB', box_size, '#90ee90') # put text on box with 10px margins box_draw = ImageDraw.Draw(box_img) box_draw.text((1, 1), name, font=font, fill='#000000') # put box on image in position (0, 0) top = get_top(celeb, img_rate) image.paste(box_img, (celeb.left, top)) # Unknownの名前表示 for unknown in ans_data.unknown: name = 'Unknown' # get text size text_size = font.getsize(name) # set box size + 5px margins box_size = (text_size[0] + 2, text_size[1] + 3) # create image with correct size and #90ee90 background box_img = Image.new('RGB', box_size, '#90ee90') # put text on box with 10px margins box_draw = ImageDraw.Draw(box_img) box_draw.text((1, 1), name, font=font, fill='#000000') # put box on image in position (0, 0) top = get_top(unknown, img_rate) image.paste(box_img, (unknown.left, top)) ans_img = np.array(image) return ans_img # 物体の枠表示 def displaying_object_boxes(ans_data): img = ans_data.img font_rate = get_font_rate(ans_data) img_rate = get_img_rate(ans_data, 4, 5) # 物体の枠 for info in ans_data.object: img = cv2.rectangle(img, (info.left, info.top), (info.left + info.width, info.top + info.height), (255, 0, 255), thickness=1) img = cv2.rectangle(img, (info.left + 4, info.top + 4), (info.left + info.width - 4, info.top + info.height - 4), (0, 255, 255), thickness=1) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) image = Image.fromarray(img) font = ImageFont.truetype(FUNTTTF, font_rate) for info in ans_data.object: name = info.name name = get_oname(name) # get text size text_size = font.getsize(name) # set box size + 5px margins box_size = (text_size[0] + 2, text_size[1] + 3) # create image with correct size and #B22222 background box_img = Image.new('RGB', box_size, '#ffc0cb') # put text on box with 10px margins box_draw = ImageDraw.Draw(box_img) box_draw.text((1, 1), name, font=font, fill='#000000') # put box on image in position (0, 0) top = info.top + img_rate left = info.left + img_rate image.paste(box_img, (left, top)) ans_img = np.array(image) return ans_img # メイン処理 def main_proc(ans_data): # 画像ファイルの加工 ans_data.img = displaying_face_boxes(ans_data) ans_data.img = displaying_object_boxes(ans_data) return True # 有名人の情報を取得する def get_celebrity_list(response, img): img_height, img_width = img.shape[:2] # 解析対象画像の高さと幅を取得 celeb_list = [] for celebrity in response['CelebrityFaces']: celeb = DetectInfo() celeb.name = celebrity['Name'] celeb.confidence = celebrity['MatchConfidence'] # 顔の位置情報を算出 celeb.top = int(img_height * celebrity['Face']['BoundingBox']['Top']) celeb.left = int(img_width * celebrity['Face']['BoundingBox']['Left']) celeb.height = int(img_height * celebrity['Face']['BoundingBox']['Height']) celeb.width = int(img_width * celebrity['Face']['BoundingBox']['Width']) # 有名人のurlを取得 celeb.url = celebrity['Urls'] celeb_list.append(celeb) return celeb_list # 有名人以外の情報を取得 def get_unknown_list(response, img): img_height, img_width = img.shape[:2] # 解析対象画像の高さと幅を取得 unknown_list = [] for unknownface in response['UnrecognizedFaces']: unknown = DetectInfo() unknown.name = 'Unknown' # 顔の位置情報を算出 unknown.top = int(img_height * unknownface['BoundingBox']['Top']) unknown.left = int(img_width * unknownface['BoundingBox']['Left']) unknown.height = int(img_height * unknownface['BoundingBox']['Height']) unknown.width = int(img_width * unknownface['BoundingBox']['Width']) unknown_list.append(unknown) return unknown_list # 物体の情報を取得 def get_object_list(response, img): img_height, img_width = img.shape[:2] # 解析対象画像の高さと幅を取得 object_list = [] for obj in response['Labels']: for instance in obj['Instances']: info = DetectInfo() info.name = obj['Name'] info.parent = obj['Parents'] info.confidence = instance['Confidence'] # 物体の位置情報を算出 info.top = int(img_height * instance['BoundingBox']['Top']) info.left = int(img_width * instance['BoundingBox']['Left']) info.height = int(img_height * instance['BoundingBox']['Height']) info.width = int(img_width * instance['BoundingBox']['Width']) object_list.append(info) return object_list # awsのrekognitionを用いて、検出するライブラリ def analysis_rekogniton(ans_data): with open(ans_data.filename, 'rb') as file_image: source_bytes = file_image.read() # 画像ファイルを読み込み、バイト列を取得 try: # 画像のバイト列をrekognition.recognize_celebritiesで解析 rekognition = boto3.client('rekognition') response = rekognition.recognize_celebrities(Image={'Bytes': source_bytes}) except (BotoCoreError, ClientError) as error: # The service returned an error, exit gracefully print(error) print("Can't rekognition the image") messagebox.showerror('Error', "BotoCoreError, ClientError: Can't rekognition the image") sys.exit(-1) # rekognitionの検出結果から人物情報(名前、顔位置情報)を取得 celeb_list = get_celebrity_list(response, ans_data.img) unknown_list = get_unknown_list(response, ans_data.img) try: # 画像のバイト列をrekognition.detect_labelsで解析 response = rekognition.detect_labels(Image={'Bytes': source_bytes}, MinConfidence=75.0) except (BotoCoreError, ClientError) as error: # The service returned an error, exit gracefully print(error) print("Can't rekognition the image") messagebox.showerror('Error', "BotoCoreError, ClientError: Can't rekognition the image") sys.exit(-1) # rekognitionの検出結果から物体情報を取得 object_list = get_object_list(response, ans_data.img) ans_data.celeb = celeb_list # 検出した人物情報リストを格納 ans_data.unknown = unknown_list # 検出したUnknownリストを格納 ans_data.object = object_list # 検出した物体情報リストを格納 return True |
SEARCH ボタンを押すことで、解析結果を出力することができれば完成です!
Amazon Polly編へ続く!
有名人画像認識アプリを開発するシリーズの「後編」は、これで以上となります。
無事、Amazon Rekognition を使って、画像認識を行うことができましたね!
今後は、このアプリを、さらに進化させていきたいと思います。
具体的には、音声合成や機械翻訳などの機能を追加する予定です。
では、次回もお楽しみに!
次回の記事はコチラ
記事を書いた人
システム開発依頼・お見積もりはこちらまでお願いします。
また、WEB・スマホ系エンジニアを積極採用中です!
こちらの記事もオススメ!
書いた人はこんな人
- ライトコード社員ブログ
- 「好きなことを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡本社、東京オフィスの2拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もりは大歓迎!
また、WEBエンジニアとモバイルエンジニアも積極採用中です!
ご応募をお待ちしております!
- IT技術2021.09.08【Unity】オブジェクトプーリングの実装方法を解説!~弾の作成チュートリアル~
- ITエンタメ2021.09.07量子コンピュータの歴史~考案から実用化までの道のり~
- エンジニアになろう!2021.09.06【最終回】Djangoで日記アプリを作ろう~総復習編~
- エンジニアになろう!2021.09.03【第8回】Djangoで日記アプリを作ろう~認証機能実装編~
- 投稿者: ライトコード社員ブログ
- IT技術
- AWS, Python, 機械学習