【Amazon Translate編】AWSのサービスを活用した有名人認識アプリの開発してみた!
2021.08.31
目次
【Amazon Translate編】有名人認識アプリの開発に挑戦!
前回に引きつづき、「有名人画像認識アプリを開発する」といったテーマを体験しながら、一緒に勉強していきましょう!
このテーマでは、
- 画像認識
- 音声合成
- 機械翻訳
この三つの技術を学ぶことができます。
今回は、機械翻訳に触れていきたいと思います!
前回の記事
こちらの記事もオススメ!
機械翻訳って何?
先ほど、Amazon Translate は機械翻訳を可能にすると述べましたが、「機械翻訳って何なの?」と疑問があるかもしれません。
機械翻訳を簡単に述べると、「コンピュータによる自動翻訳」です。
では、「自動翻訳とは何か?」といった疑問では、音声をその場で翻訳し、音声を出力するといったように、音声翻訳をさす場合に、使われます。
そして、「機械翻訳」は、その音声翻訳の流れの中で、音声認識した文字を翻訳する際のエンジンのことをさします。
音声翻訳は、機械翻訳を活用しプロセスを自動化させることで、ユーザー側に翻訳された音声を提供することができます。
近年では、ポケトークなどのように、リアルタイムな翻訳を可能にする製品も出てきています。
Amazon Translate は、大量のテキストを効率的に簡単に翻訳できる画期的なサービスです。
Amazon Translate とは?
では、その画期的なサービスを詳しく触れていきましょう。
まず、Amazon Translate の公式サイトによると、「自然で正確な言語翻訳」と説明されており、特徴としては、
- 言語対応である
- ディープラーニング手法を用いた、正確で流暢な翻訳を生成
- カスタム用語を使用可能
- 自動的に高い精度で言語を特定
- バッチ翻訳とリアルタイム翻訳に対応
以上を挙げることができます。
Amazon Translate を使ってみよう!
では、実際に Amazon Translate に触れていきましょう。
まず、Amazon Translate のページに移動してみましょう。
Amazon Translate の使用を開始するボタンを押下してください。
そうすると、テキストの読み上げ機能のページが開くと思います。
このコンソール画面では、ほとんど Google翻訳 などのほかの翻訳ツールと同じような使い方ができます。
ここでの注意点としては、Amazon Translate の管理コンソールで、基本的には、アジアパシフィック(東京)を選択、よろしくお願いいたします。
また、言語選択画面を見ていただければわかるように、54の多言語に対応していることが見れます。
しかし、Google翻訳のほうが無料で使えるし、言語ももっと多くの言語に対応しているではないかと優れている部分があります。
どちらも一長一短の部分があります。
ただ、APIで他のサービスとの連携が取れる部分や、カスタム用語により、自分自身で設定した特定の翻訳をするといった面で、様々なケースで利用している機会が多いです。
Python で Translate に触れる
Python で 「AWS」のサービスを扱うには、先ほどインストールした Boto3 を使う必要があります。
ライブラリのインポート
では、まずライブラリをインポートするコードを書いていきましょう!
1 2 3 4 5 | # ライブラリをインポートする import boto3 # Translate をつかうためのクライアントを作成 translate_client = boto3.client(service_name='translate') |
boto3 を扱うためには、Session を立ち上げて、Rekognition のクライアントを作成するといった処理が必要です。
ただ、先ほど aws config で設定した情報を認証してくれるので、省略可能になります。
そのおかげで、コード上では、個人情報が書かれていないといったセキュリティ的にも安全なものになります。
機械翻訳を行おう!
では、翻訳を行う上で、必要なコードを書いていきましょう!
今回は、三笠山と呼ばれる童話をテキストファイルとして用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | むかしむかし、木曽の山中に、天狗が住んでいました。 天狗は毎日、御嶽山のてっぺんに寝そべって富士山をながめては、 「わしの力で、あの富士山よりも高くて立派な山をつくりたいな」 と、考えていました。 そんなある日の事、天狗はこんな事を思いつきました。 「富士山よりも高くて立派な山を一から作るのは大変だが、この山のてっぺんにほかの山を持ってきて三つ笠のように並べたら、きっと富士山よりも高くて立派な山になるに違いない」 そこで天狗は真夜中になると、形の良い山を探しました。 三岳村にやってきた天狗は、倉越山に目をつけて、ためしに倉越山を御嶽山のてっぺんに乗せてみました。 すると、なかなかの良い出来です。 「よし、この分なら、夜明けまでには出来上がるだろう。とりあえず、倉越山は元に戻してと」 天狗はすっかり安心して、少し休むつもりで横になると、そのままグーグーと眠ってしまいました。 やがて朝が来て、 「コケコッコー!」 と、一番どりが鳴きました。 「しまった! もうそんな時間か!」 天狗はあわてて飛び起きたものの、東の空はすでに明るくなっており、おまけに朝の早い百姓に見つかってしまったのです。 ここあたりに住む天狗は、人間に姿を見られてはいけない決まりになっています。 「もう少しで、富士山よりも立派な三笠山が出来たのに!」 天狗はそう叫びながら山の方へ逃げて行き、それっきり二度と姿を現わしませんでした。 この時からこの山を、三笠山と呼ぶようになったそうです。 |
次に、機械翻訳するために必要なコードを書いていきましょう!
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 | # 三笠山のテキストファイルの Path を指定 BEFORE_PATH = './三笠山(ja).txt' AFTER_PATH = './三笠山(en).txt' # 三笠山のテキストを読み込む with open(BEFORE_PATH, encoding='UTF-8') as f: text = f.read() # 翻訳前のテキスト print('***翻訳前のテキスト***\n', text) # Translate_text API を利用 response = translate_client.translate_text(Text=text, SourceLanguageCode='ja', TargetLanguageCode='en') # responseで返ってきたデータ result_text = response['TranslatedText'] # 翻訳後のテキストを書き込む with open(AFTER_PATH, encoding='UTF-8', mode='w') as f: f.write(result_text) # 翻訳語のテキスト print('\n***翻訳後のテキスト***\n', result_text) |
Amazon Translate のコンソール上で利用したように、テキストを翻訳するためには、Amazon Translate の API である TranslateText というものを利用します。
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 | # Request Syntax { "SourceLanguageCode": "string", "TargetLanguageCode": "string", "TerminologyNames": [ "string" ], "Text": "string" } # Response Syntax { "AppliedTerminologies": [ { "Name": "string", "Terms": [ { "SourceText": "string", "TargetText": "string" } ] } ], "SourceLanguageCode": "string", "TargetLanguageCode": "string", "TranslatedText": "string" } |
TerminologyNamesは、カスタム辞書で扱うための辞書名のようなものです。
これにより、特定の単語を意図的に翻訳させることも可能になります。
また、リクエストで送れるテキストの最大文字列の長さは、基本的には 5000byte です。
簡単に機械翻訳に触れることができたと思います。
有名人認識アプリの開発!
前回の「Amazon Polly編」までで GUI で音声合成を行うことができました。
実装例 検出結果を機械翻訳してみよう!
では検出結果を音声合成し、出力するまでできているため、このアプリに「言語選択機能」「性別選択機能」を追加していきましょう。
流れとしては、Tkinter の機能である Menubar を用いて、設定機能を追加し、その選択された設定を用いて、機械翻訳を行えるようにしましょう。
まず、Menubar を作成していきます。
以下が参考例のコードです。
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 | class GuiApp(object): def make_app(self, app=None): # Menubar self.gendervar = tk.StringVar() self.gendervar.set("Male") self.langvar = tk.StringVar() self.langvar.set("ja") self.menubar = tk.Menu(self.app) self.filemenu = tk.Menu(self.menubar, tearoff=0) self.filemenu.add_separator() self.filemenu.add_command(label='Exit', command=self.app.destroy) # gender self.gendermenu = tk.Menu(self.menubar, tearoff=0) self.gendermenu.add_radiobutton(label = '男性', variable = self.gendervar, value = "Male") self.gendermenu.add_radiobutton(label = '女性', variable = self.gendervar, value = "Female") # lang self.langmenu = tk.Menu(self.menubar, tearoff=0) self.langmenu.add_radiobutton(label='日本', variable=self.langvar, value='ja') self.langmenu.add_radiobutton(label='英語', variable=self.langvar, value='en') self.langmenu.add_radiobutton(label='フランス語', variable=self.langvar, value='fr') self.langmenu.add_radiobutton(label='ドイツ語', variable=self.langvar, value='de') self.langmenu.add_radiobutton(label='イタリア語', variable=self.langvar, value='it') self.langmenu.add_radiobutton(label='スペイン語', variable=self.langvar, value='es') # Add self.menubar.add_cascade(label='ファイル', menu=self.filemenu) self.menubar.add_cascade(label='性別', menu=self.gendermenu) self.menubar.add_cascade(label='言語', menu=self.langmenu) self.app.config(menu=self.menubar) |
では、次に、設定から取得した情報から翻訳を行う機能を追加していきます。
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 | class GuiApp(object): def translate(self,before_text): # Translate をつかうためのクライアントを作成 translate_client = boto3.client('translate') # Translate_text API を利用 response = translate_client.translate_text(Text=before_text, SourceLanguageCode='ja', TargetLanguageCode=self.langvar.get()) # responseで返ってきたデータ return response['TranslatedText'] # 解析結果を音声合成する def thread_polly(self): result_text = make_result_text(self.ans_data) if self.langvar.get() != 'ja': result_text = self.translate(result_text) if self.gendervar.get() == 'Male': polly_voice = MALE_POLLY_LIST[self.langvar.get()] elif self.gendervar.get() == 'Female': polly_voice = FEMALE_POLLY_LIST[self.langvar.get()] text_to_sound(result_text, polly_voice) return True |
検索結果から整形した result_text を用いて、AWS のサービスである TranslateAPI の機械翻訳を行います。
その際に、設定した言語が日本語かどうかチェックを行い、日本語であれば、機械翻訳を行う必要がないと判断します。
設定した言語が日本語以外の場合、言語と性別の設定に応じて、AWSのサービスである Polly の VoiceID を取得します。
これで、GUI で Polly 機能、Translate 機能を追加することができました。
参考コード
以下、参考例のコードです。
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 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 | # 有名人画像認識アプリ import tkinter as tk import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os from tkinter import messagebox import tempfile from PIL import Image, ImageDraw, ImageFont, ImageTk import numpy as np import cv2 import sys import boto3 from botocore.exceptions import BotoCoreError, ClientError import pygame from mutagen.mp3 import MP3 as mp3 import datetime import time 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処理最小の制限 CONFIDENCE = 75.0 # 目安のConfidence ERROR_CDRAW = 20 # celeb_name描画時の制限 ERROR_ODRAW = 25 # object_name描画時の制限 FUNTTTF = 'msgothic' # Fontの設定 MALE_POLLY_LIST = { 'en':'Matthew', 'ja':'Takumi', 'fr':'Mathieu', 'de':'Hans', 'it':'Giorgio', 'es':'Enrique' } FEMALE_POLLY_LIST = { 'en':'Joanna', 'ja':'Mizuki', 'fr':'Celine', 'de':'Marlene', 'it':'Carla', 'es':'Conchita' } # 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 __init__(self): pass def make_app(self, app=None): # Window Setting self.app = app self.app.title('有名人画像認識アプリ') self.ww = self.app.winfo_screenwidth() lw = 850 self.wh = self.app.winfo_screenheight() lh = 650 self.app.geometry(str(lw)+"x"+str(lh)+"+"+str(int(self.ww/2-lw/2))+"+"+str(0)) # frame0 Setting self.frame0 = ttk.LabelFrame( self.app, text='Menu', width=1000, height=50, borderwidth=5, relief="sunken" ) self.frame0.propagate(False) # frame1 Setting self.frame1 = ttk.LabelFrame( self.frame0, text='画像ファイルを選択してください', width=300, height=50, borderwidth=5, relief="sunken" ) self.frame1.propagate(False) # frame2 Setting self.frame2 = ttk.LabelFrame( self.frame0, text='Rekognition', width=700, height=50, borderwidth=5, relief="sunken" ) self.frame2.propagate(False) # Rekognition Setting self.buttonframe = ttk.Frame( self.frame2, width=100, height=45 ) self.buttonframe.propagate(False) # frame3 Setting self.frame3 = ttk.LabelFrame( self.app, text='Picture', width=800, height=500, borderwidth=5, relief="sunken" ) self.frame3.propagate(False) # Menubar self.gendervar = tk.StringVar() self.gendervar.set("Male") self.langvar = tk.StringVar() self.langvar.set("ja") self.menubar = tk.Menu(self.app) self.filemenu = tk.Menu(self.menubar, tearoff=0) self.filemenu.add_separator() self.filemenu.add_command(label='Exit', command=self.app.destroy) # gender self.gendermenu = tk.Menu(self.menubar, tearoff=0) self.gendermenu.add_radiobutton(label = '男性', variable = self.gendervar, value = "Male") self.gendermenu.add_radiobutton(label = '女性', variable = self.gendervar, value = "Female") # lang self.langmenu = tk.Menu(self.menubar, tearoff=0) self.langmenu.add_radiobutton(label='日本', variable=self.langvar, value='ja') self.langmenu.add_radiobutton(label='英語', variable=self.langvar, value='en') self.langmenu.add_radiobutton(label='フランス語', variable=self.langvar, value='fr') self.langmenu.add_radiobutton(label='ドイツ語', variable=self.langvar, value='de') self.langmenu.add_radiobutton(label='イタリア語', variable=self.langvar, value='it') self.langmenu.add_radiobutton(label='スペイン語', variable=self.langvar, value='es') # Add self.menubar.add_cascade(label='ファイル', menu=self.filemenu) self.menubar.add_cascade(label='性別', menu=self.gendermenu) self.menubar.add_cascade(label='言語', menu=self.langmenu) self.app.config(menu=self.menubar) # Layout Setting self.frame1.grid(row=0, column=0, padx=10) self.buttonframe.grid(row=0) self.frame2.grid(row=0, column=1, padx=10) self.frame0.grid(row=0, column=0, pady=10, columnspan=2) self.frame3.grid(row=1, column=0, padx=20, sticky=tk.W+tk.E) self.frame3.grid_rowconfigure(0, weight=1) self.frame3.grid_columnconfigure(0, weight=1) self.frame1_setting() self.frame2_setting() self.frame3_setting() self.load_open = True return True """ 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 # 選択された画像に切り替える 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 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 translate(self,before_text): # Translate をつかうためのクライアントを作成 translate_client = boto3.client('translate') # Translate_text API を利用 response = translate_client.translate_text(Text=before_text, SourceLanguageCode='ja', TargetLanguageCode=self.langvar.get()) # responseで返ってきたデータ return response['TranslatedText'] # 解析結果を音声合成する def thread_polly(self): result_text = make_result_text(self.ans_data) if self.langvar.get() != 'ja': result_text = self.translate(result_text) if self.gendervar.get() == 'Male': polly_voice = MALE_POLLY_LIST[self.langvar.get()] elif self.gendervar.get() == 'Female': polly_voice = FEMALE_POLLY_LIST[self.langvar.get()] text_to_sound(result_text, polly_voice) 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 # 音声出力 self.thread_polly() return True # 有名人の result_text 作成 def make_celeb_names(ans_data): for celeb in ans_data.celeb: if celeb.confidence >= CONFIDENCE: # 一致信頼度が75%以上のものを認識結果として採用する ans_data.celeb_names += celeb.name +'、' return True # 検出した人の数 def detect_face(ans_data): for celeb in ans_data.celeb: if celeb.confidence >= CONFIDENCE: # 一致信頼度が75%以上のものを認識結果として採用する ans_data.celeb_cnt += 1 else: # 一致信頼度が75%未満はUnknown ans_data.unknown_cnt += 1 ans_data.unknown_cnt += len(ans_data.unknown) return True # 検出した物体の数 def detect_object(ans_data): for info in ans_data.object: if info.name not in ans_data.object_namelist: ans_data.object_names += info.name + '、' ans_data.object_namelist.append(info.name) return True # 解析結果テキストを作成 def make_result_text(ans_data): celeb_text = 'この写真に写っている人物は、' + ans_data.celeb_names[:-1] + ' です。' unknown_text = '名前のわからない人が' + str(ans_data.unknown_cnt) + '名いました。' obejct_text = 'この写真には、' + ans_data.object_names + ' が写っています。' not_celeb_text = '有名人を認識できませんでした。' final_text = '以上、検出結果でした。' else_text = 'またのご利用お待ちしております' result_text = '' # celeb検出 if ans_data.celeb_cnt > 0: result_text += celeb_text # celeb検出なし if ans_data.celeb_cnt <= 0: result_text += not_celeb_text # Unknown検出 if ans_data.unknown_cnt > 0: result_text += unknown_text # Object検出 if len(ans_data.object_namelist) > 0: result_text += obejct_text # Object検出なし and Unknown検出なし if len(ans_data.object_namelist) <= 0 and ans_data.unknown_cnt <= 0 and ans_data.celeb_cnt <= 0: result_text += else_text # しらんけど追加 else: result_text += final_text return result_text # 音声データ作成 def text_to_sound(result_text, polly_voice): # The text string can be a maximum of 3000 bytes long. if len(result_text.encode()) > 3000: result_text = result_text[:3000] try: polly = boto3.client('polly') # Request speech synthesis response = polly.synthesize_speech( Text=result_text, OutputFormat='mp3', VoiceId=polly_voice) except (BotoCoreError, ClientError) as error: # The service returned an error, exit gracefully print(error) print("Can't synthesize_speech the text") messagebox.showerror('Error', "BotoCoreError, ClientError: synthesize_speech the text") sys.exit(-1) # Access the audio stream from the response if 'AudioStream' in response: TMPDIR = tempfile.TemporaryDirectory() # make musicfile dt_now = datetime.datetime.now() music_path = TMPDIR.name + f'/output_{dt_now.microsecond}_{dt_now.second}_{dt_now.minute}.mp3' with open(music_path, "wb") as file: file.write(response['AudioStream'].read()) # playing music pygame.mixer.init() if not pygame.mixer.music.get_busy(): pygame.mixer.music.load(music_path) mp3_length = mp3(music_path).info.length pygame.mixer.music.play(1) time.sleep(mp3_length + 0.4) pygame.mixer.quit() TMPDIR.cleanup() else: # The response didn't contain audio data, exit gracefully print('Could not stream audio') messagebox.showerror('Error', "Could not stream audio") sys.exit(-1) 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): # 解析結果をもとに有名人の氏名 and 数を検出 detect_face(ans_data) # 有名人の result_text 作成 make_celeb_names(ans_data) # 解析結果をもとに物体数を検出 detect_object(ans_data) # 画像ファイルの加工 ans_data.img = displaying_face_boxes(ans_data) ans_data.img = displaying_object_boxes(ans_data) 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 # class変数初期化 class DetectInfo(object): def __init__(self): self.name = "" self.parent = [] self.confidence = 0 self.top = 0 self.left = 0 self.height = 0 self.width = 0 self.url = '' # 有名人の情報を取得する 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 def main(): # Window Setting app = tk.Tk() # Window size non resizable app.resizable(width=False, height=False) # Function make gui_app = GuiApp() gui_app.make_app(app) # mainloop windows open app.mainloop() if __name__ == '__main__': main() |
さいごに
「有名人画像認識アプリを開発する」シリーズを読んでいただき、大変ありがとうございました!
「 AWS 」はこんなこともできるんだよ、といったことを伝えたいために、このテーマを選択しました!
Python や GUI 開発、AWS に興味がある人は、本記事を見ていただいたことも何かの縁!
ぜひ他の記事も参考して、いろんな知識を蓄えていただければ幸いです。
では、お疲れさまでした!
記事を書いた人
システム開発依頼・お見積もりはこちらまでお願いします。
また、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, 機械学習