著者は、 Write for DOnations プログラムの一環として、 Girls WhoCodeを選択して寄付を受け取りました。

序章

コンピュータビジョンは、画像やビデオから高次の理解を引き出すことを目的としたコンピュータサイエンスのサブフィールドです。 このフィールドには、オブジェクト検出、画像復元(行列補完)、オプティカルフローなどのタスクが含まれます。 コンピュータービジョンは、自動運転車のプロトタイプ、従業員のいない食料品店、楽しいSnapchatフィルター、モバイルデバイスの顔認証システムなどのテクノロジーを強化します。

このチュートリアルでは、事前にトレーニングされたモデルを使用してSnapchat風の犬用フィルターを作成する際のコンピュータービジョンについて説明します。 Snapchatに慣れていない人のために、このフィルターはあなたの顔を検出し、その上に犬のマスクを重ね合わせます。 次に、顔の感情分類子をトレーニングして、フィルターが感情に基づいて犬のマスクを選択できるようにします。たとえば、幸せの場合はコーギー、悲しい場合はパグなどです。 その過程で、通常の最小二乗法とコンピュータービジョンの両方で関連する概念を探求し、機械学習の基礎を学びます。

チュートリアルを進めていくと、次のようになります。 OpenCV、コンピュータビジョンライブラリ、 numpy 線形代数ユーティリティの場合、および matplotlib プロット用。 また、コンピュータビジョンアプリケーションを構築するときに、次の概念を適用します。

  • 回帰および分類手法としての通常の最小二乗。
  • 確率的勾配神経回路網の基礎。

このチュートリアルを完了する必要はありませんが、これらの数学的概念に精通している場合は、より詳細な説明のいくつかを理解しやすくなります。

  • 基本的な線形代数の概念:スカラー、ベクトル、および行列。
  • 基本的な微積分:導関数をとる方法。

このチュートリアルの完全なコードは、https://github.com/do-community/emotion-based-dog-filterにあります。

始めましょう。

前提条件

このチュートリアルを完了するには、次のものが必要です。

ステップ1—プロジェクトの作成と依存関係のインストール

このプロジェクトのワークスペースを作成し、必要な依存関係をインストールしましょう。 ワークスペースと呼びます DogFilter:

  1. mkdir ~/DogFilter

に移動します DogFilter ディレクトリ:

  1. cd ~/DogFilter

次に、プロジェクトの新しいPython仮想環境を作成します。

  1. python3 -m venv dogfilter

環境をアクティブにします。

  1. source dogfilter/bin/activate

プロンプトが変わり、環境がアクティブであることを示します。 次に、このチュートリアルで使用するPythonのディープラーニングフレームワークであるPyTorchをインストールします。 インストールプロセスは、使用しているオペレーティングシステムによって異なります。

macOSで、次のコマンドを使用してPytorchをインストールします。

  1. python -m pip install torch==0.4.1 torchvision==0.2.1

Linuxでは、次のコマンドを使用します。

  1. pip install http://download.pytorch.org/whl/cpu/torch-0.4.1-cp35-cp35m-linux_x86_64.whl
  2. pip install torchvision

また、Windowsの場合は、次のコマンドを使用してPytorchをインストールします。

  1. pip install http://download.pytorch.org/whl/cpu/torch-0.4.1-cp35-cp35m-win_amd64.whl
  2. pip install torchvision

次に、事前にパッケージ化されたバイナリをインストールします OpenCVnumpy、それぞれコンピュータビジョンと線形代数ライブラリです。 前者は画像の回転などのユーティリティを提供し、後者は行列の反転などの線形代数ユーティリティを提供します。

  1. python -m pip install opencv-python==3.4.3.18 numpy==1.14.5

最後に、アセットのディレクトリを作成します。このディレクトリには、このチュートリアルで使用する画像が保持されます。

  1. mkdir assets

依存関係をインストールしたら、フィルターの最初のバージョンである顔検出器を作成しましょう。

ステップ2—顔検出器を構築する

私たちの最初の目的は、画像内のすべての顔を検出することです。 単一の画像を受け入れ、ボックスで囲まれた面を持つ注釈付き画像を出力するスクリプトを作成します。

幸い、独自の顔検出ロジックを作成する代わりに、事前トレーニング済みモデルを使用できます。 モデルを設定してから、事前にトレーニングされたパラメーターをロードします。 OpenCVは、両方を提供することでこれを簡単にします。

OpenCVは、ソースコードでモデルパラメータを提供します。 ただし、これらのパラメーターを使用するには、ローカルにインストールされたOpenCVへの絶対パスが必要です。 その絶対パスは異なる可能性があるため、代わりに独自のコピーをダウンロードして、 assets フォルダ:

  1. wget -O assets/haarcascade_frontalface_default.xml https://github.com/opencv/opencv/raw/master/data/haarcascades/haarcascade_frontalface_default.xml

The -O オプションは宛先を次のように指定します assets/haarcascade_frontalface_default.xml. 2番目の引数はソースURLです。

次の画像のPexels(CC0、元の画像へのリンク)からすべての顔を検出します。

まず、画像をダウンロードします。 次のコマンドは、ダウンロードした画像を次のように保存します children.png の中に assets フォルダ:

  1. wget -O assets/children.png https://assets.digitalocean.com/articles/python3_dogfilter/CfoBWbF.png

検出アルゴリズムが機能することを確認するために、個々の画像でそれを実行し、結果の注釈付き画像をディスクに保存します。 作成する outputs これらの注釈付き結果のフォルダー。

  1. mkdir outputs

次に、顔検出器用のPythonスクリプトを作成します。 ファイルを作成する step_1_face_detect を使用して nano またはお気に入りのテキストエディタ:

  1. nano step_2_face_detect.py

次のコードをファイルに追加します。 このコードは、画像ユーティリティと顔分類子を含むOpenCVをインポートします。 残りのコードは、典型的なPythonプログラムの定型文です。

step_2_face_detect.py
"""Test for face detection"""

import cv2


def main():
    pass

if __name__ == '__main__':
    main()

今すぐ交換 pass の中に main ダウンロードしたOpenCVパラメータを使用して顔分類子を初期化するこのコードで機能します assets フォルダ:

step_2_face_detect.py
def main():
    # initialize front face classifier
    cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

次に、この行を追加して画像を読み込みます children.png.

step_2_face_detect.py
    frame = cv2.imread('assets/children.png')

次に、このコードを追加して、分類器が白黒画像でトレーニングされているため、画像を白黒に変換します。 これを実現するために、グレースケールに変換してからヒストグラムを離散化します。

step_2_face_detect.py
    # Convert to black-and-white
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    blackwhite = cv2.equalizeHist(gray)

次に、OpenCVの detectMultiScale 関数を使用して、画像内のすべての顔を検出します。

step_2_face_detect.py
    rects = cascade.detectMultiScale(
        blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
        flags=cv2.CASCADE_SCALE_IMAGE)
  • scaleFactor 各次元に沿って画像がどれだけ縮小されるかを指定します。
  • minNeighbors 候補長方形を保持する必要がある隣接する長方形の数を示します。
  • minSize は、検出されたオブジェクトの最小許容サイズです。 これよりも小さいオブジェクトは破棄されます。

戻り値のタイプは、タプルのリストです。各タプルには、長方形の最小x、最小y、幅、高さをこの順序で示す4つの数値があります。

検出されたすべてのオブジェクトを繰り返し処理し、 cv2.rectangle を使用して、画像上に緑色で描画します。

step_2_face_detect.py
    for x, y, w, h in rects:
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
  • 2番目と3番目の引数は、長方形の反対側の角です。
  • 4番目の引数は使用する色です。 (0, 255, 0) RGB色空間の緑に対応します。
  • 最後の引数は、線の幅を示します。

最後に、バウンディングボックス付きの画像を新しいファイルに書き込みます。 outputs/children_detected.png:

step_2_face_detect.py
    cv2.imwrite('outputs/children_detected.png', frame)

完成したスクリプトは次のようになります。

step_2_face_detect.py
"""Tests face detection for a static image."""  
  
import cv2  
  
  
def main():  
  
    # initialize front face classifier  
    cascade = cv2.CascadeClassifier(  
        "assets/haarcascade_frontalface_default.xml")  
  
    frame = cv2.imread('assets/children.png')  
  
    # Convert to black-and-white  
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  
    blackwhite = cv2.equalizeHist(gray)  
  
    rects = cascade.detectMultiScale(  
        blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),  
    flags=cv2.CASCADE_SCALE_IMAGE)  
  
    for x, y, w, h in rects:  
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)  
  
    cv2.imwrite('outputs/children_detected.png', frame)  
  
if __name__ == '__main__':  
    main()

ファイルを保存して、エディターを終了します。 次に、スクリプトを実行します。

  1. python step_2_face_detect.py

開ける outputs/children_detected.png. ボックスで輪郭が描かれた顔を示す次の画像が表示されます。

