サイトリニューアル中のお知らせ

【2024】OpenCV×ラズパイで実現!顔認証&顔識別によって漫画キャラの反応を変える

スポンサーリンク

2024-05月実施。

Adafruit Eyes Kitを使った制作の中で、ふと『自分の顔を近づけたら、目の表示が変わるようにしたら面白いのでは?』と思いつきました。
しかし、OpenCVを利用すれば可能と知ったものの、初心者だった私は壁にぶつかります。あらゆる参考サイトを見ても、情報が古いのか自分のラズパイ4では顔認識・顔識別が上手くいきませんでした……。

それでも数か月にわたる試行錯誤の末、ついに2024年の現在で顔識別まで実現可能な解決策を見つけました!自分の顔と他者の顔を識別して目の反応を変える――イケメンには喜び、自分には冷たい反応を向ける(笑)。OpenCVを利用したそんなちょっとユーモラスな製作です」
今回はeyes kitの利用が目的な為、もう一枚のラズパイ4と音声発生用にラズパイピコを利用していますが、顔認証してモーター等を動作させることも可能となりました。

目次
スポンサーリンク

1opencvで顔認証

使用したハード構成
raspberry pi4 4B         Camera Module3

顔認証に用いたハード。
(ラズパイ4とpi3カメラ)
ラズパイ4とpi3カメラ

利用環境
Bullseye 64ビット (参照サイトでは32ビット利用とはあるのだが・・)

ラズパイ4の顔認証に際して
OSはBullseye 64ビットを使用
Bullseye 64ビット

とにかくいろんなサイトを試しまくって分かってきたのが、おそらくラズパイでの利用は構成が重要ではないかと?。osのバージョンと32ビット or 64ビット、
カメラもcamera2は持ってませんが、プログラムがcamera3では動かないケースもあるような気がします。

ラズパイ4とpi3カメラを接続する際、ケーブルの接続方向写真
カメラケーブル接続方向

したがってこのサイトの利用も上記の構成以外ではほぼ動きません。

私が良く分かって無いせいだとは思いますが実際このサイトを利用してもこういう結果となり、最終的にBullseye
64ビットに落ち着きました。

Bookworm仮想環境無し Numpyがインストールエラー
仮想環境有り OpenCV Pythonがインストールエラー
Bullseye 32bitCV2 インポートエラーとなった
Buster 32bitpi3カメラを認識してくれなかった。
環境を変えてテストした結果

今回、基本として利用させて頂いたのは以下のサイトです。
Face Detection on Raspberry Pi 4 using OpenCV and Camera Module
パーツメーカーのCytron様がデモして頂いてました。

