CommentOut

機械学習を用いて画像から人の顔を検出するプログラム 機械学習を用いて画像から人の顔を検出するプログラム

機械学習を用いて画像から人の顔を検出するプログラム

公開日:  最終更新日:

今回、画像から人の顔を検出するプログラムを作ってみました。

画像から人の顔を検出するアルゴリズムは、最近流行りの生成AIやディープラーニングとはちょっと違います。
今回は”Haar Cascade”というアルゴリズムを活用します。

Haar Cascadeとは

画像から物体検出を行うために用いられるアルゴリズムです。
画像のピクセルの明るさの差と位置関係から、物体を検知・識別するアルゴリズムです。

機械学習にはPython!

機械学習ではお馴染みのPythonを活用します。
なぜならライブラリが充実してるからです。

Pythonが言語仕様的に特別機械学習に向いているのかわかりませんが、機械学習に関連したライブラリが充実していることは確かです。
しかも、Pythonはクライアント環境でもサーバー環境でも動作するという強みがあります。

顔検出プログラムの全コード

とりあえず、私が作った画像から人の顔を検知するプログラムを掲載しておきます。

import cv2
import numpy as np
import requests
from matplotlib import pyplot as plt

# Haar Cascadeの事前ロード(1回だけでOK)
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

def detect_faces_from_url(image_url):
    try:
        response = requests.get(image_url)
        response.raise_for_status()
        img_array = np.frombuffer(response.content, np.uint8)
        image = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
    except Exception as e:
        print(f"画像の取得または読み込みに失敗: {e}")
        return None, None

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

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

    return image, faces

# 画像URL
image_url = "画像URL"
result_image, faces = detect_faces_from_url(image_url)

if faces is not None and len(faces) > 0:
    print("検出された顔の座標:", faces)
    
    # 表示
    plt.imshow(cv2.cvtColor(result_image, cv2.COLOR_BGR2RGB))
    plt.axis("off")
    plt.show()
else:
    print("顔が検出されませんでした")

元画像

顔検出処理後

使用ライブラリ

import cv2
import numpy as np
import requests
from io import BytesIO
from matplotlib import pyplot as plt

cv2はOpenCVライブラリであり、今回のプログラムは画像を読み込んで解析するために使用します。

numpyはAI開発でよく出てくる数値計算やデータ処理ライブラリです。

requestsは画像をWEB上からダウンロードするために使用します。
実際に何かのプロジェクトで活用する時にも、S3にアップロードされた画像データを読み込んで解析したりすると思うので、こういう方法にしました。

matplotlibはグラフや図形を描くライブラリで、解析結果の画像に図形を描きこむために使用します。

顔検出モデルを読み込み

今回、顔を検出のにHaar Cascadeを利用するので、モデルを読み込みます。

face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

ここで読み込んでいるモデル”haarcascade_frontalface_default.xml”は正面の顔を検出するモデルです。
他にも以下のようなモデルがあります。

顔(精度高め)haarcascade_frontalface_alt2.xml
横顔haarcascade_profileface.xml
haarcascade_eye.xml
笑顔haarcascade_smile.xml
上半身haarcascade_upperbody.xml
全身haarcascade_fullbody.xml
猫の顔haarcascade_frontalcatface.xml

今回のサンプルでは正面の顔のモデルを活用するので、横顔は検出できないということになります。
なので、集合写真など被写体がカメラを意識している場面での検出に活用できます。

顔を検出する関数を定義

今回はサンプルプログラムなので、1回しかコールしませんが、プロジェクトで再利用することを考慮して、顔を検出する処理を関数でラッピングしておきます。

def detect_faces_from_url(image_url):
    try:
        response = requests.get(image_url)
        response.raise_for_status()
        img_array = np.frombuffer(response.content, np.uint8)
        image = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
    except Exception as e:
        print(f"画像の取得または読み込みに失敗: {e}")
        return None, None

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

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

    return image, faces

画像をダウンロードして読み込みを行う

今回、関数にはURLを渡しているので、URLから画像の読み込みます。

try:
    response = requests.get(image_url)
    response.raise_for_status()
    img_array = np.frombuffer(response.content, np.uint8)
    image = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
except Exception as e:
    print(f"画像の取得または読み込みに失敗: {e}")
    return None, None

画像の読み込みに失敗したら、顔の検出もできないので、Noneを返して関数を抜けます。

グレースケール処理を実施

“なぜグレースケールするのか?”
Haar Cascadeはピクセルの明るさと位置関係から物体を検知する仕組みです。
色情報が含まれていると、明るいのか?暗いのか?を判別しにくいのです。
なので、ノイズになる可能性のある情報を除外するために、cv2ライブラリを使って、グレースケール処理を行います。

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

顔の検出

detectMultiScale関数で顔の検出を行います。
関数を実行すると、検出された顔の位置情報が返ってきます。

faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

gray:グレースケールした画像オブジェクト
scaleFactor:顔を検出するために画像サイズを縮小する倍率
minNeighbors:顔と判断するための最小隣接矩形数
minSize:検出する顔の最小サイズ

