aptpod Tech Blog

株式会社アプトポッドのテクノロジーブログです

Pythonで動画の登場人物ごとに顔をグループ分けしてみた

aptpod Advent Calendar 2024 12月16日を担当するintdashグループの呉羽です。

弊社が提供するintdashは動画データをアップロードすることで、HLSやMP4への変換や、Webブラウザ上での閲覧機能を提供しています。

そこで今回は動画を使ったネタとして『Pythonを使って動画ファイルから顔を検出し、人物ごとにグループ分けするプログラム』をご紹介します。 動画内の顔を認識して分類することで、誰がどのシーンに現れるかを把握することが可能です。

Pythonライブラリ face_recognition について

今回用いるface_recognitionは、Pythonで顔認識を行うためのライブラリです。このライブラリは、dlibという機械学習ライブラリの顔認識モデルをベースにしており、以下の機能を提供します。

  • 顔の検出(位置の特定)
  • 顔のエンコード(特徴量の抽出)
  • 顔同士の類似度計算(同一人物の判定)

インストール方法はface_recognitionのGitHubリポジトリを参考にします。基本的にはPythonのパッケージ管理ツールpipでインストールするだけです。今回はv1.2.2を用います。

プログラム全体の流れ

以下に、プログラムの流れを説明します。

  1. 動画ファイルからフレームを順に読み取る。
  2. フレームから顔の検出を行う。
  3. 過去に検出した顔と比較し、同一人物かどうかを判定する。
  4. 検出した顔の画像を保存する。

実装したコードはGitHubで公開しています。

github.com

今回はサンプル動画として、フリー素材として公開されているこちらの動画を利用します。動画内でいくつかの人物がディスカッションしている様子が伺えます。

pixabay.com

動画ファイルからフレームを順に読み取る。

Pythonで動画ファイルのフレームを読み取るためにopencv-pythonを使います。以下のようにフレームを順に読み取れます。

import cv2

path_to_video = "./xxx.mp4"
video_capture = cv2.VideoCapture(path_to_video)

while video_capture.isOpened():
    ret, raw_frame = video_capture.read()
    if not ret:
        print("Video file ended")
        break

全てのフレームを取得すると、例えば30FPSかつ5分間の動画のフレーム数はおよそ9,000になります。ここでは処理の軽量化のため、以下のように約2秒間隔のフレームごとに処理します。

fps = video_capture.get(cv2.CAP_PROP_FPS)
frame_count = 0

while video_capture.isOpened():
    frame_count += 1
    # take a frame every 2 seconds
    if frame_count % int(fps*2) != 0:
        continue

フレームから顔の検出を行う。

face_recognition.face_locations()にフレームを渡すことで、そのフレーム内の全ての顔の位置を検出できます。以下はフレーム内の顔の全ての位置情報を出力するコードです。

import face_recognition

face_locations = face_recognition.face_locations(frame)
print("Found {} face(s) in frame #{}.".format(len(face_locations), frame_count))

for i in range(len(face_locations)):
    face_location = face_locations[i]

    top, right, bottom, left = face_location
    face_size = (right - left) * (bottom - top)
    print(" - A face is located at pixel location Top: {}, Left: {}, Bottom: {}, Right: {}, Size: {}".format(top, left, bottom, right, face_size))

過去に検出した顔と比較し、同一人物かどうかを判定する。

次にface_recognition.face_encodings()を使い、顔を比較するための特徴量を計算します。そしてface_recognition.face_distance()を使うことで、2つ顔の特徴量の距離[0,1](小さいほど類似している)を計算できます。 これを用いて、ある閾値(ここでは0.6)以下の場合に同一人物と判定し、グループ分けします。

face_groups = [] # list of face encodings
face_encodings = face_recognition.face_encodings(frame, face_locations)

for i in range(len(face_locations)):
    face_location = face_locations[i]
    face_encoding = face_encodings[i]

    group_id = -1
    if len(face_groups) > 0:
      distances = face_recognition.face_distance(face_groups, face_encoding)
      if np.min(distances) < 0.6: # threshold to consider the face as the same person
          group_id = np.argmin(distances)

    if group_id == -1: # create a new group
        face_groups.append(face_encoding)
        group_id = len(face_groups) - 1

検出した顔の画像を保存する。

検出した顔の画像を、人物(グループ)ごとにディレクトリに保存します。分かりやすいように、フレームの経過時間をファイル名として保存しています。

name = "./output/{}/{}/{}.jpg".format(output_dir, group_id, time.strftime("%H_%M_%S", frame_time))
os.makedirs(os.path.dirname(name), exist_ok=True)

face = raw_frame[top:bottom, left:right]
cv2.imwrite(name, face, [int(cv2.IMWRITE_JPEG_QUALITY), 95])

サンプル動画では5人の人物が検知されました。例えば、ある一つのディレクトリに保存された画像は以下のようになりました。

結果の例

他のディレクトリを見てみると、同一人物の誤判定があるようでした。これを解消するためには、手動で閾値を調整する必要があります。

他の人物と混同している例

まとめ

本記事では、Pythonを使った動画内の顔検出・分類プログラムを解説しました。このプログラムをさらに改善・発展させるには、以下のような方法が考えられます。

  • GPUを使った高速な処理
    • dlibをインストールする際にNVIDIA CUDAを有効化すればより高速に処理ができます。
  • 高度なグループ分け
    • k-means法を使えば、人物数(グループ数)を指定しつつ精度良く分類できます。
  • リアルタイム処理への対応
    • Webカメラなどのライブ映像から直接顔を検出するように変更することで、リアルタイムでの分析が可能になります。

また弊社が提供するintdashを活用することで、intdashからの動画ストリーミングをリアルタイム処理したり、得られたデータを別途保存したりすることが可能です。ぜひこのプログラムを参考にオリジナルのアプリケーション開発に挑戦してみてください。