こちらに関してはリンク案内みたいなものなので特に特筆すべきことも無いのですが・・動画で説明されてますので注視して以下を抜き出しました。
(字幕もあるが動画と違って字幕には抜けがある

スクロールできます
sudo apt-get update
 
sudo apt-get install python3-opencv
 
sudo apt-get install libqt5gui5 libqt5test5 python3-sip python3-pyqt5
libjasper-dev libatlas-base-dev libhdf5-dev libhdf5-serial-dev -y
 
pip3 install opencv-contrib-python==4.5.5.62
 
pip3 install -U numpy
 
python3
 
import cv2
 
cv2.__version__

尚、64ビットでインストールだと途中

スクロールできます
sudo apt-get install libqt5gui5 libqt5test5 python3-sip python3-pyqt5
libjasper-dev libatlas-base-dev libhdf5-dev libhdf5-serial-dev -y

のときと
pip3 install -U numpy
のインストール時にワーニングが出ますが、顔認証の利用には問題ありません。
32ビットインストールだと出ないんですが、逆に最後のimport
cv2がエラーで動かないんですよね・・・デモのバージョン不明なのですが、一体どのバージョンでやってるんでしょう?。

libjasper-devエラー
libjasper-devエラー
Numpyインストール時にワーニングが出る
Numpyワーニング

あとは、カスケードファイルはサイトのサンプルプログラムの中にある

スクロールできます
https://github.com/opencv/opencv/blob/4.x/data/haarcascades/haarcascade_frontalface_default.xml

より、ダウンロードして、サンプルプログラム内の指定フォルダに配置。

スクロールできます
/home/pi/Face Recognition/haarcascade_frontalface_default.xml
顔認証用OpenCVインストール成功
完成

これで基本的な顔認識までは出来ます。
プログラムはCytron様のサイトをご確認下さい。

顔認証を実行することが出来ました。
顔認証

このサイト以外でも顔認識や、画像変換までなら出来るサイトはありましたが顔識別になると私の力ではどうしてもうまくいきませんでした。
しかしこれをベースにすると顔識別までもなんとか出来ました。

また、カメラの利用に関して
sudo raspi-configのレガシーカメラはオフのままで設定不用。
良くみかけるconfigの変更記述”dtoverlay=imx708”等もしなくてもopencvは動いてます。

スポンサーリンク

2 顔識別

上記により顔認証は取り合えずできましたが、参照サイトは顔認識まで。
あくまでも目標は識別画像によってGPIOからのリアクションを変えること。
知識ゼロからのチカラ押しだけでやってきましたが、さすがに何度も試すと大体理屈はわかってきました。

顔認証の流れ

顔認証の認識の流れをブロック図にしてみる
顔認証

顔識別の流れ

顔識別の認識の流れをブロック図にしてみる
顔識別

独学における認識なので間違ってたらご指摘下さい。
顔認証をベースに、右赤枠の個人を特定する自分の写真と他者の写真を利用した顔識別モデルを作成。それを識別判断するプログラムに変更です。
/home/pi/Face Recognition/detected_faces
トレーニングデータ用フォルダを作成し、自分写真(フォルダ0)と他者写真(フォルダ1)を作成しました。
libcamera-still -o test.jpg
トレーニング画像として自分自身を30枚撮影して、フォルダ0に保存。
pi3カメラで撮影するとワイドになり、無駄な情報が含まれてしまうのでPCでトリミングしてまたラズパイに戻してます。

トレーニング用の自分顔写真データ
トレーニング用に自分のおっさん写真30枚

もう一つは他者
工作の都合上、映画「賭ケグルイ」出演であるイケメン俳優の高杉真宙様の写真をネットからフォルダ1に保存しました。

識別用の他者、顔写真データ
他者のイケメン写真30枚

下のプログラムを利用して、写真から顔識別モデルを作成しました。

顔写真から識別モデル作成
スクロールできます
/home/pi/Face Recognition/detected_faces/face_recognizer_model.xml

detected_faces配下のフォルダはさらに下にフォルダを作っていても全のフォルダを抽出してくれます。
(フォルダ1、フォルダ2、個別に指定しなくても良い)

下記、トレーニングデータ(face_recognizer_model.xml)作成プログラム

スクロールできます
import cv2
import numpy as np
import os  
 
# トレーニング画像のパス
train_images_path = “/home/pi/Face Recognition/detected_faces”
 
# トレーニングデータの準備
images = []
labels = []

# ラベル名を数値にマッピング
label_mapping = {“1”: 0, “2”: 1, “other”: 2}
 
# トレーニング画像の読み込みとラベル付け
for label_folder in os.listdir(train_images_path):
    label = label_mapping.get(label_folder, 2)  #フォルダ名に基づいてラベルを設定
    label_path = os.path.join(train_images_path,label_folder)
    for image_file in os.listdir(label_path):
        image_path = os.path.join(label_path,image_file)
        image = cv2.imread(image_path,cv2.IMREAD_GRAYSCALE)
        images.append(image)
        labels.append(label)  #ラベルを追加する

# 顔識別モデルの作成とトレーニング
face_recognizer = cv2.face.LBPHFaceRecognizer_create()
labels_array = np.array(labels, dtype=np.int32)  #ラベルをNumPy配列に変換
face_recognizer.train(images, labels_array)

 # モデルの保存
face_recognizer.save(“/home/pi/FaceRecognition/detected_faces/face_recognizer_model.xml”)

ラベルという記述がありますが、自分と他者を認識させた時に認識の違いをはっきりさせたい為、
名前を表示させるようにしたいとラベルをつけました。
1:(0 自分) 2:(1 イケメン俳優) other:(2 どちらでも無い /unkown)

スポンサーリンク

上記を元にしたメインプログラムが以下です。

スクロールできます
import cv2 #OpenCVライブラリ。画像処理とコンピュータビジョンに使用されます。
import os #ファイルやディレクトリ操作を行うための標準ライブラリ。
import time #時間操作を行うための標準ライブラリ。
import numpy as np #数値計算ライブラリ。
from picamera2 import Picamera2 #RaspberryPiのカメラモジュール用のライブラリ。
 
# — トレーニング済みの顔認識モデルとHaar Cascade分類器の読み込み —
# トレーニング済みの顔識別モデルのパス
face_recognizer_directory = “/home/pi/FaceRecognition/detected_faces”
face_recognizer_model = os.path.join(face_recognizer_directory,“face_recognizer_model.xml”)

# モデルをロード
face_recognizer = cv2.face.LBPHFaceRecognizer_create(radius=1,neighbors=8, grid_x=8, grid_y=8)
face_recognizer.read(face_recognizer_model)
 
# Haar Cascade分類器をロード
face_detector = cv2.CascadeClassifier(“/home/pi/FaceRecognition/haarcascade_frontalface_default.xml”)
cv2.startWindowThread()
 
#– Picamera2を使ってリアルタイム映像をキャプチャ —
# Piカメラの設定
picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration(main={“format”:‘XRGB8888’, “size”: (640, 480)}))
picam2.start()
 
