« 動画生成AIサービス「Luma AI」を試してみた | トップページ | 最近買った小物類 »

2024年6月21日 (金)

大量の画像ファイルからOpenCV使って特定の顔が映ってる写真だけを抜き出すやつを作った

タイトルの通りです。必要性に迫られて、ある顔が映っている画像ファイルを選別して抜き出すやつを作ってみました。
長男の卒業式写真をもらったんですが・・・約740枚もあって、とんでもない量が送られてきました。容量も全部で11.2GB。
いやあ、いくらなんでも740枚は多過ぎだろう。欲しいのは、自分の子供が映っているやつだけなんだけど。
ちょっと、学校側は張り切り過ぎです。

てことで、

1.テンプレート画像となる顔画像を用意
2.740枚の画像が入ったフォルダ「data」の1枚1枚を読み込み、顔検出する
3.その検出した画像とテンプレート画像の顔をと比較し、類似度を計算
4.類似度がある一定以上の顔画像を含む画像ファイルを「result」というフォルダにコピーする

というプログラムを作りました。

いわゆるディープラーニングの画像認識を使わず、OpenCVのみとしました。なんせ、740枚も処理するので、畳み込みニューラルネットワーク(CNN)では時間がかかり過ぎです。このため、簡易な方法を選びました。

具体的には、顔検出にはhaar-like特徴分類器を、類似度計算に使う画像特徴量にはSIFT特徴を、2つの画像の特徴量のマッチングにはBFMatcher、つまり総当たりのアルゴリズムを使ってます。

で、作ったのが以下のプログラムです。
(「face_pickup.py」という名前にしました)


import cv2
import numpy as np
import os
import shutil
# 顔を検出する関数
def detect_faces(image):
    # Haar-like特徴分類器を使用して顔を検出
    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=10, minSize=(200, 200))
    return faces
# SIFT特徴抽出器を作成する関数
def create_sift():
    sift = cv2.SIFT_create()
    return sift
# 画像の特徴量を計算する関数
def compute_features(image, sift):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    keypoints, descriptors = sift.detectAndCompute(gray, None)
    return keypoints, descriptors
# マッチングを行い、類似度を計算する関数
def match_features(des1, des2):
    bf = cv2.BFMatcher()
    matches = bf.knnMatch(des1, des2, k=2)
    good_matches = []
    try:
        for m, n in matches:
            if m.distance < 0.75 * n.distance:
                good_matches.append(m)
    except ValueError:
        pass
    similarity = len(good_matches) / max(len(des1), len(des2))
    return similarity
# メインの処理
def main():
    # 顔検出用の画像を読み込む
    face_image = cv2.imread('face.jpg')

   
# dataフォルダ内の画像を読み込む
    images_folder = 'data'
    images = [f for f in os.listdir(images_folder) if os.path.isfile(os.path.join(images_folder, f))]

    # SIFT特徴抽出器を作成する
    sift = create_sift()
    # 類似度のしきい値
    similarity_threshold = 0.065

    # 類似度の高い顔が含まれている画像をresultフォルダにコピーする
    for image_file in images:
        image_path = os.path.join(images_folder, image_file)
        image = cv2.imread(image_path)
        # 顔を検出する
        faces = detect_faces(image)

        if len(faces) > 0:
            # 顔の特徴量を計算する
            face_keypoints, face_descriptors = compute_features(face_image, sift)
            max_sim = 0.0
            similarity = 0.0
            for (x, y, w, h) in faces:
                detectface_image = image[y:y+h,x:x+w]
                image_keypoints, image_descriptors = compute_features(detectface_image, sift)
                # 類似度を計算する
                if image_descriptors is not None:
                    similarity = match_features(face_descriptors, image_descriptors)
                if similarity>max_sim:
                    max_sim = similarity
            print(image_file, ',', len(faces), ',', max_sim)
            # 類似度がしきい値以上の場合、resultフォルダにコピーする
            if max_sim > similarity_threshold:
                result_folder = 'result'
                if not os.path.exists(result_folder):
                    os.makedirs(result_folder)
                shutil.copy(image_path, os.path.join(result_folder, image_file))
    print("類似度の高い顔が含まれている画像のコピーが完了しました。")
if __name__ == '__main__':
    main()

実は、Bing Chatで作らせたんですが、エラーだらけでして、これを修正してまともに動かせるように変えてます。
ChatGPTもかなり進化してますが、なかなかそのまま使えるコードをいきなりは作ってくれませんね。

これと、顔画像を検索させたい画像ファイルを「data」というフォルダにぶち込んでおきます。
使うライブラリは、OpenCV、numpy辺りがあれば使えます。
さらに、face.jpgという名前で、検索したい顔画像を用意しておきます。

Face

ぼかしのおかげでちょっとヤバい感じの写真ですが、これがテンプレート画像です(もちろん、実際にはぼかし無しです)。

20240620-194645

フォルダ構成は以上のようになります。
プログラム本体「face_pickup.py」以外には、検索させたい画像をおさめた「data」フォルダ、抜き出した画像をおさめた「result」フォルダ、テンプレート画像「face.jpg」を用意します。
(「result」フォルダはなくても自動で作られます)

これで、準備完了。

実行は、コマンドプロンプトなどでこのプログラムの入ったフォルダに移動して、

python face_pickup.py

です。

20240620-200511

こんな感じに実行されます。ファイル名、検出された顔画像の数、そしてその中で一番高い類似度の値、の順に表示されます。

なお類似度の閾値は、プログラム中ほどにある「similarity_threshold = 0.065」の0.065の値を調整します。
これを決めるために、最初は2、30枚くらいの画像でトライした方がいいでしょう。

で、閾値を決めたら、740枚を一気に走らせます。

20240620-193504

で、出てきた結果がこれ。数分ほどで処理は完了。結果、だいたい200枚くらいが拾われました。
ですが、結構余計な画像が多いですね。顔ですらない画像も中には含まれてました。
それらを削ると、残ったのは結局100枚くらい。

さほど精度がいいとは言い難いですが、740枚から1枚1枚探ることを考えると、これでもかなりマシです。
おそらくですが、何枚かは欠落しているかと思いますが、これだけ取れれば十分かと。

最初、妻が全部見て選ぶ言ってたんですが、途中で音を上げました。
そんな妻に、Pythonの威力とやらを見せつけてやりましたよ。ほんと、OpenCVの画像処理サマサマです。

高精度なCNNを使わずとも、OpenCVだけでもここまでのことはできるんです。ご参考まで。


Pythonと深層学習による革新的な画像認識と処理の裏技~CNNとOpenCVで実現する顔認識、物体検出、画像分類のテクニック~

« 動画生成AIサービス「Luma AI」を試してみた | トップページ | 最近買った小物類 »

数値解析系」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

« 動画生成AIサービス「Luma AI」を試してみた | トップページ | 最近買った小物類 »

無料ブログはココログ

スポンサード リンク

ブログ村