Python Lab #4 ― 顔認識 on GUI

# Python Lab #4 ― 顔認識 on GUI

GUI 上に写真ファイルをドラッグ・ドロップしたら写真の中の人間の顔を認識するという Python スクリプトを開発してみました。
ただし、ざっと開発してみただけなので、認識精度の調整などはしていません。

## 開発に至った動機

OpenCV を採用すれば十数行ほどで簡単な顔認識が開発できるということを知り、AI 技術の習得への第一歩を踏み出してみたいと考えました。
CUI だと色々な写真を試すのが面倒なので、GUI で顔認識処理をラップすることにしました。GUI 部分は Tkinter を採用しました（注）。

注）flet ではファイルのドラッグ・ドロップは実現できないとのことです。残念。

## 実行イメージ

**GUI 起動時**

真っ白い部分に写真ファイルをドラッグ・ドロップすると、顔認識を開始する。

![PythonLab-FaceRecognizer_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3529613/2e8d81c6-e390-40a3-cf7f-f8dad0dc0ecf.jpeg)

**顔認識**

顔認識をした写真を表示する。
別の写真ファイルをドラッグ・ドロップして、再度、顔認識をさせることもできる。

見ての通り、認識精度がイマイチ...。
とはいえ、頭に被り物をしていても、人間の顔であることを認識できるらしい。
写真は、<https://www.pexels.com/ja-jp/photo/22643338/> からダウンロードしたもの。

![PythonLab-FaceRecognizer_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3529613/98cc295c-6c15-f35f-1bb9-167a7556aa52.jpeg)

**エラー時**

エラー画面を表示する。
フォルダー名がマルチバイト文字のとき、エラーになってしまう。

![PythonLab-FaceRecognizer_3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3529613/01f34e01-2bdd-593a-6d2d-0704615863af.jpeg)

## 認識している残課題

**認識精度がイマイチ**

認識技術を学習して実装していく必要があります。
そもそも、今のソースコードに不備があるのかもしれない...。

**画面スクロール機能なし、画面リサイズに未対応**

tkinter の知識不足のため、まだ対処方法が分かっていません。

**フォルダー名やファイル名がマルチバイト文字のときに動作できず**

* PIL ライブラリの “Image.open()” において、フォルダー名がマルチバイト文字のファイルの読み込みができないようです。
* OpenCV ライブラリの “cv2.imread()” において、フォルダー名やファイル名がマルチバイト文字のファイルの読み込みができないようです。

対処方法として、一時的に “C:\temp” 等にコピーして読み込むという手を使えばできそうと考えています。試していません。

## 学習データのダウンロード

事前に XML ファイル “haarcascade_frontalface_default.xml” をフォルダー “.\xml” 配下に置く必要があります。

**XML ファイル “haarcascade_frontalface_default.xml” の取出し方法**

1. OpenCV のホームページ “<https://opencv.org>” にアクセスします。
2. ホームページ “<https://opencv.org>” のメニューの [Library] - [Releases] をクリックします。
3. リリースページ “<https://opencv.org/releases/>” の [OpenCV - 4.9.0] - [Sources] をクリックします。 
4. “opencv-4.9.0.zip” のダウンロードが開始されます。
5. “opencv-4.9.0.zip” を解凍します。
6. フォルダー “.\opencv-4.9.0\data\haarcascades” から XML ファイル　 “haarcascade_frontalface_default.xml” を取り出します。

## ソースコードの簡単な説明