この時点で、顔検出器が機能しています。 画像を入力として受け取り、画像内のすべての面の周りに境界ボックスを描画して、注釈付きの画像を出力します。 次に、これと同じ検出をライブカメラフィードに適用してみましょう。

ステップ3—カメラフィードをリンクする

次の目的は、コンピューターのカメラを顔検出器にリンクすることです。 静止画像で顔を検出する代わりに、コンピューターのカメラからすべての顔を検出します。 カメラ入力を収集し、すべての顔を検出して注釈を付けてから、注釈付きの画像をユーザーに表示します。 手順2のスクリプトから続行するので、そのスクリプトを複製することから始めます。

  1. cp step_2_face_detect.py step_3_camera_face_detect.py

次に、エディターで新しいスクリプトを開きます。

  1. nano step_3_camera_face_detect.py

更新します main 公式OpenCVドキュメントのこのテストスクリプトのいくつかの要素を使用して機能します。 最初に初期化する VideoCapture コンピューターのカメラからライブフィードをキャプチャするように設定されているオブジェクト。 これを最初に配置します main 関数、関数内の他のコードの前:

step_3_camera_face_detect.py
def main():
    cap = cv2.VideoCapture(0)
    ...

定義する行から開始 frame、既存のすべてのコードをインデントし、すべてのコードを while ループ。

step_3_camera_face_detect.py
    while True:
        frame = cv2.imread('assets/children.png')
        ...
        for x, y, w, h in rects:  
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)  

        cv2.imwrite('outputs/children_detected.png', frame)

定義する行を置き換えます frame の開始時に while ループ。 ディスク上の画像から読み取る代わりに、カメラから読み取るようになりました。

step_3_camera_face_detect.py
    while True:
        # frame = cv2.imread('assets/children.png') # DELETE ME
        # Capture frame-by-frame
        ret, frame = cap.read()

行を置き換えます cv2.imwrite(...) の終わりに while ループ。 画像をディスクに書き込む代わりに、注釈付きの画像をユーザーの画面に表示します。

step_3_camera_face_detect.py
      cv2.imwrite('outputs/children_detected.png', frame)  # DELETE ME
      # Display the resulting frame
      cv2.imshow('frame', frame)

また、プログラムを停止できるように、キーボード入力を監視するコードを追加します。 ユーザーがヒットしたかどうかを確認します q 文字を入力し、その場合はアプリケーションを終了します。 直後の cv2.imshow(...) 以下を追加します。

step_3_camera_face_detect.py
...
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
...

この線 cv2.waitkey(1) キャプチャされた画像をユーザーに表示できるように、プログラムを1ミリ秒停止します。

最後に、キャプチャを解放して、すべてのウィンドウを閉じます。 これを外に置きます while ループして終了します main 関数。

step_3_camera_face_detect.py
...

    while True:
    ...


    cap.release()
    cv2.destroyAllWindows()

スクリプトは次のようになります。

step_3_camera_face_detect.py
"""Test for face detection on video camera.

Move your face around and a green box will identify your face.
With the test frame in focus, hit `q` to exit.
Note that typing `q` into your terminal will do nothing.
"""

import cv2


def main():
    cap = cv2.VideoCapture(0)

    # initialize front face classifier
    cascade = cv2.CascadeClassifier(
        "assets/haarcascade_frontalface_default.xml")

    while True:
        # Capture frame-by-frame
        ret, frame = cap.read()

        # Convert to black-and-white
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        blackwhite = cv2.equalizeHist(gray)

        # Detect faces
        rects = cascade.detectMultiScale(
            blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
            flags=cv2.CASCADE_SCALE_IMAGE)

        # Add all bounding boxes to the image
        for x, y, w, h in rects:
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

        # Display the resulting frame
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    # When everything done, release the capture
    cap.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

ファイルを保存して、エディターを終了します。

次に、テストスクリプトを実行します。

  1. python step_3_camera_face_detect.py

これにより、カメラがアクティブになり、カメラのフィードを表示するウィンドウが開きます。 あなたの顔はリアルタイムで緑色の四角で囲まれます:

:物事が機能するために非常に静止している必要がある場合は、部屋の照明が適切でない可能性があります。 あなたとあなたの背景が高いコントラストを持っている明るい部屋に移動してみてください。 また、頭の近くの明るい光は避けてください。 たとえば、太陽に背を向けている場合、このプロセスはうまく機能しない可能性があります。

次の目的は、検出された顔を取得し、それぞれに犬のマスクを重ねることです。

ステップ4—ドッグフィルターを作成する

フィルタ自体を作成する前に、画像がどのように数値で表されるかを調べてみましょう。 これにより、画像を変更し、最終的に犬のフィルターを適用するために必要な背景が得られます。

例を見てみましょう。 数字を使用して白黒画像を作成できます。 0 黒に対応し、 1 白に対応します。

1と0の境界線に注目してください。 どんな形が見えますか?

0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 1 1 1 0 0 0
0 0 1 1 1 1 1 0 0
0 0 0 1 1 1 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0

画像はひし形です。 このマトリックスの値を画像として保存する場合。 これにより、次の図が得られます。

0.1、0.26、0.74391など、0から1までの任意の値を使用できます。 0に近い数値は暗く、1に近い数値は明るくなります。 これにより、白、黒、および任意のグレーの色合いを表すことができます。 これは、0、1、およびその間の任意の値を使用して任意のグレースケール画像を作成できるようになったため、私たちにとって素晴らしいニュースです。 たとえば、次のことを考慮してください。 それが何であるかわかりますか? 繰り返しますが、各数字はピクセルの色に対応しています。

1  1  1  1  1  1  1  1  1  1  1  1
1  1  1  1  0  0  0  0  1  1  1  1
1  1  0  0 .4 .4 .4 .4  0  0  1  1
1  0 .4 .4 .5 .4 .4 .4 .4 .4  0  1
1  0 .4 .5 .5 .5 .4 .4 .4 .4  0  1
0 .4 .4 .4 .5 .4 .4 .4 .4 .4 .4  0
0 .4 .4 .4 .4  0  0 .4 .4 .4 .4  0
0  0 .4 .4  0  1 .7  0 .4 .4  0  0
0  1  0  0  0 .7 .7  0  0  0  1  0
1  0  1  1  1  0  0 .7 .7 .4  0  1
1  0 .7  1  1  1 .7 .7 .7 .7  0  1
1  1  0  0 .7 .7 .7 .7  0  0  1  1
1  1  1  1  0  0  0  0  1  1  1  1
1  1  1  1  1  1  1  1  1  1  1  1

画像として再レンダリングすると、これが実際にはポケボールであることがわかります。

これで、白黒画像とグレースケール画像が数値でどのように表現されるかがわかりました。 色を導入するには、より多くの情報をエンコードする方法が必要です。 画像の高さと幅は次のように表されます h x w.

現在のグレースケール表現では、各ピクセルは0から1までの1つの値です。 同等に、画像には寸法があると言えます h x w x 1. 言い換えれば、すべて (x, y) 画像内の位置には1つの値しかありません。

色表現では、0から1までの3つの値を使用して各ピクセルの色を表現します。 1つの数字は「赤の程度」に対応し、1つは「緑の程度」に対応し、最後の数字は「青の程度」に対応します。 これをRGB色空間と呼びます。 これは、 (x, y) 画像内の位置には、3つの値があります (r, g, b). その結果、私たちのイメージは今 h x w x 3:

ここでは、各番号の範囲は0から1ではなく0から255ですが、考え方は同じです。 数字のさまざまな組み合わせは、濃紫などのさまざまな色に対応しています (102, 0, 204) または明るいオレンジ (255, 153, 51). 要点は次のとおりです。

  1. 各画像は、高さ、幅、カラーチャンネルの3つの次元を持つ数字のボックスとして表されます。 この数字のボックスを直接操作することは、画像を操作することと同じです。
  2. このボックスをフラット化して、単なる数字のリストにすることもできます。 このようにして、画像はvectorになります。 後で、画像をベクトルと呼びます。

画像が数値でどのように表現されるかを理解したので、犬のマスクを顔に適用する準備が整いました。 犬のマスクを適用するには、子画像の値を白以外の犬のマスクのピクセルに置き換えます。 まず、単一の画像で作業します。 手順2で使用した画像からこの顔の切り抜きをダウンロードします。

  1. wget -O assets/child.png https://assets.digitalocean.com/articles/python3_dogfilter/alXjNK1.png

また、以下の犬用マスクをダウンロードしてください。 このチュートリアルで使用されている犬のマスクは私自身の絵であり、CC0ライセンスの下でパブリックドメインにリリースされています。

これをダウンロードしてください wget:

  1. wget -O assets/dog.png https://assets.digitalocean.com/articles/python3_dogfilter/ED32BCs.png

と呼ばれる新しいファイルを作成します step_4_dog_mask_simple.py これは、犬のマスクを顔に適用するスクリプトのコードを保持します。

  1. nano step_4_dog_mask_simple.py

Pythonスクリプト用に次の定型文を追加し、OpenCVをインポートして numpy ライブラリ:

step_4_dog_mask_simple.py
"""Test for adding dog mask"""

import cv2
import numpy as np


def main():
    pass

if __name__ == '__main__':
    main()

交換 pass の中に main 元の画像と犬のマスクをメモリにロードするこれらの2行で機能します。

step_4_dog_mask_simple.py
...
def main():
    face = cv2.imread('assets/child.png')
    mask = cv2.imread('assets/dog.png')

次に、犬のマスクを子供に合わせます。 ロジックは以前に行ったものよりも複雑なので、次のような新しい関数を作成します。 apply_mask コードをモジュール化する。 画像をロードする2行の直後に、この行を追加して、 apply_mask 関数:

step_4_dog_mask_simple.py
...
    face_with_mask = apply_mask(face, mask)

と呼ばれる新しい関数を作成します apply_mask 上に置きます main 関数:

step_4_dog_mask_simple.py
...
def apply_mask(face: np.array, mask: np.array) -> np.array:
    """Add the mask to the provided face, and return the face with mask."""
    pass

def main():
...

この時点で、ファイルは次のようになります。

step_4_dog_mask_simple.py
"""Test for adding dog mask"""

import cv2
import numpy as np


def apply_mask(face: np.array, mask: np.array) -> np.array:
    """Add the mask to the provided face, and return the face with mask."""
    pass


def main():
    face = cv2.imread('assets/child.png')
    mask = cv2.imread('assets/dog.png')
    face_with_mask = apply_mask(face, mask)

if __name__ == '__main__':
    main()

構築しましょう apply_mask 関数。 私たちの目標は、子供の顔にマスクを適用することです。 ただし、ドッグマスクのアスペクト比を維持する必要があります。 そのためには、犬のマスクの最終的な寸法を明示的に計算する必要があります。 内部 apply_mask 機能、交換 pass 両方の画像の高さと幅を抽出するこれらの2本の線で:

step_4_dog_mask_simple.py
...
    mask_h, mask_w, _ = mask.shape
    face_h, face_w, _ = face.shape

次に、どのディメンションを「さらに縮小」する必要があるかを判断します。 正確には、2つの制約のうちのより厳しいものが必要です。 この行をに追加します apply_mask 関数:

step_4_dog_mask_simple.py
...

    # Resize the mask to fit on face
    factor = min(face_h / mask_h, face_w / mask_w)

次に、このコードを関数に追加して、新しい形状を計算します。

step_4_dog_mask_simple.py
...
    new_mask_w = int(factor * mask_w)
    new_mask_h = int(factor * mask_h)
    new_mask_shape = (new_mask_w, new_mask_h)

ここでは、数値を整数にキャストします。 resize 関数には整数次元が必要です。

次に、このコードを追加して、犬のマスクのサイズを新しい形状に変更します。

step_4_dog_mask_simple.py
...

    # Add mask to face - ensure mask is centered
    resized_mask = cv2.resize(mask, new_mask_shape)

最後に、画像をディスクに書き込んで、スクリプトの実行後にサイズ変更された犬のマスクが正しいことを再確認できるようにします。

step_4_dog_mask_simple.py
    cv2.imwrite('outputs/resized_dog.png', resized_mask)

完成したスクリプトは次のようになります。

step_4_dog_mask_simple.py
"""Test for adding dog mask"""
import cv2
import numpy as np

def apply_mask(face: np.array, mask: np.array) -> np.array:
    """Add the mask to the provided face, and return the face with mask."""
    mask_h, mask_w, _ = mask.shape
    face_h, face_w, _ = face.shape
    
    # Resize the mask to fit on face
    factor = min(face_h / mask_h, face_w / mask_w)
    new_mask_w = int(factor * mask_w)
    new_mask_h = int(factor * mask_h)
    new_mask_shape = (new_mask_w, new_mask_h)
    
    # Add mask to face - ensure mask is centered
    resized_mask = cv2.resize(mask, new_mask_shape)
    cv2.imwrite('outputs/resized_dog.png', resized_mask)


def main():
    face = cv2.imread('assets/child.png')
    mask = cv2.imread('assets/dog.png')
    face_with_mask = apply_mask(face, mask)

if __name__ == '__main__':
    main()

ファイルを保存して、エディターを終了します。 新しいスクリプトを実行します。

  1. python step_4_dog_mask_simple.py

で画像を開きます outputs/resized_dog.png マスクのサイズが正しく変更されたことを再確認します。 これは、このセクションで前述した犬のマスクと一致します。

次に、犬のマスクを子に追加します。 を開きます step_4_dog_mask_simple.py 再度ファイルして、に戻ります apply_mask 関数:

  1. nano step_4_dog_mask_simple.py

まず、サイズ変更されたマスクを書き込むコード行をから削除します apply_mask あなたはもはやそれを必要としないので機能します:

    cv2.imwrite('outputs/resized_dog.png', resized_mask)  # delete this line
    ...

その代わりに、このセクションの最初から画像表現の知識を適用して、画像を変更します。 子画像のコピーを作成することから始めます。 この行をに追加します apply_mask 関数:

step_4_dog_mask_simple.py
...
    face_with_mask = face.copy()

次に、犬のマスクが白でない、または白に近いすべての位置を見つけます。 これを行うには、すべてのカラーチャネルでピクセル値が250未満であるかどうかを確認します。これは、白に近いピクセルが近くにあると予想されるためです。 [255, 255, 255]. このコードを追加します:

step_4_dog_mask_simple.py
...
    non_white_pixels = (resized_mask < 250).all(axis=2)

この時点で、犬の画像はせいぜい子の画像と同じ大きさです。 犬の画像を顔の中央に配置したいので、このコードをに追加して、犬の画像を中央に配置するために必要なオフセットを計算します。 apply_mask:

step_4_dog_mask_simple.py
...
    off_h = int((face_h - new_mask_h) / 2)  
    off_w = int((face_w - new_mask_w) / 2)

犬の画像から子の画像に白以外のすべてのピクセルをコピーします。 子の画像は犬の画像よりも大きい可能性があるため、子の画像のサブセットを取得する必要があります。

step_4_dog_mask_simple.py
    face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
            resized_mask[non_white_pixels]

次に、結果を返します。

step_4_dog_mask_simple.py
    return face_with_mask

の中に main 関数、このコードを追加して、結果を書き込みます apply_mask 結果を手動で再確認できるように、出力画像に機能します。

step_4_dog_mask_simple.py
...
    face_with_mask = apply_mask(face, mask)
    cv2.imwrite('outputs/child_with_dog_mask.png', face_with_mask)

完成したスクリプトは次のようになります。

step_4_dog_mask_simple.py
"""Test for adding dog mask"""

import cv2
import numpy as np


def apply_mask(face: np.array, mask: np.array) -> np.array:
    """Add the mask to the provided face, and return the face with mask."""
    mask_h, mask_w, _ = mask.shape
    face_h, face_w, _ = face.shape

    # Resize the mask to fit on face
    factor = min(face_h / mask_h, face_w / mask_w)
    new_mask_w = int(factor * mask_w)
    new_mask_h = int(factor * mask_h)
    new_mask_shape = (new_mask_w, new_mask_h)
    resized_mask = cv2.resize(mask, new_mask_shape)

    # Add mask to face - ensure mask is centered
    face_with_mask = face.copy()
    non_white_pixels = (resized_mask < 250).all(axis=2)
    off_h = int((face_h - new_mask_h) / 2)  
    off_w = int((face_w - new_mask_w) / 2)
    face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
         resized_mask[non_white_pixels]

    return face_with_mask

def main():
    face = cv2.imread('assets/child.png')
    mask = cv2.imread('assets/dog.png')
    face_with_mask = apply_mask(face, mask)
    cv2.imwrite('outputs/child_with_dog_mask.png', face_with_mask)

if __name__ == '__main__':
    main()

スクリプトを保存して実行します。

  1. python step_4_dog_mask_simple.py

次の犬のマスクをした子供の写真があります outputs/child_with_dog_mask.png:

これで、犬のマスクを顔に適用するユーティリティができました。 次に、作成したものを使用して、犬のマスクをリアルタイムで追加しましょう。

手順3で中断したところから再開します。 コピー step_3_camera_face_detect.pystep_4_dog_mask.py.

  1. cp step_3_camera_face_detect.py step_4_dog_mask.py

新しいスクリプトを開きます。

  1. nano step_4_dog_mask.py

まず、スクリプトの上部にあるNumPyライブラリをインポートします。

step_4_dog_mask.py
import numpy as np
...

次に、 apply_mask 以前の作業からこの新しいファイルへの機能 main 関数:

step_4_dog_mask.py
def apply_mask(face: np.array, mask: np.array) -> np.array:
    """Add the mask to the provided face, and return the face with mask."""
    mask_h, mask_w, _ = mask.shape
    face_h, face_w, _ = face.shape

    # Resize the mask to fit on face
    factor = min(face_h / mask_h, face_w / mask_w)
    new_mask_w = int(factor * mask_w)
    new_mask_h = int(factor * mask_h)
    new_mask_shape = (new_mask_w, new_mask_h)
    resized_mask = cv2.resize(mask, new_mask_shape)

    # Add mask to face - ensure mask is centered
    face_with_mask = face.copy()
    non_white_pixels = (resized_mask < 250).all(axis=2)
    off_h = int((face_h - new_mask_h) / 2)  
    off_w = int((face_w - new_mask_w) / 2)
    face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
         resized_mask[non_white_pixels]

    return face_with_mask
...

次に、この行を main 関数:

step_4_dog_mask.py
    cap = cv2.VideoCapture(0)

その行の後に次のコードを追加して、犬のマスクをロードします。

step_4_dog_mask.py
    cap = cv2.VideoCapture(0)
  
    # load mask
    mask = cv2.imread('assets/dog.png')
    ...

次に、 while ループし、次の行を見つけます。

step_4_dog_mask.py
        ret, frame = cap.read()

その後に次の行を追加して、画像の高さと幅を抽出します。

step_4_dog_mask.py
        ret, frame = cap.read()
        frame_h, frame_w, _ = frame.shape
        ...

次に、の行を削除します main バウンディングボックスを描画します。 この行は、 for 検出された面を反復するループ:

step_4_dog_mask.py
        for x, y, w, h in rects:
        ...
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) # DELETE ME
        ...

その代わりに、フレームをトリミングするこのコードを追加します。 美的目的のために、顔より少し広い領域をトリミングします。

step_4_dog_mask.py
        for x, y, w, h in rects:
            # crop a frame slightly larger than the face
            y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
            x0, x1 = x, x + w

検出された顔がエッジに近すぎる場合に備えて、チェックを導入します。

step_4_dog_mask.py
            # give up if the cropped frame would be out-of-bounds
            if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
                continue

最後に、マスク付きの顔を画像に挿入します。

step_4_dog_mask.py
            # apply mask
            frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

スクリプトが次のようになっていることを確認します。

step_4_dog_mask.py
"""Real-time dog filter

Move your face around and a dog filter will be applied to your face if it is not out-of-bounds. With the test frame in focus, hit `q` to exit. Note that typing `q` into your terminal will do nothing.
"""

import numpy as np
import cv2


def apply_mask(face: np.array, mask: np.array) -> np.array:
    """Add the mask to the provided face, and return the face with mask."""
    mask_h, mask_w, _ = mask.shape
    face_h, face_w, _ = face.shape

    # Resize the mask to fit on face
    factor = min(face_h / mask_h, face_w / mask_w)
    new_mask_w = int(factor * mask_w)
    new_mask_h = int(factor * mask_h)
    new_mask_shape = (new_mask_w, new_mask_h)
    resized_mask = cv2.resize(mask, new_mask_shape)

    # Add mask to face - ensure mask is centered
    face_with_mask = face.copy()
    non_white_pixels = (resized_mask < 250).all(axis=2)
    off_h = int((face_h - new_mask_h) / 2)
    off_w = int((face_w - new_mask_w) / 2)
    face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
         resized_mask[non_white_pixels]

    return face_with_mask

def main():
    cap = cv2.VideoCapture(0)

    # load mask
    mask = cv2.imread('assets/dog.png')

    # initialize front face classifier
    cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

    while(True):
        # Capture frame-by-frame
        ret, frame = cap.read()
        frame_h, frame_w, _ = frame.shape

        # Convert to black-and-white
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        blackwhite = cv2.equalizeHist(gray)

        # Detect faces
        rects = cascade.detectMultiScale(
            blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
            flags=cv2.CASCADE_SCALE_IMAGE)

        # Add mask to faces
        for x, y, w, h in rects:
            # crop a frame slightly larger than the face
            y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
            x0, x1 = x, x + w

            # give up if the cropped frame would be out-of-bounds
            if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
                continue

            # apply mask
            frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

        # Display the resulting frame
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    # When everything done, release the capture
    cap.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

ファイルを保存して、エディターを終了します。 次に、スクリプトを実行します。

  1. python step_4_dog_mask.py

これで、リアルタイムのドッグフィルターが実行されます。 このスクリプトは、写真内の複数の顔でも機能するため、友達を集めて犬の自動化を行うことができます。

これで、Snapchat風の犬用フィルターを作成するというこのチュートリアルの最初の主な目的は終わりです。 次に、顔の表情を使用して、顔に適用される犬のマスクを決定しましょう。

ステップ5—最小二乗法を使用して基本的な顔の感情分類器を構築する

このセクションでは、表示された感情に基づいてさまざまなマスクを適用する感情分類子を作成します。 微笑むと、フィルターはコーギーマスクを適用します。 眉をひそめると、パグマスクが適用されます。 その過程で、機械学習の概念を理解して議論するための基本となる最小二乗フレームワークについて説明します。

データを処理して予測を生成する方法を理解するために、最初に機械学習モデルについて簡単に説明します。

検討するモデルごとに2つの質問をする必要があります。 今のところ、モデルを区別するには、次の2つの質問で十分です。

  1. 入力:モデルはどのような情報を提供しますか?
  2. 出力:モデルは何を予測しようとしていますか?

大まかに言えば、目標は感情分類のモデルを開発することです。 モデルは次のとおりです。

  1. 入力:顔の画像を指定します。
  2. 出力:対応する感情を予測します。
model: face -> emotion

使用するアプローチは最小二乗です。 一連のポイントを取得し、最適なラインを見つけます。 次の画像に示す最適な線は、私たちのモデルです。

私たちのラインの入力と出力を考えてみましょう。

  1. 入力:与えられた x 座標。
  2. 出力:対応する$y$座標を予測します。
least squares line: x -> y

私たちの入力 x 顔と出力を表す必要があります y 感情の分類に最小二乗法を使用するには、感情を表す必要があります。

  • x -> faceone番号を使用する代わりに x、次の値のvectorを使用します x. したがって、 x 顔の画像を表すことができます。 記事通常の最小二乗は、次の値のベクトルを使用できる理由を説明しています。 x.
  • y -> emotion:それぞれの感情は数字に対応します。 たとえば、「怒っている」は0、「悲しい」は1、「幸せ」は2です。 この上、 y 感情を表すことができます。 ただし、この行はではなくで、 y 値0、1、および2。 可能なy値は無限にあり、1.2、3.5、または10003.42の可能性があります。 それらをどのように翻訳しますか y クラスに対応する整数への値? 詳細と説明については、記事 One-HotEncodingを参照してください。

この背景知識を身に付けて、ベクトル化された画像とワンホットエンコードされたラベルを使用して、単純な最小二乗分類器を構築します。 これは、次の3つのステップで実行できます。

  1. データの前処理:このセクションの冒頭で説明したように、サンプルはベクトルであり、各ベクトルは顔の画像をエンコードします。 ラベルは感情に対応する整数であり、これらのラベルにワンホットエンコーディングを適用します。
  2. モデルの指定とトレーニング:閉形式の最小二乗解を使用し、 w^*.
  3. モデルを使用して予測を実行します。 Xw^* 予測された感情を取得します。

始めましょう。

まず、データを格納するディレクトリを設定します。

  1. mkdir data

次に、Pierre-LucCarrierとAaronCourvilleによってキュレーションされたデータをKaggleでの2013Face EmotionClassificationコンペティションからダウンロードします。

  1. wget -O data/fer2013.tar https://bitbucket.org/alvinwan/adversarial-examples-in-computer-vision-building-then-fooling/raw/babfe4651f89a398c4b3fdbdd6d7a697c5104cff/fer2013.tar

に移動します data ディレクトリを作成し、データを解凍します。

  1. cd data
  2. tar -xzf fer2013.tar

次に、最小二乗モデルを実行するスクリプトを作成します。 プロジェクトのルートに移動します。

  1. cd ~/DogFilter

スクリプト用の新しいファイルを作成します。

  1. nano step_5_ls_simple.py

Pythonボイラープレートを追加し、必要なパッケージをインポートします。

step_5_ls_simple.py
"""Train emotion classifier using least squares."""

import numpy as np

def main():
    pass
    
if __name__ == '__main__':
    main()

次に、データをメモリにロードします。 交換 pass あなたの中で main 次のコードで機能します。

step_5_ls_simple.py

    # load data
    with np.load('data/fer2013_train.npz') as data:
        X_train, Y_train = data['X'], data['Y']

    with np.load('data/fer2013_test.npz') as data:
        X_test, Y_test = data['X'], data['Y']

次に、ラベルをワンホットエンコードします。 これを行うには、で単位行列を作成します numpy 次に、ラベルのリストを使用してこのマトリックスにインデックスを付けます。

step_5_ls_simple.py
    # one-hot labels
    I = np.eye(6)
    Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]

ここでは、 i-単位行列の5番目の行は、 i-番目のエントリ。 したがって、i番目の行はクラスのラベルのワンホットエンコーディングです i. さらに、 numpyの高度なインデックス作成、ここで [a, b, c, d][[1, 3]] = [b, d].