# — 顔検出を行い、検出された顔部分を切り出し、前処理(リサイズとヒストグラム均等化)を行う —
# 検出された顔画像の保存ディレクトリ
output_directory = “detected_faces”
os.makedirs(output_directory, exist_ok=True)

# ラベル名のマッピング
label_names = {0: “ginten”, 1: “takasugi”, 2: “other”}

def preprocess_face(face):
   
“””画像前処理:リサイズとヒストグラム均等化”””
    face = cv2.resize(face, (200, 200))
    face = cv2.equalizeHist(face)
    return face
 
while True:
    im = picam2.capture_array()
    grey = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    faces = face_detector.detectMultiScale(grey,scaleFactor=1.1, minNeighbors=5, minSize=(50, 50))
 
    for (x, y, w, h) in faces:
        cv2.rectangle(im, (x, y), (x + w, y +h), (0, 255, 0), 2)

        # 顔部分を切り出し
        face_roi = grey[y:y+h, x:x+w]
        preprocessed_face = preprocess_face(face_roi)
 
        # 顔を識別
        label, confidence = face_recognizer.predict(preprocessed_face)
 
        # 認識結果と信頼度をログに表示
        print(f“Detected face: Label={label}, Confidence={confidence})
 
        #信頼度が一定の閾値を超える場合にのみ識別結果を表示
        if confidence < 70:  #閾値を再調整
            label_text = label_names.get(label, “other”)
            cv2.putText(im, f{label_text} ({confidence:.2f})”, (x, y – 10),cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2, cv2.LINE_AA)
        else:
            cv2.putText(im, “Unknown”, (x, y – 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0,255), 2, cv2.LINE_AA)
 
        # 顔画像を保存(デバッグ用)
        timestamp = int(time.time() * 1000)
        filename = os.path.join(output_directory, f“face_{timestamp}.jpg”)
        cv2.imwrite(filename,preprocessed_face)  # 顔部分のみ保存
 
    # — カメラ映像に認識結果をオーバーレイして表示 —
    cv2.imshow(“Camera”, im)
    # — ‘q’キーが押されたらプログラムを終了 —
    if cv2.waitKey(1) == ord(‘q’):
        break

#終了処理
cv2.destroyAllWindows()