* face_recognizer.py
    ```python:face_recognizer.py
    #!/usr/bin/env python3
    
    ・・・
    
    # Import Libraries
    import cv2
    import tkinter as tk
    from tkinter import Label, messagebox
    from tkinterdnd2 import *
    from PIL import Image, ImageTk, ImageOps
    
    # Constants
    XML_FILE_PATH = '.\\xml\\haarcascade_frontalface_default.xml'
    WINDOW_RATIO = 60
    WINDOW_WIDTH = 16 * WINDOW_RATIO
    WINDOW_HEIGHT = 10 * WINDOW_RATIO
    LINE_GAP = 70
    
    
    # Recognize Face
    def recognize_face(file_name: str):
        try:
            cascade = cv2.CascadeClassifier(XML_FILE_PATH)
            image_org = cv2.imread(file_name)
            image_gray = cv2.cvtColor(image_org, cv2.COLOR_BGR2GRAY)
            face_list = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(1, 1))
            for (x, y, w, h) in face_list:
                red = (0, 0, 255)
                cv2.rectangle(image_org, (x, y), (x + w, y + h), red, thickness=5)
            rgb_image = cv2.cvtColor(image_org, cv2.COLOR_BGR2RGB)
            return rgb_image
        except Exception as ex:
            raise Exception(ex)
    
    
    # Image Data
    global disp_image
    
    
    # Main
    def main() -> None:
    
        def drop_file(event):
    
            global disp_image
    
            canvas.delete('all')
            canvas_width = canvas.winfo_width()
            canvas_height = canvas.winfo_height()
            try:
                rgb_image = recognize_face(event.data)
                pil_image = Image.fromarray(rgb_image)
                align_image = ImageOps.pad(pil_image, (canvas_width, canvas_height), color='gray')
                disp_image = ImageTk.PhotoImage(image=align_image)
                canvas.create_image(0, 0, image=disp_image, anchor=tk.NW)
            except Exception as ex:  # noqa
                canvas.create_line(0 + LINE_GAP, 0 + LINE_GAP,
                                   canvas_width - LINE_GAP, canvas_height - LINE_GAP, fill='red')
                canvas.create_line(canvas_width - LINE_GAP, 0 + LINE_GAP,
                                   0 + LINE_GAP, canvas_height - LINE_GAP, fill='red')
                canvas.create_text(canvas_width / 2, canvas_height / 2, text='Read file error has occurred.')
                canvas.create_text(canvas_width / 2, canvas_height / 2 + 20, text='( %s )' % ex)
                messagebox.showerror('Error', 'Read file error has occurred.\n( %s )' % ex)
            return event.action
    
        root = TkinterDnD.Tk()
        root.title('Face Recognizer')
        root.geometry(f'{WINDOW_WIDTH}x{WINDOW_HEIGHT}')
    
        label = Label(root, text='Please drag and drop image file on below.')
        label.pack()
    
        root.drop_target_register(DND_FILES)
        root.dnd_bind('<<Drop>>', drop_file)
        canvas = tk.Canvas(root, bg='white')
        canvas.pack(expand=True, fill=tk.BOTH)
    
        root.mainloop()
    
    
    # Goto Main
    if __name__ == '__main__':
        main()
    ```
    **recoginize_face()**
    OpenCV を使用して学習済データ “.\xml\haarcascade_frontalface_default.xml” をもとに顔認識を行う。
    **drop_file()**
    ファイルがドラッグ・ドロップされたとき、“recognize_face()” を呼び出し、認識後の画像を画面に描画する。
    **main()**
    メイン処理。画面にドラッグ・ドロップのエリアを設定したり、ドラッグ・ドロップのイベント関数 “drop_file” を設定したりなどする。

## ソースコードの置き場所

https://github.com/tengbu-haoshou/Python_lab-face_recognizer

## 参考

**顔認識**

ただし、XML ファイル “haarcascade_frontalface_default.xml”  は、OpenCV ホームページからダウンロードしたほうがよさそうです。

https://qiita.com/1152v/items/dafa746d365b44c3aaa7

**XML ファイル “haarcascade_frontalface_default.xml” のダウンロード**

https://sh0122.hatenadiary.jp/entry/2017/10/30/210411

私も下記のようにハマり、下記と同じように対処しました。

> GitHubからhaarcascade_frontalface_default.xmlをダウンロードしてどこかのフォルダに保存。
> https://github.com/opencv/opencv/tree/master/data/haarcascades
> こちらから拝借しました。
> フォルダを保存したパスで、cascade_pathを変えてみるが・・・。エラーが。
>
> 解決策
> 色々調べていると、OpenCVの公式サイトがあるらしいので、そこからダウンロードする。

**OpenCV の新しい顔検出**

今後は、こちらにチャレンジしてみるかな？

https://qiita.com/UnaNancyOwen/items/f3db189760037ec680f3

**デモで使用した写真が置いてある場所**

https://www.pexels.com/ja-jp/

> https://www.pexels.com/ja-jp/license/
> シンプルな法的規律
> Pexels のすべての写真や動画は、無料でダウンロードして利用できます。