コンピューティング (X^TX)^{-1} コモディティハードウェアでは時間がかかりすぎる X^TX2304x2304 400万を超える値を持つ行列なので、最初の100個の特徴のみを選択することでこの時間を短縮します。 このコードを追加します:

step_5_ls_simple.py
...
    # select first 100 dimensions
    A_train, A_test = X_train[:, :100], X_test[:, :100]

次に、このコードを追加して、閉じた形式の最小二乗解を評価します。

step_5_ls_simple.py
...
    # train model
    w = np.linalg.inv(A_train.T.dot(A_train)).dot(A_train.T.dot(Y_oh_train))

次に、トレーニングセットと検証セットの評価関数を定義します。 これをあなたの前に置いてください main 関数:

step_5_ls_simple.py
def evaluate(A, Y, w):
    Yhat = np.argmax(A.dot(w), axis=1)
    return np.sum(Yhat == Y) / Y.shape[0]

ラベルを推定するために、各サンプルの内積を取得し、を使用して最大値のインデックスを取得します。 np.argmax. 次に、正しい分類の平均数を計算します。 この最後の数字はあなたの正確さです。

最後に、このコードを最後に追加します main を使用してトレーニングと検証の精度を計算する関数 evaluate あなたが書いたばかりの関数:

step_5_ls_simple.py
    # evaluate model
    ols_train_accuracy = evaluate(A_train, Y_train, w)
    print('(ols) Train Accuracy:', ols_train_accuracy)
    ols_test_accuracy = evaluate(A_test, Y_test, w)
    print('(ols) Test Accuracy:', ols_test_accuracy)

スクリプトが以下と一致することを再確認してください。

step_5_ls_simple.py
"""Train emotion classifier using least squares."""

import numpy as np


def evaluate(A, Y, w):
    Yhat = np.argmax(A.dot(w), axis=1)
    return np.sum(Yhat == Y) / Y.shape[0]

def main():

    # load data
    with np.load('data/fer2013_train.npz') as data:
        X_train, Y_train = data['X'], data['Y']

    with np.load('data/fer2013_test.npz') as data:
        X_test, Y_test = data['X'], data['Y']

    # one-hot labels
    I = np.eye(6)
    Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]

    # select first 100 dimensions
    A_train, A_test = X_train[:, :100], X_test[:, :100]

    # train model
    w = np.linalg.inv(A_train.T.dot(A_train)).dot(A_train.T.dot(Y_oh_train))

    # evaluate model
    ols_train_accuracy = evaluate(A_train, Y_train, w)
    print('(ols) Train Accuracy:', ols_train_accuracy)
    ols_test_accuracy = evaluate(A_test, Y_test, w)
    print('(ols) Test Accuracy:', ols_test_accuracy)


if __name__ == '__main__':
    main()

ファイルを保存し、エディターを終了して、Pythonスクリプトを実行します。

  1. python step_5_ls_simple.py

次の出力が表示されます。

Output
(ols) Train Accuracy: 0.4748918316507146 (ols) Test Accuracy: 0.45280545359202934

私たちのモデルは47.5%の列車精度を提供します。 検証セットでこれを繰り返して、45.3%の精度を取得します。 3者間分類の問題の場合、45.3%は推測をかなり上回り、33%です。 これは感情検出の最初の分類子であり、次のステップでは、この最小二乗モデルを基にして精度を向上させます。 精度が高いほど、感情ベースの犬フィルターは、検出された各感情に適切な犬フィルターをより確実に見つけることができます。

ステップ6—入力を特徴づけることによる精度の向上

より表現力豊かなモデルを使用して、精度を高めることができます。 これを実現するために、入力をfeaturizeします。

元の画像はその位置を示しています(0, 0) 赤い、 (1, 0)は茶色などです。 特徴的な画像は、画像の左上に犬がいること、中央に人がいることなどを示している可能性があります。 機能化は強力ですが、その正確な定義はこのチュートリアルの範囲を超えています。

ランダムガウス行列を使用して、動径基底関数(RBF)カーネルに近似を使用します。 このチュートリアルでは詳しく説明しません。 代わりに、これを高次の特徴を計算するブラックボックスとして扱います。

前の手順で中断したところから続行します。 前のスクリプトをコピーして、適切な開始点を用意します。

  1. cp step_5_ls_simple.py step_6_ls_simple.py

エディターで新しいファイルを開きます。

  1. nano step_6_ls_simple.py

まず、特徴的なランダム行列を作成します。 繰り返しになりますが、新しい機能スペースでは100個の機能のみを使用します。

次の行を見つけて、次のように定義します A_trainA_test:

step_6_ls_simple.py
    # select first 100 dimensions
    A_train, A_test = X_train[:, :100], X_test[:, :100]

この定義のすぐ上 A_trainA_test、ランダムな特徴行列を追加します。

step_6_ls_simple.py
    d = 100
    W = np.random.normal(size=(X_train.shape[1], d))
    # select first 100 dimensions
    A_train, A_test = X_train[:, :100], X_test[:, :100]  ...

次に、の定義を置き換えます A_trainA_test. このランダムな特徴を使用して、design行列と呼ばれる行列を再定義します。

step_6_ls_simple.py
    A_train, A_test = X_train.dot(W), X_test.dot(W)

ファイルを保存して、スクリプトを実行します。

  1. python step_6_ls_simple.py

次の出力が表示されます。

Output
(ols) Train Accuracy: 0.584174642717 (ols) Test Accuracy: 0.584425799685

この機能化により、58.4%のトレイン精度と58.4%の検証精度が提供され、検証結果が13.1%向上します。 X行列を次のようにトリミングしました 100 x 100、しかし100の選択は任意でした。 トリミングすることもできます X 行列になる 1000 x 1000 また 50 x 50. の次元を言う xd x d. より多くの値をテストできます d Xを再トリミングして d x d 新しいモデルを再計算します。

より多くの値を試す d、61.7%へのテスト精度の追加の4.3% i改善が見つかりました。 次の図では、変化するときの新しい分類器のパフォーマンスを考慮しています。 d. 直感的に、 d 元のデータをどんどん使用するので、精度が上がるはずです。 ただし、バラ色の絵を描くのではなく、グラフは負の傾向を示しています。

より多くのデータを保持するにつれて、トレーニングと検証の精度の間のギャップも大きくなります。 これは、過剰適合の明らかな証拠です。ここで、モデルは、すべてのデータに一般化できなくなった表現を学習しています。 過剰適合と戦うために、複雑なモデルにペナルティを課すことにより、モデルを正規化します。

通常の最小二乗目的関数を正則化項で修正し、新しい目的を与えます。 新しい目的関数はリッジ回帰と呼ばれ、次のようになります。

min_w |Aw- y|^2 + lambda |w|^2

この方程式では、 lambda 調整可能なハイパーパラメータです。 プラグ lambda = 0 方程式に代入すると、リッジ回帰は最小二乗になります。 プラグ lambda = infinity 方程式に入れれば、あなたは最高のものを見つけるでしょう w ゼロ以外の場合と同様に、ゼロでなければなりません w 無限の損失が発生します。 結局のところ、この目的は閉じた形の解ももたらします。

w^* = (A^TA + lambda I)^{-1}A^Ty

引き続き機能化されたサンプルを使用して、モデルを再トレーニングして再評価します。

開ける step_6_ls_simple.py 再びあなたのエディタで:

  1. nano step_6_ls_simple.py

今回は、新しい特徴空間の次元を次のように増やします。 d=1000​. の値を変更します d から 1001000 次のコードブロックに示すように:

step_6_ls_simple.py
...
    d = 1000
    W = np.random.normal(size=(X_train.shape[1], d))
...

次に、の正則化を使用してリッジ回帰を適用します lambda = 10^{10}. 定義する行を置き換えます w 次の2行で:

step_6_ls_simple.py
...
    # train model
    I = np.eye(A_train.shape[1])
    w = np.linalg.inv(A_train.T.dot(A_train) + 1e10 * I).dot(A_train.T.dot(Y_oh_train))

次に、このブロックを見つけます。

step_6_ls_simple.py
...
  ols_train_accuracy = evaluate(A_train, Y_train, w)
  print('(ols) Train Accuracy:', ols_train_accuracy)
  ols_test_accuracy = evaluate(A_test, Y_test, w)
  print('(ols) Test Accuracy:', ols_test_accuracy)

次のように置き換えます。

step_6_ls_simple.py
...

  print('(ridge) Train Accuracy:', evaluate(A_train, Y_train, w))
  print('(ridge) Test Accuracy:', evaluate(A_test, Y_test, w))

完成したスクリプトは次のようになります。

step_6_ls_simple.py
"""Train emotion classifier using least squares."""

import numpy as np

def evaluate(A, Y, w):
    Yhat = np.argmax(A.dot(w), axis=1)
    return np.sum(Yhat == Y) / Y.shape[0]