検出した顔を図形で描画

顔を検出した時に、座標情報が返ってくるので、その座標情報を元に、画像に矩形を描画します。

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

画像URLを顔検出関数に渡して、結果を受け取る

先ほど定義した関数に画像URLを渡して、顔部分に図形を描画した画像データと検出した顔の座標データを受け取ります。

# 画像URL
image_url = "画像URL"
result_image, faces = detect_faces_from_url(image_url)

顔の検出結果を表示する

関数を実行して、関数から受け取った顔の座標データをチェックします。
データ件数が0件なら、顔は検出できなかったということになります。

顔が検出されたら、座標データと画像を表示するというプログラムになります。

if faces is not None and len(faces) > 0:
    print("検出された顔の座標:", faces)
    
    # 表示
    plt.imshow(cv2.cvtColor(result_image, cv2.COLOR_BGR2RGB))
    plt.axis("off")
    plt.show()
else:
    print("顔が検出されませんでした")

顔検出の処理時間について

精度と処理速度はトレードオフです。
以下の顔を検出する処理のところで、精度と処理速度に関係する値が出てきます。

faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

scaleFactor:顔を検出するために画像サイズを縮小する倍率
minNeighbors:顔と判断するための最小隣接矩形数
minSize:検出する顔の最小サイズ

この値を変えた時に、どの程度処理時間が変わるのか試してみたいと思います。

基準値

種類
scaleFactor1.1
minNeighbors5
minSize(30, 30)

実行時間は2.4333 秒~3.6321 秒
この数値を基準として調査してみます。

各数値を細かくした場合

より精度を高める方向で、数値を小さくします。

種類
scaleFactor1.05
minNeighbors3
minSize(20, 20)

実行時間は3.8842 秒~4.0793 秒
処理時間は増えました。
しかも、精度を高めるつもりでしたが、関係ない箇所まで検出されるようになってしまいました。
細かくしすぎるとダメなようです。

scaleFactorだけを大きくした場合

scaleFactorを1.1→1.3に変更してみました。

種類
scaleFactor1.3
minNeighbors5
minSize(30, 30)

実行時間は1.6966 秒~1.8845 秒
実行時間は早くなりました。
しかし、数値を大きくしたことで、画像によってはこれまで検出されていた顔が検出されなくなりました。(今回のフリー画像では検出されています。)

scaleFactorだけを小さくした場合

種類
scaleFactor1.05
minNeighbors5
minSize(30, 30)

実行時間は3.4067 秒~4.3813 秒
時間も増えたし、顔じゃない物も検出されるようになりました。
背景部分に顔検出マークが出てますよね。

minNeighborsだけを大きくした場合

種類
scaleFactor1.1
minNeighbors10
minSize(30, 30)

実行時間は2.3226 秒~3.0270 秒
検出データには変化なしでした。

minNeighborsだけを小さくした場合

種類
scaleFactor1.1
minNeighbors3
minSize(30, 30)

実行時間は2.3915 秒~2.6319 秒
minNeighborsは実行速度には大きな影響を与えないようです。
しかし、minNeighborsを小さくした場合では、顔じゃない物が顔として認識されてしまいました。

他の画像でテストした際は、特に顕著に顔ではない物がたくさん検出される結果となりました。
minNeighborsを小さすぎると、小さな模様まで人の顔に見えるようです。
まるで人間のパレイドリア現象やシミュラクラ現象みたいですね。

minSizeだけを大きくした場合

種類
scaleFactor1.1
minNeighbors5
minSize(50, 50)

実行時間は2.1474 秒~2.1715 秒
検証には元々人の顔が大きめに写っている写真を利用したので、検出結果は基準値の時と違いがみられませんでした。
テーマパークなどでたくさんの人が遠近関係なく写っている写真では違いが出るかもしれません。

minSizeだけを小さくした場合

種類
scaleFactor1.1
minNeighbors5
minSize(10, 10)

実行時間は2.6424 秒~3.7143 秒
今回のフリー画像では差異は見られませんでしたが、他の画像でテストした際、minSizeを小さくしたことで、小さなシミが顔として認識されるようになってしまいました。

このように、設定によって検出精度や実行速度が異なるため、被写体となる物や想定される環境に合わせて最適な設定を行う必要があるようです。

宣伝
WordPressサイトのテンプレート編集やトラブル対応、バグ修正、簡単なJavascriptの作成(カルーセルやバリデーション等)など、小規模なスポット対応を受け付けております。
もしお困りごとがありましたら、お問い合わせフォームよりご相談ください。

この記事を書いた人

uilou

uilou

プログラマー

基本的に、自分自身の備忘録のつもりでブログを書いています。 自分と同じ所で詰まった人の助けになれば良いかなと思います。 システムのリファクタリングを得意としており、バックエンド、フロントエンド、アプリケーション、SQLなど幅広い知識と経験があります。 広いだけでなく、知識をもっと深堀りしていきたいですね。