6DRepNet頭部3次元姿勢推定
【概要】6DRepNetはリアルタイムで人間の頭部姿勢を推定するAI技術である。従来のEuler角表現と異なり、6次元回転表現を採用することで角度の曖昧性問題を解決し、推定を実現している。本ページでは、パソコンカメラを使用してリアルタイムに頭部の向きを検出し、ピッチ・ヨー・ロール角度を数値とグラフィカルな軸表示で確認できる。Windows環境での実行手順、プログラムコード、実験アイデアを含む。
実際にプログラムを動作させることで、AI技術が人間の動作を数値化する過程を体験し、頭部姿勢推定の精度や応答性を直接確認できる。顔の向きや表情の変化に対するAIの反応を観察することで、コンピュータビジョン技術の実用性と限界を理解する。

目次
事前準備
Python, Windsurfをインストールしていない場合の手順(インストール済みの場合は実行不要)。
- 管理者権限でコマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー >
cmd
と入力 > 右クリック > 「管理者として実行」)。 - 以下のコマンドをそれぞれ実行する(
winget
コマンドは1つずつ実行)。
REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Windsurf をシステム領域にインストール
winget install --scope machine --id Codeium.Windsurf -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul
REM Windsurf のパス設定
set "WINDSURF_PATH=C:\Program Files\Windsurf"
if exist "%WINDSURF_PATH%" (
echo "%PATH%" | find /i "%WINDSURF_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%WINDSURF_PATH%" /M >nul
)
Python パッケージのインストール
コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd
と入力 > 右クリック > 「管理者として実行」)し、以下を実行する:
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install sixdrepnet numpy insightface
pip install -U opencv-python
プログラムコード
# プログラム名: 6DRepNet頭部姿勢推定プログラム
# 特徴技術名: 6DRepNet(6次元回転表現による頭部姿勢推定)
# 出典: Hempel, T., & Abdelrahman, A. A. (2022). 6D Rotation Representation for Unconstrained Head Pose Estimation. In 2022 IEEE International Conference on Image Processing (ICIP) (pp. 2496-2500). IEEE.
# 特徴機能: 6次元回転表現による連続的な頭部姿勢推定。従来のEuler角表現の不連続性・ジンバルロック問題を解決し、高精度な頭部姿勢推定を実現
# 学習済みモデル: 6DRepNet事前学習済みモデル(300W-LPデータセットで学習)。AFLW2000で3.97°、BIWIで2.66°のMAEを達成。ライブラリ内で自動ダウンロード
# 方式設計:
# - 関連利用技術: InsightFace(顔検出、RetinaFaceベース)、OpenCV(画像処理・表示)、NumPy(数値計算)
# - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://github.com/opencv/opencv/blob/master/samples/data/vtest.aviを使用)、出力: OpenCV画面でリアルタイム表示(姿勢角度と軸表示)、1秒間隔でprint()表示、終了時result.txt保存
# - 処理手順: 1.顔検出(InsightFace)、2.顔領域抽出、3.6DRepNetで姿勢推定、4.ピッチ・ヨー・ロール角度算出、5.結果表示
# - 前処理、後処理: 前処理:顔検出による顔領域の特定、画像サイズの正規化。後処理:角度値の度数法変換、3D軸の描画
# - 追加処理: 顔検出の信頼度フィルタリング(低信頼度の検出結果を除外)
# - 調整を必要とする設定値: DET_SIZE(顔検出解像度、デフォルト(640,640))- 大きいほど精度向上するが処理速度低下
# 将来方策: 顔検出解像度(DET_SIZE)の自動最適化機能。フレームレートを監視し、目標FPSを維持できる最大解像度を動的に選択
# その他の重要事項: Windows環境での動作確認済み。リアルタイム処理のためカメラバッファクリア実装
# 前準備: pip install sixdrepnet numpy insightface
# pip install -U opencv-python
import cv2
import numpy as np
from sixdrepnet import SixDRepNet
from insightface.app import FaceAnalysis
import tkinter as tk
from tkinter import filedialog
import os
import time
import urllib.request
import sys
import io
import torch
# Windows標準出力エンコーディング設定
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', line_buffering=True)
# 定数定義
CAMERA_WIDTH = 640
CAMERA_HEIGHT = 480
USE_FACE_DETECTION = True
DET_SIZE = (640, 640) # 顔検出解像度 - 大きいほど精度向上するが処理速度低下
PRINT_INTERVAL = 1.0 # 結果出力間隔(秒)- 小さくすると出力頻度が上がる
FONT_SCALE = 0.7
FONT_THICKNESS = 2
FONT_COLOR_POSE = (0, 255, 255)
FONT_COLOR_FACE = (255, 255, 0)
FONT_COLOR_TEXT = (255, 255, 255)
BBOX_COLOR = (0, 255, 0)
BBOX_THICKNESS = 2
WINDOW_NAME = '6DRepNet Head Pose Estimation'
# プログラム開始時の説明
print('6DRepNet頭部姿勢推定プログラム')
print('このプログラムは6次元回転表現を使用して高精度な頭部姿勢推定を行います')
print('操作方法:')
print('- qキーまたはESCキー: プログラム終了')
print('- OpenCV画面に姿勢角度(ピッチ、ヨー、ロール)と3D軸が表示されます')
print('')
# GPU/CPU判定
gpu_available = torch.cuda.is_available()
if gpu_available:
print('GPUが利用可能です')
else:
print('GPUが利用できません。CPUモードで実行します')
# モデル初期化
print('6DRepNetモデルを初期化中...')
if gpu_available:
model = SixDRepNet()
print('6DRepNetモデルの初期化完了(GPU使用)')
else:
# CPUモードで初期化
os.environ['CUDA_VISIBLE_DEVICES'] = '-1' # CUDAを無効化
model = SixDRepNet(gpu_id=-1) # CPU使用を指定
print('6DRepNetモデルの初期化完了(CPU使用)')
face_app = None
if USE_FACE_DETECTION:
print('InsightFace顔検出器を初期化中...')
face_app = FaceAnalysis(allowed_modules=['detection'])
if gpu_available:
try:
face_app.prepare(ctx_id=0, det_size=DET_SIZE) # GPU使用
print('InsightFace顔検出器の初期化完了(GPU使用)')
except:
face_app.prepare(ctx_id=-1, det_size=DET_SIZE) # CPUフォールバック
print('InsightFace顔検出器の初期化完了(CPU使用)')
else:
face_app.prepare(ctx_id=-1, det_size=DET_SIZE) # CPU使用
print('InsightFace顔検出器の初期化完了(CPU使用)')
# 結果記録用
results = []
last_print_time = time.time()
def video_processing(frame):
global last_print_time, results
faces = []
if USE_FACE_DETECTION and face_app is not None:
faces = face_app.get(frame)
for face in faces:
bbox = face.bbox.astype(int)
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), BBOX_COLOR, BBOX_THICKNESS)
pitch, yaw, roll = model.predict(frame)
model.draw_axis(frame, yaw, pitch, roll)
cv2.putText(frame, f'Pitch: {float(pitch):.1f}deg', (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, FONT_COLOR_POSE, FONT_THICKNESS)
cv2.putText(frame, f'Yaw: {float(yaw):.1f}deg', (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, FONT_COLOR_POSE, FONT_THICKNESS)
cv2.putText(frame, f'Roll: {float(roll):.1f}deg', (10, 90),
cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, FONT_COLOR_POSE, FONT_THICKNESS)
if USE_FACE_DETECTION:
cv2.putText(frame, f'Faces: {len(faces)}', (10, 120),
cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, FONT_COLOR_FACE, FONT_THICKNESS)
cv2.putText(frame, 'Press q or ESC to quit',
(frame.shape[1] - 250, frame.shape[0] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, FONT_COLOR_TEXT, 1)
# 指定間隔でprint表示
current_time = time.time()
if current_time - last_print_time >= PRINT_INTERVAL:
result_str = f'Time: {current_time:.1f}, Pitch: {float(pitch):.1f}, Yaw: {float(yaw):.1f}, Roll: {float(roll):.1f}, Faces: {len(faces)}'
print(result_str)
results.append(result_str)
last_print_time = current_time
return frame
print('0: 動画ファイル')
print('1: カメラ')
print('2: サンプル動画')
choice = input('選択: ')
temp_file = None
if choice == '0':
root = tk.Tk()
root.withdraw()
path = filedialog.askopenfilename()
if not path:
exit()
cap = cv2.VideoCapture(path)
elif choice == '1':
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, CAMERA_WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, CAMERA_HEIGHT)
elif choice == '2':
# サンプル動画ダウンロード・処理
url = 'https://github.com/opencv/opencv/raw/master/samples/data/vtest.avi'
filename = 'vtest.avi'
try:
print(f'サンプル動画をダウンロード中: {url}')
urllib.request.urlretrieve(url, filename)
temp_file = filename
cap = cv2.VideoCapture(filename)
except Exception as e:
print(f'動画のダウンロードに失敗しました: {url}')
print(f'エラー: {e}')
exit()
else:
print('無効な選択です')
exit()
# メイン処理
try:
print('頭部姿勢推定を開始します')
while True:
cap.grab()
ret, frame = cap.retrieve()
if not ret:
break
processed_frame = video_processing(frame)
cv2.imshow(WINDOW_NAME, processed_frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q') or key == 27:
break
finally:
cap.release()
cv2.destroyAllWindows()
if temp_file:
os.remove(temp_file)
# 結果をファイルに保存
if results:
with open('result.txt', 'w', encoding='utf-8') as f:
for result in results:
f.write(result + '\n')
print('result.txtに保存しました')
print('プログラムを終了しました')
使用方法
- プログラムファイルを任意のフォルダに保存する
- コマンドプロンプトでそのフォルダに移動し、
python ファイル名.py
を実行する - カメラの映像が表示され、顔を検出すると頭部姿勢の推定が開始される
- 画面左上に角度情報、顔の位置に緑の矩形、頭部の向きに3次元軸が表示される
- 'q'キーまたはESCキーで終了する
実験・探求のアイデア
顔検出機能の比較
プログラム内のUSE_FACE_DETECTION
を変更することで、顔検出機能の有無を比較できる:
USE_FACE_DETECTION = True
:InsightFaceによる顔検出ありUSE_FACE_DETECTION = False
:顔検出なし、全画面から頭部姿勢を推定
顔検出ありの場合は検出された顔領域に対してのみ姿勢推定を行い、なしの場合は画面全体から主要な顔を自動検出して推定する。
カメラ解像度の調整
CAMERA_WIDTH
とCAMERA_HEIGHT
を変更することで、処理速度と精度のトレードオフを観察できる:
- 高解像度(1280×720):精度向上、処理速度低下
- 低解像度(320×240):処理速度向上、精度低下
基本体験
- 正面向きでの基準値確認:正面を向いた状態での各角度の基準値を確認する
- 極端な角度での限界確認:横顔や上下を向いた際の推定限界を観察する
- 複数人での同時検出:複数人が画面に映った際の検出精度を確認する
精度検証実験
- 角度測定実験:分度器を用いて実際の角度と推定値を比較し、誤差を測定する
- 距離による影響確認:カメラからの距離を変えて推定精度の変化を観察する
- 照明条件の影響:明るさや光の方向を変えて推定結果への影響を確認する
応用シナリオ検証
- 視線追跡シミュレーション:画面上の特定点を見つめた際の角度変化を記録する
- 表情変化の影響:笑顔や驚いた表情での推定精度の変化を観察する
- 髪型・眼鏡の影響:帽子や眼鏡着用時の推定結果変化を確認する
これらの実験を通じて、技術の実用性と限界を具体的に理解し、頭部姿勢推定技術の応用可能性を探索できる。