上記プログラムをホームフォルダに置いて、Thonnyの実行結果は以下です。
自分はpiカメラを除き込んで、高杉さんはプリントアウトをカメラに写して、又別の人はスマホの画像を利用しました。
部屋の明るさや、顔の角度によって認識精度が大分変りますね。
プリントアウトでは僅かに傾いていたら(トレーニングデータとして利用したテスト画像と全く同じはずなのに)認識しないくらいです。

顔識別実行結果。
自分と他者を識別出来ました。
顔識別実行結果
ginten/takasugi/unkown

unkownは顔がモデルに登録されていないか、認識精度が低いために表示されるものですが、そもそもotherに該当するフォルダは作っていないので、unkownで良いのかな。
自分とそれ以外の他者との区別は出来たので、まずは良しとします。

スポンサーリンク

3 adafruit eyes kitとの組み合わせ

最初に掲げた通り、今回opencvというのをやってみたいと思ったのは
adafuruit eyes kit利用の工作の中で、顔認証と組み合わせたら面白いかな?と思ったから。
なのでいよいよ組み合わせて見ます。
同じ構成で利用する方もいないと思うので、参考という形でeyes kitやPICOのプログラムは省略します。
eyes kitの中で瞳孔を変更するsvgファイルの動作を変更する為、ちょっと工夫しています。
もし、eyes kitをお持ちの方でどうやったの?という方が居ましたらお気軽にご連絡下さい。

ラズパイ4(顔認証用)とラズパイ4(adafruit eyes kit用)とラズパイピコとdfplayer(音声用)の接続チャート
OpenCVとeyes kitとの組み合わせ

概略は、

顔を識別する(opencv ラズパイ)→目の表情が変わる(eyes ラズパイ)→サウンド(PICO)(音声)を出す。

顔を識別した後GPIOに出力しているだけにも拘わらず何故こんな複雑な構成になっているかというと。

以前も書きましたeyes kitの問題点ですが、

〇 eyes kitがHDMI出力のコピーを液晶に表示させている為、サウンドがジャックから出力されない(強制的にHDMI出力のみになる)。

〇 eyeskitが32bitでしか動かない

どうしてもこの辺クリア出来ないので、このような組み合わせになってます。

eyes kitの回路図等は省略してますが、接続は以下です。

ラズパイ4(顔認証用)とラズパイ4(adafruit eyes kit用)とラズパイピコとdfplayer(音声用)の接続図
OpenCVラズパイとeyes kitラズパイの接続

顔を識別した時のラベルの値によって
0:ぎんてん、1:イケメン様、2:その他の人
ぎんてんなら”ブタ”と出力。イケメンなら”合格”と出力。その他は何もしないようにGPIOへの出力を追加しました。

以下は今回の製作におけるeyes kitとの接続によるGPIO修正ですが、上記の組み合わせを変更すればモーター接続等も利用した製作も可能です。

スクロールできます
import cv2 # OpenCVライブラリ。画像処理とコンピュータビジョンに使用されます。
import os  #ファイルやディレクトリ操作を行うための標準ライブラリ。
import time #時間操作を行うための標準ライブラリ。
import numpy as np #数値計算ライブラリ。
from picamera2 import Picamera2 #Raspberry Piのカメラモジュール用のライブラリ。
import RPi.GPIO as GPIO # GPIO制御ライブラリ

# トレーニング済みの顔識別モデルのパス
face_recognizer_directory = “/home/pi/Face Recognition/detected_faces”
face_recognizer_model = os.path.join(face_recognizer_directory, “face_recognizer_model.xml”)

# モデルをロード
face_recognizer = cv2.face.LBPHFaceRecognizer_create(radius=1, neighbors=8, grid_x=8, grid_y=8)
face_recognizer.read(face_recognizer_model)

# Haar Cascade分類器をロード
face_detector = cv2.CascadeClassifier(“/home/pi/Face Recognition/haarcascade_frontalface_default.xml”)
cv2.startWindowThread()

# Piカメラの設定
picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration(main={“format”: ‘XRGB8888’, “size”: (640, 480)}))
picam2.start()

# GPIO設定
GPIO.setmode(GPIO.BCM)  # BCMモードを使用
GPIO.setup(25, GPIO.OUT)  # GPIO25を出力モードに設定 eyes
GPIO.setup(21, GPIO.OUT)  # GPIO21を状態ピンに設定