def main():
    # load data
    with np.load('data/fer2013_train.npz') as data:
        X_train, Y_train = data['X'], data['Y']

    with np.load('data/fer2013_test.npz') as data:
        X_test, Y_test = data['X'], data['Y']

    # one-hot labels
    I = np.eye(6)
    Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]
    d = 1000
    W = np.random.normal(size=(X_train.shape[1], d))
    # select first 100 dimensions
    A_train, A_test = X_train.dot(W), X_test.dot(W)

    # train model
    I = np.eye(A_train.shape[1])
    w = np.linalg.inv(A_train.T.dot(A_train) + 1e10 * I).dot(A_train.T.dot(Y_oh_train))

    # evaluate model
    print('(ridge) Train Accuracy:', evaluate(A_train, Y_train, w))
    print('(ridge) Test Accuracy:', evaluate(A_test, Y_test, w))

if __name__ == '__main__':
    main()

ファイルを保存し、エディターを終了して、スクリプトを実行します。

  1. python step_6_ls_simple.py

次の出力が表示されます。

Output
(ridge) Train Accuracy: 0.651173462698 (ridge) Test Accuracy: 0.622181436812

列車の精度が65.1%に低下するため、検証精度が0.4%向上して62.2%になります。 もう一度、いくつかの異なるものを再評価します d、リッジ回帰のトレーニングと検証の精度の間に小さなギャップが見られます。 言い換えれば、リッジ回帰は過剰適合が少ないということです。

これらの追加の拡張機能を備えた最小二乗法のベースラインパフォーマンスは、適度に良好に機能します。 トレーニングと推論の時間はすべて合わせて、最高の結果を得るのに20秒もかかりません。 次のセクションでは、さらに複雑なモデルについて説明します。

ステップ7—PyTorchで畳み込みニューラルネットワークを使用して顔感情分類器を構築する

このセクションでは、最小二乗法の代わりにニューラルネットワークを使用して2番目の感情分類器を作成します。 繰り返しになりますが、私たちの目標は、顔を入力として受け入れ、感情を出力するモデルを作成することです。 最終的に、この分類子は、適用する犬のマスクを決定します。

ニューラルネットワークの簡単な視覚化と紹介については、ニューラルネットワークについての記事を参照してください。 ここでは、PyTorchというディープラーニングライブラリを使用します。 広く使用されているディープラーニングライブラリは多数あり、それぞれにさまざまな長所と短所があります。 PyTorchは、開始するのに特に適した場所です。 このニューラルネットワーク分類器を暗示するために、最小二乗分類器で行ったように、再び3つのステップを実行します。

  1. データを前処理します。ワンホットエンコーディングを適用してから、PyTorch抽象化を適用します。
  2. モデルの指定とトレーニング:PyTorchレイヤーを使用してニューラルネットワークを設定します。 最適化ハイパーパラメータを定義し、確率的勾配降下法を実行します。
  3. モデルを使用して予測を実行します。ニューラルネットワークを評価します。

名前の付いた新しいファイルを作成します step_7_fer_simple.py

  1. nano step_7_fer_simple.py

必要なユーティリティをインポートし、データを保持するPythonクラスを作成します。 ここでのデータ処理では、トレインとテストのデータセットを作成します。 これらを行うには、PyTorchを実装します Dataset 顔感情認識データセット用のPyTorchの組み込みデータパイプラインをロードして使用できるようにするインターフェイス:

step_7_fer_simple.py
from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
    """Face Emotion Recognition dataset.
    
    Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
    and Aaron Courville in 2013.
    
    Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
    """
    pass

を削除します pass のプレースホルダー Fer2013Dataset クラス。 その代わりに、データホルダーを初期化する関数を追加します。

step_7_fer_simple.py
    def __init__(self, path: str):
        """
        Args:
            path: Path to `.np` file containing sample nxd and label nx1
        """
        with np.load(path) as data:
            self._samples = data['X']
            self._labels = data['Y']
        self._samples = self._samples.reshape((-1, 1, 48, 48))

        self.X = Variable(torch.from_numpy(self._samples)).float()
        self.Y = Variable(torch.from_numpy(self._labels)).float()
...

この関数は、サンプルとラベルをロードすることから始まります。 次に、データをPyTorchデータ構造でラップします。

直後 __init__ 関数、追加 __len__ これは、を実装するために必要であるため、 Dataset インターフェイスPyTorchは次のことを期待しています。

step_7_fer_simple.py
...
    def __len__(self):
        return len(self._labels)

最後に、 __getitem__ サンプルとラベルを含むdictionaryを返すメソッド。

step_7_fer_simple.py
    def __getitem__(self, idx):
        return {'image': self._samples[idx], 'label': self._labels[idx]}

ファイルが次のようになっていることを再確認してください。

step_7_fer_simple.py
from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
    """Face Emotion Recognition dataset.
    Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
    and Aaron Courville in 2013.
    Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
    """

    def __init__(self, path: str):
        """
        Args:
            path: Path to `.np` file containing sample nxd and label nx1
        """
        with np.load(path) as data:
            self._samples = data['X']
            self._labels = data['Y']
        self._samples = self._samples.reshape((-1, 1, 48, 48))

        self.X = Variable(torch.from_numpy(self._samples)).float()
        self.Y = Variable(torch.from_numpy(self._labels)).float()

    def __len__(self):
        return len(self._labels)

    def __getitem__(self, idx):
        return {'image': self._samples[idx], 'label': self._labels[idx]}

次に、 Fer2013Dataset データセット。 次のコードをファイルの最後に追加します。 Fer2013Dataset クラス:

step_7_fer_simple.py
trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)

このコードは、を使用してデータセットを初期化します Fer2013Dataset 作成したクラス。 次に、トレインセットと検証セットの場合、データセットを DataLoader. これにより、データセットが後で使用できる反復可能に変換されます。

健全性チェックとして、データセットユーティリティが機能していることを確認します。 を使用してサンプルデータセットローダーを作成します DataLoader そのローダーの最初の要素を印刷します。 ファイルの最後に以下を追加します。

step_7_fer_simple.py
if __name__ == '__main__':
    loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False)
    print(next(iter(loader)))

完成したスクリプトが次のようになっていることを確認します。

step_7_fer_simple.py
from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
    """Face Emotion Recognition dataset.
    Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
    and Aaron Courville in 2013.
    Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
    """

    def __init__(self, path: str):
        """
        Args:
            path: Path to `.np` file containing sample nxd and label nx1
        """
        with np.load(path) as data:
            self._samples = data['X']
            self._labels = data['Y']
        self._samples = self._samples.reshape((-1, 1, 48, 48))

        self.X = Variable(torch.from_numpy(self._samples)).float()
        self.Y = Variable(torch.from_numpy(self._labels)).float()

    def __len__(self):
        return len(self._labels)

    def __getitem__(self, idx):
        return {'image': self._samples[idx], 'label': self._labels[idx]}
      
trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)

if __name__ == '__main__':
    loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False)
    print(next(iter(loader)))

エディターを終了して、スクリプトを実行します。

  1. python step_7_fer_simple.py

これにより、次のテンソルのペアが出力されます。 データパイプラインは、2つのサンプルと2つのラベルを出力します。 これは、データパイプラインが稼働し、準備ができていることを示しています。

Output
{'image': (0 ,0 ,.,.) = 24 32 36 ... 173 172 173 25 34 29 ... 173 172 173 26 29 25 ... 172 172 174 ... ⋱ ... 159 185 157 ... 157 156 153 136 157 187 ... 152 152 150 145 130 161 ... 142 143 142 ⋮ (1 ,0 ,.,.) = 20 17 19 ... 187 176 162 22 17 17 ... 195 180 171 17 17 18 ... 203 193 175 ... ⋱ ... 1 1 1 ... 106 115 119 2 2 1 ... 103 111 119 2 2 2 ... 99 107 118 [torch.LongTensor of size 2x1x48x48] , 'label': 1 1 [torch.LongTensor of size 2] }

データパイプラインが機能することを確認したので、に戻ります。 step_7_fer_simple.py ニューラルネットワークとオプティマイザーを追加します。 開ける step_7_fer_simple.py.

  1. nano step_7_fer_simple.py

まず、前の反復で追加した最後の3行を削除します。

step_7_fer_simple.py
# Delete all three lines
if __name__ == '__main__':
    loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False)
    print(next(iter(loader)))

その代わりに、3つの畳み込み層とそれに続く3つの完全に接続された層を含むPyTorchニューラルネットワークを定義します。 これを既存のスクリプトの最後に追加します。

step_7_fer_simple.py
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 6, 3)
        self.conv3 = nn.Conv2d(6, 16, 3)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 48)
        self.fc3 = nn.Linear(48, 3)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

次に、ニューラルネットワークを初期化し、損失関数を定義し、スクリプトの最後に次のコードを追加して最適化ハイパーパラメーターを定義します。

step_7_fer_simple.py
net = Net().float()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

