顔の位置を固定するPythonコードの解説

アイキャッチ画像

はじめに

皆さんは、鳥の「頭を静止させる不思議な能力」をご存知ですか?
試しに「bird head stability」などで検索をかけてみてください。とてもかわいいですよ。

調べてみましたが、この現象に特定の名前があるわけではないようです。
この能力は鳥の首関節の驚異的な柔軟性に起因するそうで、頭を静止させることで、激しい飛行中の環境内オブジェクト追跡を可能にしているそうです。

この性質を利用して、鳥の頭にカメラを取り付け、スタビライザー(ジンバル)のように動かないカメラを作る猛者(CM)もいるようです。

さて、この鳥の頭のように、人間の頭も相対的に静止させることができるでしょうか?
画像処理の技術を使えば、人間の頭を静止させることができます。

元動画

まずは、元動画をご覧ください。

https://pixabay.com/ja/videos/%E5%A5%B3%E6%80%A7-%E9%A1%94-%E8%8B%A5%E3%81%84%E3%81%A7%E3%81%99-%E8%A6%8B%E3%82%8B-78733/

動画内で顔の位置が動く場合に、その顔を一定の位置に固定します。
具体的には、顔の中心を両目の中心に設定し、その位置を固定します。

import sys

sys.path.append('/usr/lib/python3/dist-packages')

import cv2
import mediapipe as mp
import numpy as np

# MediaPipeのFaceMeshモデルを初期化
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh()

# input.mp4から映像を取得
cap = cv2.VideoCapture("assets/input.mp4")

# 動画のフレームサイズを取得
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

# 両目の固定位置(画像の中心に設定)
fixed_eye_x = width // 2
fixed_eye_y = height // 2

# 両目の固定距離(ピクセル単位で設定、例えば100)
fixed_eye_distance = 100

# 出力動画の設定
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('output.mp4', fourcc, 30.0, (width, height))

while cap.isOpened():
    ret, image = cap.read()
    if not ret:
        break

    # BGRからRGBに変換
    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    # 顔の検出
    results = face_mesh.process(image_rgb)

    if results.multi_face_landmarks:
        for face_landmarks in results.multi_face_landmarks:
            # 両目の中心を計算
            left_eye = face_landmarks.landmark[33]
            right_eye = face_landmarks.landmark[263]
            eye_x = int((left_eye.x + right_eye.x) * width // 2)
            eye_y = int((left_eye.y + right_eye.y) * height // 2)

            # 両目の距離を計算
            eye_distance = int(np.sqrt((left_eye.x - right_eye.x)**2 + (left_eye.y - right_eye.y)**2) * width)

            # スケーリングファクターを計算
            scale_factor = fixed_eye_distance / eye_distance

            # 平行移動とスケーリングを行う
            M_translate_scale = np.float32([[scale_factor, 0, fixed_eye_x - eye_x * scale_factor],
                                            [0, scale_factor, fixed_eye_y - eye_y * scale_factor]])
            image = cv2.warpAffine(image, M_translate_scale, (width, height))

            # 両目の位置に基づいて回転角度を計算
            angle = np.arctan2((left_eye.y - right_eye.y) * height, (left_eye.x - right_eye.x) * width)
            angle = np.degrees(angle)

            # 回転行列を計算
            M_rotate = cv2.getRotationMatrix2D((fixed_eye_x, fixed_eye_y), angle, 1)

            # 画像を回転
            image = cv2.warpAffine(image, M_rotate, (width, height))

            # 画像を上下反転
            image = cv2.flip(image, 0)

    # 出力動画にフレームを追加
    cv2.namedWindow('test', cv2.WINDOW_NORMAL)
    out.write(image)
    
    # imshow()で画面に表示
    cv2.imshow('test', image)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break


# リソースを解放
cap.release()
out.release()
cv2.destroyAllWindows()

各部分の詳細

コードの各行を順番に見ていきましょう。

顔の検出

results = face_mesh.process(image_rgb)

RGB形式の画像(image_rgb)をMediaPipeのFace Meshモデルに渡して、顔のランドマークを検出します。

両目の中心を計算

left_eye = face_landmarks.landmark[33]
right_eye = face_landmarks.landmark[263]
eye_x = int((left_eye.x + right_eye.x) * width // 2)
eye_y = int((left_eye.y + right_eye.y) * height // 2)

ここでは、検出された顔のランドマークから左目(landmark[33])と右目(landmark[263])の位置を取得し、それらの平均位置を画像内での座標(eye_x, eye_y)に変換します。

両目の距離とスケーリングファクターの計算

eye_distance = int(np.sqrt((left_eye.x - right_eye.x)**2 + (left_eye.y - right_eye.y)**2) * width)
scale_factor = fixed_eye_distance / eye_distance

この部分で、両目の距離(eye_distance)とスケーリングファクター(scale_factor)を計算します。スケーリングファクターは、固定したい両目の距離(fixed_eye_distance)を実際の両目の距離で割って求めます。

平行移動とスケーリング

M_translate_scale = np.float32([[scale_factor, 0, fixed_eye_x - eye_x * scale_factor],
                                [0, scale_factor, fixed_eye_y - eye_y * scale_factor]])
image = cv2.warpAffine(image, M_translate_scale, (width, height))

cv2.warpAffine関数を使用して、計算したスケーリングファクターと両目の位置に基づいて画像を平行移動とスケーリングを行います。

画像の回転

angle = np.arctan2((left_eye.y - right_eye.y) * height, (left_eye.x - right_eye.x) * width)
angle = np.degrees(angle)
M_rotate = cv2.getRotationMatrix2D((fixed_eye_x, fixed_eye_y), angle, 1)
image = cv2.warpAffine(image, M_rotate, (width, height))

最後にcv2.warpAffineを再度使用して、画像を回転させます。回転角度は、両目の位置に基づいてnp.arctan2関数で計算します。

結果


できました!
顔の位置が固定されていますね。

まとめ

顔の位置を固定する技術は、多くの応用分野で活用されます。

顔認証・顔検出

顔の位置が固定されていると、顔認証や顔検出の精度が向上します。顔が動画内で頻繁に動く場合や、角度が変わる場合に有用です。

ビデオ会議

ビデオ会議での顔の位置を固定することで、参加者が話をする際に顔がしっかりとフレーム内に収まるようになります。

ヒューマンコンピュータインタラクション(HCI)

顔の動きをトラッキングすることで、より直感的なインターフェイスやコントロールが可能になります。たとえば顔の位置に応じてカーソルを動かすといった応用が考えられます。

医療診断

顔の特定の部分(たとえば、目や口)に焦点を当てる必要がある医療診断でも、この技術は有用です。

以上です。
今回は鳥の頭のように、人間の頭も相対的に静止させる方法を紹介しました。