# 検出された顔画像の保存ディレクトリ
output_directory = “detected_faces”
os.makedirs(output_directory, exist_ok=True)

# ラベル名のマッピング
label_names = {0: “ginten”, 1: “takasugi”, 2: “other”}

def preprocess_face(face):
    “””画像前処理:リサイズとヒストグラム均等化”””
    face = cv2.resize(face, (200, 200))
    face = cv2.equalizeHist(face)
    return face

try:
    while True:
        im = picam2.capture_array()
        grey = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
        faces = face_detector.detectMultiScale(grey, scaleFactor=1.1, minNeighbors=5, minSize=(50, 50))

        for (x, y, w, h) in faces:
            cv2.rectangle(im, (x, y), (x + w, y + h), (0, 255, 0), 2)

            # 顔部分を切り出し
            face_roi = grey[y:y+h, x:x+w]
            preprocessed_face = preprocess_face(face_roi)

            # 顔を識別
            label, confidence = face_recognizer.predict(preprocessed_face)

            # 認識結果と信頼度をログに表示
            print(f“Detected face: Label={label}, Confidence={confidence}”)

            # 信頼度が一定の閾値を超える場合にのみ識別結果を表示
            if confidence < 70:  # 閾値を再調整
                label_text = label_names.get(label, “other”)
                cv2.putText(im, f{label_text} ({confidence:.2f})”, (x, y – 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2, cv2.LINE_AA)
            
                if label == 0: 
                    GPIO.output(25, GPIO.LOW) #eyes kitへLOW出力(制御)
                    GPIO.output(21, GPIO.LOW) #制御信号と合わせて、嫌な目を指示(ブタ)
                       
                elif label == 1:
                    GPIO.output(25, GPIO.LOW) #eyes kitへLOW出力(制御)
                    GPIO.output(21, GPIO.HIGH) #制御信号と合わせて、好いている目を指示(合格)
                    
            else:
                cv2.putText(im, “Unknown”, (x, y – 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2, cv2.LINE_AA)
                GPIO.output(25, GPIO.HIGH)  # 顔が識別されない場合はGPIO25をHIGHに設定

            # 顔画像を保存(デバッグ用)
            timestamp = int(time.time() * 1000)
            filename = os.path.join(output_directory, f“face_{timestamp}.jpg”)
            cv2.imwrite(filename, preprocessed_face)  # 顔部分のみ保存

        cv2.imshow(“Camera”, im)
        if cv2.waitKey(1) == ord(‘q’):
            break

finally:
    #終了処理
    cv2.destroyAllWindows()
    GPIO.cleanup()  # GPIO設定をリセット
    

顔の識別によってeyes kitへGPIO振り分けしたのが以下の箇所です

              if label == 0: 
                    GPIO.output(25, GPIO.LOW) #eyes kitへLOW出力(制御)
                    GPIO.output(21, GPIO.LOW) #制御信号と合わせて、嫌な目を指示(ブタ)
                       
                elif label == 1:
                    GPIO.output(25, GPIO.LOW) #eyes kitへLOW出力(制御)
                    GPIO.output(21, GPIO.HIGH) #制御信号と合わせて、好いている目を指示(合格)

この箇所によってGPIO21のHIGHかLOWがeyeskitのループターンに合っていれば、それに合わせて目の表示が変わります。

目の表情の切り替えの反応が遅いのはeyes kitの表示ループのタイミングに顔認証のタイミングが合わない時があるためです。ここはもっと工夫したいとおもいます。
完成動画とは別に、もう少し面白みのある構成にしてyoutube等でもあげてみたいですね。
open cvって何?っていうところから始めまりましたが、こうやって完成すると思った以上に面白く、顔認証と機械動作を組み合わせるとか、もっと応用が出来そうです。
試しにやってみたOpen CVでしたが、今後も必要となりそうなので自分もここは一つ、きちんと本を読んで勉強することにしました。

スポンサーリンク
よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

目次