2つのエポックのトレーニングを行います。 今のところ、エポックは、すべてのトレーニングサンプルが1回だけ使用されたトレーニングの反復であると定義しています。

まず、抽出します imagelabel データセットローダーから、それぞれをPyTorchでラップします Variable. 次に、フォワードパスを実行してから、損失とニューラルネットワークを逆伝播します。 これを行うには、スクリプトの最後に次のコードを追加します。

step_7_fer_simple.py
for epoch in range(2):  # loop over the dataset multiple times

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs = Variable(data['image'].float())
        labels = Variable(data['label'].long())
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.data[0]
        if i % 100 == 0:
            print('[%d, %5d] loss: %.3f' % (epoch, i, running_loss / (i + 1)))

スクリプトは次のようになります。

step_7_fer_simple.py
from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
    """Face Emotion Recognition dataset.

    Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
    and Aaron Courville in 2013.

    Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
    """
    def __init__(self, path: str):
        """
        Args:
            path: Path to `.np` file containing sample nxd and label nx1
        """
        with np.load(path) as data:
            self._samples = data['X']
            self._labels = data['Y']
        self._samples = self._samples.reshape((-1, 1, 48, 48))

        self.X = Variable(torch.from_numpy(self._samples)).float()
        self.Y = Variable(torch.from_numpy(self._labels)).float()

    def __len__(self):
        return len(self._labels)


    def __getitem__(self, idx):
        return {'image': self._samples[idx], 'label': self._labels[idx]}


trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 6, 3)
        self.conv3 = nn.Conv2d(6, 16, 3)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 48)
        self.fc3 = nn.Linear(48, 3)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net().float()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)


for epoch in range(2):  # loop over the dataset multiple times

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs = Variable(data['image'].float())
        labels = Variable(data['label'].long())
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.data[0]
        if i % 100 == 0:
            print('[%d, %5d] loss: %.3f' % (epoch, i, running_loss / (i + 1)))

ファイルを保存し、コードを確認したらエディターを終了します。 次に、この概念実証トレーニングを開始します。

  1. python step_7_fer_simple.py

ニューラルネットワークがトレーニングすると、次のような出力が表示されます。

Output
[0, 0] loss: 1.094 [0, 100] loss: 1.049 [0, 200] loss: 1.009 [0, 300] loss: 0.963 [0, 400] loss: 0.935 [1, 0] loss: 0.760 [1, 100] loss: 0.768 [1, 200] loss: 0.775 [1, 300] loss: 0.776 [1, 400] loss: 0.767

次に、他の多くのPyTorchユーティリティを使用してこのスクリプトを拡張し、モデルの保存と読み込み、トレーニングと検証の精度の出力、学習率のスケジュールの微調整などを行うことができます。 学習率0.01、運動量0.9で20エポックのトレーニングを行った後、ニューラルネットワークは87.9%のトレーニング精度と75.5%の検証精度を達成し、これまでで最も成功した最小二乗アプローチの66.6%よりもさらに6.8%向上しました。 。 これらの追加のベルとホイッスルを新しいスクリプトに含めます。

ライブカメラフィードが使用する最終的な顔の感情検出器を保持する新しいファイルを作成します。 このスクリプトには、上記のコードと、コマンドラインインターフェイス、および後で使用するコードのインポートが簡単なバージョンが含まれています。 さらに、より精度の高いモデル用に事前に調整されたハイパーパラメータが含まれています。

  1. nano step_7_fer.py

次のインポートから始めます。 これは以前のファイルと一致しますが、OpenCVが追加で含まれています import cv2.

step_7_fer.py
from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse

これらのインポートのすぐ下で、コードを再利用します。 step_7_fer_simple.py ニューラルネットワークを定義するには:

step_7_fer.py
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 6, 3)
        self.conv3 = nn.Conv2d(6, 16, 3)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 48)
        self.fc3 = nn.Linear(48, 3)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

繰り返しますが、からの顔感情認識データセットのコードを再利用します step_7_fer_simple.py そしてそれをこのファイルに追加します:

step_7_fer.py
class Fer2013Dataset(Dataset):
    """Face Emotion Recognition dataset.
    Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
    and Aaron Courville in 2013.
    Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
    """

    def __init__(self, path: str):
        """
        Args:
            path: Path to `.np` file containing sample nxd and label nx1
        """
        with np.load(path) as data:
            self._samples = data['X']
            self._labels = data['Y']
        self._samples = self._samples.reshape((-1, 1, 48, 48))

        self.X = Variable(torch.from_numpy(self._samples)).float()
        self.Y = Variable(torch.from_numpy(self._labels)).float()

    def __len__(self):
        return len(self._labels)

    def __getitem__(self, idx):
        return {'image': self._samples[idx], 'label': self._labels[idx]}

次に、ニューラルネットワークのパフォーマンスを評価するためのいくつかのユーティリティを定義します。 まず、を追加します evaluate ニューラルネットワークの予測された感情を単一の画像の真の感情と比較する関数:

step_7_fer.py
def evaluate(outputs: Variable, labels: Variable, normalized: bool=True) -> float:
    """Evaluate neural network outputs against non-one-hotted labels."""
    Y = labels.data.numpy()
    Yhat = np.argmax(outputs.data.numpy(), axis=1)
    denom = Y.shape[0] if normalized else 1
    return float(np.sum(Yhat == Y) / denom)

次に、という関数を追加します batch_evaluate これは、最初の関数をすべての画像に適用します。

step_7_fer.py
def batch_evaluate(net: Net, dataset: Dataset, batch_size: int=500) -> float:
    """Evaluate neural network in batches, if dataset is too large."""
    score = 0.0
    n = dataset.X.shape[0]
    for i in range(0, n, batch_size):
        x = dataset.X[i: i + batch_size]
        y = dataset.Y[i: i + batch_size]
        score += evaluate(net(x), y, False)
    return score / n

ここで、という関数を定義します get_image_to_emotion_predictor これは、事前にトレーニングされたモデルを使用して、画像を取り込み、予測された感情を出力します。

step_7_fer.py
def get_image_to_emotion_predictor(model_path='assets/model_best.pth'):
    """Returns predictor, from image to emotion index."""
    net = Net().float()
    pretrained_model = torch.load(model_path)
    net.load_state_dict(pretrained_model['state_dict'])

    def predictor(image: np.array):
        """Translates images into emotion indices."""
        if image.shape[2] > 1:
            image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        frame = cv2.resize(image, (48, 48)).reshape((1, 1, 48, 48))
        X = Variable(torch.from_numpy(frame)).float()
        return np.argmax(net(X).data.numpy(), axis=1)[0]
    return predictor

最後に、次のコードを追加して、 main 他のユーティリティを活用する機能:

step_7_fer.py
def main():
    trainset = Fer2013Dataset('data/fer2013_train.npz')
    testset = Fer2013Dataset('data/fer2013_test.npz')
    net = Net().float()

    pretrained_model = torch.load("assets/model_best.pth")
    net.load_state_dict(pretrained_model['state_dict'])

    train_acc = batch_evaluate(net, trainset, batch_size=500)
    print('Training accuracy: %.3f' % train_acc)
    test_acc = batch_evaluate(net, testset, batch_size=500)
    print('Validation accuracy: %.3f' % test_acc)


if __name__ == '__main__':
    main()

これにより、事前にトレーニングされたニューラルネットワークが読み込まれ、提供された顔の感情認識データセットでそのパフォーマンスが評価されます。 具体的には、スクリプトは、トレーニングに使用した画像と、テスト用に取っておいた別の画像セットの精度を出力します。

ファイルが以下と一致することを再確認してください。

step_7_fer.py
from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 6, 3)
        self.conv3 = nn.Conv2d(6, 16, 3)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 48)
        self.fc3 = nn.Linear(48, 3)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
 

class Fer2013Dataset(Dataset):
    """Face Emotion Recognition dataset.
    Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
    and Aaron Courville in 2013.
    Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
    """

    def __init__(self, path: str):
        """
        Args:
            path: Path to `.np` file containing sample nxd and label nx1
        """
        with np.load(path) as data:
            self._samples = data['X']
            self._labels = data['Y']
        self._samples = self._samples.reshape((-1, 1, 48, 48))

        self.X = Variable(torch.from_numpy(self._samples)).float()
        self.Y = Variable(torch.from_numpy(self._labels)).float()

    def __len__(self):
        return len(self._labels)

    def __getitem__(self, idx):
        return {'image': self._samples[idx], 'label': self._labels[idx]}
      

def evaluate(outputs: Variable, labels: Variable, normalized: bool=True) -> float:
    """Evaluate neural network outputs against non-one-hotted labels."""
    Y = labels.data.numpy()
    Yhat = np.argmax(outputs.data.numpy(), axis=1)
    denom = Y.shape[0] if normalized else 1
    return float(np.sum(Yhat == Y) / denom)
  

def batch_evaluate(net: Net, dataset: Dataset, batch_size: int=500) -> float:
    """Evaluate neural network in batches, if dataset is too large."""
    score = 0.0
    n = dataset.X.shape[0]
    for i in range(0, n, batch_size):
        x = dataset.X[i: i + batch_size]
        y = dataset.Y[i: i + batch_size]
        score += evaluate(net(x), y, False)
    return score / n

  
def get_image_to_emotion_predictor(model_path='assets/model_best.pth'):
    """Returns predictor, from image to emotion index."""
    net = Net().float()
    pretrained_model = torch.load(model_path)
    net.load_state_dict(pretrained_model['state_dict'])

    def predictor(image: np.array):
        """Translates images into emotion indices."""
        if image.shape[2] > 1:
            image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        frame = cv2.resize(image, (48, 48)).reshape((1, 1, 48, 48))
        X = Variable(torch.from_numpy(frame)).float()
        return np.argmax(net(X).data.numpy(), axis=1)[0]
    return predictor
  
  
def main():
    trainset = Fer2013Dataset('data/fer2013_train.npz')
    testset = Fer2013Dataset('data/fer2013_test.npz')
    net = Net().float()

    pretrained_model = torch.load("assets/model_best.pth")
    net.load_state_dict(pretrained_model['state_dict'])

    train_acc = batch_evaluate(net, trainset, batch_size=500)
    print('Training accuracy: %.3f' % train_acc)
    test_acc = batch_evaluate(net, testset, batch_size=500)
    print('Validation accuracy: %.3f' % test_acc)


if __name__ == '__main__':
    main(

ファイルを保存して、エディターを終了します。

以前と同様に、顔検出器を使用して、事前にトレーニングされたモデルパラメータをダウンロードし、 assets 次のコマンドを使用してフォルダを作成します。

  1. wget -O assets/model_best.pth https://github.com/alvinwan/emotion-based-dog-filter/raw/master/src/assets/model_best.pth

スクリプトを実行して、事前にトレーニングされたモデルを使用および評価します。

  1. python step_7_fer.py

これにより、次のように出力されます。

Output
Training accuracy: 0.879 Validation accuracy: 0.755

この時点で、かなり正確な顔の感情の分類子を作成しました。 本質的に、私たちのモデルは、10回のうち8回、幸せ、悲しみ、驚きの顔を正しく明確にすることができます。 これはかなり良いモデルなので、この顔感情分類子を使用して、顔に適用する犬のマスクを決定することができます。

ステップ8—感情ベースの犬のフィルターを完成させる

新しい顔感情分類器を統合する前に、動物用マスクを選択する必要があります。 DalmationマスクとSheepdogマスクを使用します。

これらのコマンドを実行して、両方のマスクを assets フォルダ:

  1. wget -O assets/dalmation.png https://assets.digitalocean.com/articles/python3_dogfilter/E9ax7PI.png # dalmation
  2. wget -O assets/sheepdog.png https://assets.digitalocean.com/articles/python3_dogfilter/HveFdkg.png # sheepdog

次に、フィルターでマスクを使用しましょう。 を複製することから始めます step_4_dog_mask.py ファイル:

  1. cp step_4_dog_mask.py step_8_dog_emotion_mask.py

新しいPythonスクリプトを開きます。

  1. nano step_8_dog_emotion_mask.py

スクリプトの上部に新しい行を挿入して、感情予測子をインポートします。

step_8_dog_emotion_mask.py
from step_7_fer import get_image_to_emotion_predictor
...

次に、 main() 関数、この行を見つけます:

step_8_dog_emotion_mask.py
    mask = cv2.imread('assets/dog.png')

これを次のように置き換えて、新しいマスクをロードし、すべてのマスクをタプルに集約します。

step_8_dog_emotion_mask.py
    mask0 = cv2.imread('assets/dog.png')
    mask1 = cv2.imread('assets/dalmation.png')
    mask2 = cv2.imread('assets/sheepdog.png')
    masks = (mask0, mask1, mask2)

改行を追加してから、このコードを追加して感情予測子を作成します。

step_8_dog_emotion_mask.py

    # get emotion predictor
    predictor = get_image_to_emotion_predictor()

君の main 関数は次のように一致するはずです。

step_8_dog_emotion_mask.py
def main():
    cap = cv2.VideoCapture(0)

    # load mask
    mask0 = cv2.imread('assets/dog.png')
    mask1 = cv2.imread('assets/dalmation.png')
    mask2 = cv2.imread('assets/sheepdog.png')
    masks = (mask0, mask1, mask2)

    # get emotion predictor
    predictor = get_image_to_emotion_predictor()

    # initialize front face classifier
    ...

次に、次の行を見つけます。

step_8_dog_emotion_mask.py

            # apply mask
            frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

下に次の行を挿入します # apply mask 予測子を使用して適切なマスクを選択する行:

step_8_dog_emotion_mask.py
            # apply mask
            mask = masks[predictor(frame[y:y+h, x: x+w])]
            frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

完成したファイルは次のようになります。

step_8_dog_emotion_mask.py
"""Test for face detection"""

from step_7_fer import get_image_to_emotion_predictor
import numpy as np
import cv2

def apply_mask(face: np.array, mask: np.array) -> np.array:
    """Add the mask to the provided face, and return the face with mask."""
    mask_h, mask_w, _ = mask.shape
    face_h, face_w, _ = face.shape

    # Resize the mask to fit on face
    factor = min(face_h / mask_h, face_w / mask_w)
    new_mask_w = int(factor * mask_w)
    new_mask_h = int(factor * mask_h)
    new_mask_shape = (new_mask_w, new_mask_h)
    resized_mask = cv2.resize(mask, new_mask_shape)

    # Add mask to face - ensure mask is centered
    face_with_mask = face.copy()
    non_white_pixels = (resized_mask < 250).all(axis=2)
    off_h = int((face_h - new_mask_h) / 2)
    off_w = int((face_w - new_mask_w) / 2)
    face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
         resized_mask[non_white_pixels]

    return face_with_mask

def main():

    cap = cv2.VideoCapture(0)
    # load mask
    mask0 = cv2.imread('assets/dog.png')
    mask1 = cv2.imread('assets/dalmation.png')
    mask2 = cv2.imread('assets/sheepdog.png')
    masks = (mask0, mask1, mask2)

    # get emotion predictor
    predictor = get_image_to_emotion_predictor()

    # initialize front face classifier
    cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

    while True:
        # Capture frame-by-frame
        ret, frame = cap.read()
        frame_h, frame_w, _ = frame.shape

        # Convert to black-and-white
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        blackwhite = cv2.equalizeHist(gray)

        rects = cascade.detectMultiScale(
            blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
            flags=cv2.CASCADE_SCALE_IMAGE)

        for x, y, w, h in rects:
            # crop a frame slightly larger than the face
            y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
            x0, x1 = x, x + w
            # give up if the cropped frame would be out-of-bounds
            if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
                continue
            # apply mask
            mask = masks[predictor(frame[y:y+h, x: x+w])]
            frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

        # Display the resulting frame
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

エディターを保存して終了します。 次に、スクリプトを起動します。

  1. python step_8_dog_emotion_mask.py

今それを試してみてください! 笑顔は「幸せ」として登録され、元の犬を表示します。 中立的な顔やしかめっ面は「悲しい」として登録され、ダルメーションを生み出します。 素敵な大きなあごのドロップを伴う「サプライズ」の顔は、牧羊犬を生み出します。

これで、感情ベースの犬のフィルターとコンピュータービジョンへの進出は終わりです。

結論

このチュートリアルでは、コンピュータービジョンを使用して顔検出器と犬のフィルターを作成し、機械学習モデルを使用して、検出された感情に基づいてマスクを適用しました。

機械学習は広く適用できます。 ただし、機械学習を適用する際に各アプリケーションの倫理的影響を考慮するのは、実践者の責任です。 このチュートリアルで作成したアプリケーションは楽しい演習でしたが、モデルをトレーニングするために独自のデータを提供するのではなく、OpenCVと既存のデータセットを使用して顔を識別したことを忘れないでください。 使用されるデータとモデルは、プログラムの動作に大きな影響を与えます。

たとえば、モデルが候補者に関するデータでトレーニングされた求人検索エンジンを想像してみてください。 人種、性別、年齢、文化、第一言語、またはその他の要因など。 そして、おそらく開発者は、スパース性を強制するモデルをトレーニングしました。これにより、機能スペースが、性別が分散の大部分を説明するサブスペースに縮小されます。 その結果、このモデルは、主に性別に基づく候補者の求人検索や企業の選択プロセスにも影響を与えます。 ここで、モデルの解釈が難しく、特定の機能が何に対応するかわからない、より複雑な状況を考えてみましょう。 これについて詳しくは、カリフォルニア大学バークレー校のMoritzHardt教授による機械学習における機会の平等をご覧ください。

機械学習には、圧倒的な不確実性が存在する可能性があります。 このランダム性と複雑さを理解するには、数学的な直感と確率論的思考スキルの両方を身に付ける必要があります。 実践者として、機械学習の理論的基盤を掘り下げるのはあなた次第です。