Vision Transormer による表情推定システム
【概要】YOLOv11ベースの顔検出とVision Transformerを組み合わせた表情推定システム。顔を検出し、Ekmanの6基本感情+中性状態(怒り、嫌悪、恐怖、幸福、悲しみ、驚き、中性)を認識。68点顔キーポイント、16x16パッチ分割によるViTの文脈理解で、顔の向きや照明に頑健な感情分析を目指す。Windows環境での実行手順、プログラムコード、実験アイデアを含む。

目次
概要
本教材では、AI技術による表情推定技術を体験する。
主要技術: 表情推定(Facial Expression Recognition)
論文: "Facial Expression Recognition: A Review of Trends and Applications" および実装にVision Transformerを活用した "An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale" (Dosovitskiy et al., ICLR 2021)
技術の新規性・特徴: 人間の顔画像から感情状態を自動的に認識する技術。Ekmanの7基本感情(怒り、嫌悪、恐怖、幸福、中性、悲しみ、驚き)を判定する。従来手法に加えて68点顔キーポイントによる前処理強化とVision Transformerによるグローバル文脈理解により、顔の向きや照明条件に頑健な認識を実現する。
アプリケーション例: 感情分析システム、顧客満足度調査、教育支援システム、ヘルスケアモニタリング、人間-コンピュータインタラクション、心理状態評価
体験価値: リアルタイムで自分の表情がどのように数値化・分類されるかを確認でき、表情と感情の関係性、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
)
必要なライブラリのインストール
コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する:
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install ultralytics transformers opencv-python numpy pillow
YOLOv11顔検出+Vision Transformer表情推定プログラム(折れ線グラフ機能付き)
概要
本プログラムは、動画像から人間の顔を検出し、その表情を7種類(怒り、嫌悪、恐怖、喜び、悲しみ、驚き、中立)に分類する。リアルタイムで複数の顔を同時に処理し、各顔の表情変化を時系列グラフとして可視化する。
主要技術
- YOLOv11
物体検出アルゴリズムである。画像全体を一度の推論で処理する[1]。本プログラムでは顔検出に特化したYOLOv11-faceモデルを使用し、68点の顔キーポイント検出も同時に行う。
- Vision Transformer (ViT)
画像を16×16ピクセルのパッチに分割し、各パッチをトークンとして扱うTransformerベースの画像認識手法である[2]。Self-Attention機構により画像全体の文脈を考慮した認識が可能となる。本プログラムではtrpakov/vit-face-expressionモデルを使用し、FER2013データセットで学習済みの表情分類を行う。
参考文献
- [1] Jocher, G., Qiu, J., & Chaurasia, A. (2024). Ultralytics YOLO11. https://github.com/ultralytics/ultralytics
- [2] Dosovitskiy, A., Beyer, L., Kolesnikov, A., Weissenborn, D., Zhai, X., Unterthiner, T., ... & Houlsby, N. (2021). An image is worth 16x16 words: Transformers for image recognition at scale. In International Conference on Learning Representations.
# プログラム名: YOLOv11顔検出+Vision Transformer表情推定システム(折れ線グラフ機能付き)
# 特徴技術名: Vision Transformer (ViT) + Real-time Emotion Time Series Visualization
# 出典: Dosovitskiy, A., Beyer, L., Kolesnikov, A., Weissenborn, D., Zhai, X., Unterthiner, T., ... & Houlsby, N. (2021). An image is worth 16x16 words: Transformers for image recognition at scale. In International Conference on Learning Representations.
# 新機能: 基本感情の時系列可視化(多線折れ線グラフ)
# 可視化理論根拠: Sheidin, J., et al. (2019). A comparative evaluation of techniques for time series visualizations of emotions
# 特徴機能: 画像を16x16パッチに分割し、Self-Attention機構により画像全体の文脈を考慮した高精度な表情認識を実現
# 学習済みモデル:
# - YOLOv11-face: 顔検出と68点キーポイント検出用モデル、WIDER FACEデータセットで学習
# モデルサイズ選択可能(デフォルト:s):
# n (nano): https://github.com/akanametov/yolo-face/releases/download/v0.0.0/yolov11n-face.pt
# s (small): https://github.com/akanametov/yolo-face/releases/download/v0.0.0/yolov11s-face.pt
# m (medium): https://github.com/akanametov/yolo-face/releases/download/v0.0.0/yolov11m-face.pt
# l (large): https://github.com/akanametov/yolo-face/releases/download/v0.0.0/yolov11l-face.pt
# - trpakov/vit-face-expression: FER2013データセットで学習済み、7基本表情(Angry, Disgust, Fear, Happy, Sad, Surprise, Neutral)分類対応、Hugging Faceから自動ダウンロード、https://huggingface.co/trpakov/vit-face-expression
# 方式設計:
# - 関連利用技術:
# - YOLOv11: リアルタイム物体検出、C3k2ブロックとC2PSA注意機構搭載、従来版より22%少ないパラメータで高精度検出を実現
# - OpenCV: 画像処理、カメラ制御、描画処理
# - Transformers: Hugging Face提供ライブラリ、事前学習済みViTモデルの簡易利用を可能にする
# - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.aviを使用)、出力: OpenCV画面でリアルタイム表示(検出した顔と表情をバウンディングボックスと68点キーポイントで表示)、1秒間隔でprint()による処理結果表示、プログラム終了時にresult.txtファイルに保存、リアルタイム折れ線グラフ表示
# - 処理手順: 1.YOLOv11で顔検出と68点キーポイント取得、2.検出顔領域を224x224にリサイズ、3.ViTで16x16パッチ分割とpositional encoding付与、4.Self-Attention計算で表情特徴抽出、5.7クラス表情分類と信頼度出力、6.時系列データ蓄積と折れ線グラフ更新
# - 前処理、後処理: 前処理:顔向き補正(目の位置から傾斜角度計算し回転補正)、Lanczos補間による高品質リサイズ。後処理:キーポイント数に基づく信頼度調整(60点以上で+5%、40点以上で+2%)、移動平均による時系列平滑化
# - 追加処理: バッファクリア処理(最新フレーム取得のためgrab()とretrieve()を分離)、環境変数設定によるKeras互換性問題回避、非ブロッキンググラフ更新
# - 調整を必要とする設定値: FACE_CONFIDENCE_THRESHOLD(顔検出閾値、デフォルト0.4)、KEYPOINT_QUALITY_THRESHOLD(顔向き補正を行う角度閾値、デフォルト5度)、MODEL_SIZE(YOLOモデルサイズ、デフォルト's'、選択可能:'n', 's', 'm', 'l')、CHART_UPDATE_INTERVAL(グラフ更新間隔、デフォルト1.0秒)、CHART_HISTORY_SIZE(グラフ表示履歴数、デフォルト60秒)
# 将来方策: FACE_CONFIDENCE_THRESHOLDの最適値を自動決定する機能として、初回実行時に複数の閾値で検出を試行し、検出顔数と処理速度を計測して最適値を提案する機能の実装
# その他の重要事項: Windows環境専用、CUDA対応GPU推奨、初回実行時は学習済みモデルのダウンロードに時間がかかる
# 前準備:
# - pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
# - pip install ultralytics transformers opencv-python numpy pillow
import cv2
import tkinter as tk
from tkinter import filedialog
import os
import torch
import numpy as np
from PIL import Image
from transformers import pipeline
from ultralytics import YOLO
import warnings
import time
import urllib.request
from datetime import datetime
from collections import deque, defaultdict
# Keras 3互換性問題の解決(Transformers公式推奨)
os.environ['USE_TF'] = '0'
os.environ['USE_TORCH'] = '1'
warnings.filterwarnings('ignore')
# ===== 設定・定数管理 =====
# YOLOv11-faceモデル設定(より効果的な設定)
MODEL_SIZE = 's' # 使用するモデルサイズ(検出精度向上)
MODEL_PATH = os.path.join(os.path.expanduser('~'), 'Documents', f'yolov11{MODEL_SIZE}-face.pt')
MODEL_URL = f'https://github.com/akanametov/yolo-face/releases/download/v0.0.0/yolov11{MODEL_SIZE}-face.pt'
SAMPLE_VIDEO_URL = 'https://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.avi'
SAMPLE_VIDEO_NAME = 'vtest.avi'
RESULT_FILE = 'result.txt'
# カメラ設定(高解像度)
WINDOW_WIDTH = 1920 # Full HD
WINDOW_HEIGHT = 1080 # Full HD
FPS = 30 # フレームレート
# 検出パラメータ(調整済み)
FACE_CONFIDENCE_THRESHOLD = 0.4 # 小さい顔の検出率向上
KEYPOINT_QUALITY_THRESHOLD = 5 # 顔向き補正角度閾値(度)
HIGH_QUALITY_KEYPOINTS = 60 # 高品質キーポイント数
MEDIUM_QUALITY_KEYPOINTS = 40 # 中品質キーポイント数
HIGH_QUALITY_BONUS = 0.05 # 高品質ボーナス(0.0-1.0)
MEDIUM_QUALITY_BONUS = 0.02 # 中品質ボーナス(0.0-1.0)
# === 折れ線グラフ設定パラメータ ===
CHART_ENABLED = True # グラフ表示有効化
CHART_UPDATE_INTERVAL = 1.0 # グラフ更新間隔(秒)
CHART_HISTORY_SIZE = 60 # グラフ表示履歴数(秒)
CHART_WINDOW_WIDTH = 800 # グラフウィンドウ幅
CHART_WINDOW_HEIGHT = 600 # グラフウィンドウ高さ
CHART_MARGIN = 80 # グラフマージン
# 表示設定
PRINT_INTERVAL = 1.0 # 結果出力間隔(秒)
IMAGE_SIZE = 224 # ViT入力画像サイズ
# キーポイントインデックス(68点顔ランドマーク)
LEFT_EYE_START = 36
LEFT_EYE_END = 42
RIGHT_EYE_START = 42
RIGHT_EYE_END = 48
MIN_VALID_KEYPOINTS = 5
# 7基本表情(Ekmanの基本感情理論)
EMOTION_JAPANESE = {
'angry': 'Angry', 'disgust': 'Disgust', 'fear': 'Fear', 'happy': 'Happy',
'neutral': 'Neutral', 'sad': 'Sad', 'surprise': 'Surprise'
}
# 表情別色分け(BGR形式)
EMOTION_COLORS = {
'angry': (0, 0, 255), # 赤
'disgust': (0, 128, 0), # 緑
'fear': (128, 0, 128), # 紫
'happy': (0, 255, 255), # 黄
'neutral': (128, 128, 128), # グレー
'sad': (255, 0, 0), # 青
'surprise': (0, 165, 255) # オレンジ
}
# 基本感情リスト(Neutral除く - 調査結果に基づく6基本感情)
BASIC_EMOTIONS = ['happy', 'sad', 'angry', 'fear', 'disgust', 'surprise']
class EmotionTimeSeriesChart:
def __init__(self):
self.enabled = CHART_ENABLED
if not self.enabled:
return
# データ格納用
self.timestamps = deque(maxlen=CHART_HISTORY_SIZE)
self.emotion_data = {emotion: deque(maxlen=CHART_HISTORY_SIZE) for emotion in BASIC_EMOTIONS}
# グラフ設定
self.chart_width = CHART_WINDOW_WIDTH
self.chart_height = CHART_WINDOW_HEIGHT
self.margin = CHART_MARGIN
self.graph_width = self.chart_width - 2 * self.margin
self.graph_height = self.chart_height - 2 * self.margin
print('OpenCV感情グラフを初期化しました')
def add_data(self, emotion_scores, timestamp=None):
if not self.enabled:
return
if timestamp is None:
timestamp = datetime.now()
self.timestamps.append(timestamp)
# 各感情のスコアを追加
for emotion in BASIC_EMOTIONS:
score = emotion_scores.get(emotion, 0.0) * 100
self.emotion_data[emotion].append(score)
def create_chart_image(self):
if not self.enabled or len(self.timestamps) < 2:
return None
# 背景画像を作成
img = np.zeros((self.chart_height, self.chart_width, 3), dtype=np.uint8)
# タイトル
cv2.putText(img, 'Emotion Time Series Chart', (20, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
# グラフ枠
cv2.rectangle(img, (self.margin, self.margin),
(self.margin + self.graph_width, self.margin + self.graph_height),
(255, 255, 255), 1)
# Y軸ラベル(0-100%)
for i in range(0, 101, 20):
y = self.margin + self.graph_height - int(i * self.graph_height / 100)
cv2.line(img, (self.margin - 5, y), (self.margin, y), (255, 255, 255), 1)
cv2.putText(img, f'{i}%', (10, y + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
# データ点数
data_count = len(self.timestamps)
if data_count < 2:
return img
# 各感情の線を描画
for i, emotion in enumerate(BASIC_EMOTIONS):
color = EMOTION_COLORS[emotion]
data = list(self.emotion_data[emotion])
if len(data) < 2:
continue
# 線を描画
points = []
for j, value in enumerate(data):
x = self.margin + int(j * self.graph_width / (data_count - 1))
y = self.margin + self.graph_height - int(value * self.graph_height / 100)
points.append((x, y))
# 線を描画
for k in range(len(points) - 1):
cv2.line(img, points[k], points[k + 1], color, 2)
# 凡例
legend_y = 50 + i * 25
cv2.rectangle(img, (self.chart_width - 150, legend_y),
(self.chart_width - 130, legend_y + 15), color, -1)
cv2.putText(img, EMOTION_JAPANESE[emotion],
(self.chart_width - 120, legend_y + 12),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
return img
def save_chart(self, filename='emotion_chart.png'):
img = self.create_chart_image()
if img is not None:
cv2.imwrite(filename, img)
return True
return False
def show_chart(self):
img = self.create_chart_image()
if img is not None:
cv2.imshow('Emotion Chart', img)
cv2.waitKey(1)
def close(self):
try:
if cv2.getWindowProperty('Emotion Chart', cv2.WND_PROP_VISIBLE) >= 0:
cv2.destroyWindow('Emotion Chart')
except:
pass # ウィンドウが存在しない場合は何もしない
def download_yolo_model():
if not os.path.exists(MODEL_PATH):
print(f'YOLOv11{MODEL_SIZE}-faceモデルをダウンロードしています...')
print(f'モデルサイズ: {MODEL_SIZE} (nano=最軽量, small=軽量, medium=中程度, large=高精度)')
try:
os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
urllib.request.urlretrieve(MODEL_URL, MODEL_PATH)
print(f'YOLOv11{MODEL_SIZE}-faceモデルのダウンロードが完了しました')
except Exception as e:
print(f'YOLOv11{MODEL_SIZE}-faceモデルのダウンロードに失敗しました')
print(f'エラー: {e}')
print(f'URL: {MODEL_URL}')
exit()
else:
print(f'YOLOv11{MODEL_SIZE}-faceモデルが既に存在します: {MODEL_PATH}')
# プログラム概要表示
print('=== YOLOv11顔検出+Vision Transformer表情推定システム(折れ線グラフ機能付き)===')
print('概要: リアルタイムで顔を検出し、Vision Transformerで7種類の表情を認識します')
print('機能: YOLOv11による顔検出 + ViT Self-Attentionによる表情分析')
print('新機能: リアルタイム感情時系列グラフ')
print('可視化: 6基本感情の多線折れ線グラフ(学術研究に基づく最適化済み)')
print('操作: qキーで終了、sキーで詳細情報表示、cキーでグラフ画像保存')
print('出力: 1秒間隔での処理結果表示、終了時にresult.txt保存、リアルタイムグラフ表示')
print()
# システム初期化
print('システム初期化中...')
# YOLOv11-faceモデルダウンロード
download_yolo_model()
# GPU/CPU自動フォールバック(簡素化)
device_str = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'{device_str.upper()}使用モード')
# YOLOv11モデル初期化(deviceパラメータを削除)
try:
face_model = YOLO(MODEL_PATH)
print(f'YOLOv11{MODEL_SIZE}-faceモデルの初期化が完了しました')
except Exception as e:
print(f'YOLOv11{MODEL_SIZE}-faceモデルの初期化に失敗しました')
print(f'エラー: {e}')
exit()
# ViT表情認識モデル初期化
try:
emotion_pipeline = pipeline(
'image-classification',
model='trpakov/vit-face-expression',
device=device_str,
framework='pt'
)
print('ViT表情認識モデルの初期化が完了しました')
except Exception as e:
print(f'ViT表情認識モデルの初期化に失敗しました')
print(f'エラー: {e}')
exit()
# 感情グラフ初期化
emotion_chart = EmotionTimeSeriesChart()
print('初期化完了')
print()
# グローバル変数
frame_count = 0
last_print_time = time.time()
last_chart_update = time.time()
results_log = []
def process_face(frame, box, kp):
x1, y1, x2, y2 = map(int, box)
face_img = frame[y1:y2, x1:x2]
if face_img.size == 0:
return None
# 顔向き補正
enhanced_face = face_img
if kp is not None and len(kp) >= 68:
valid_kp = kp[kp[:, 0] > 0]
if len(valid_kp) >= MIN_VALID_KEYPOINTS:
# 目の中心点計算
left_eye = kp[LEFT_EYE_START:LEFT_EYE_END]
right_eye = kp[RIGHT_EYE_START:RIGHT_EYE_END]
left_center = np.mean(left_eye[left_eye[:, 0] > 0], axis=0)
right_center = np.mean(right_eye[right_eye[:, 0] > 0], axis=0)
if len(left_center) == 2 and len(right_center) == 2:
# 傾斜角度計算
angle = np.arctan2(
right_center[1] - left_center[1],
right_center[0] - left_center[0]
) * 180 / np.pi
# 閾値を超える場合は回転補正
if abs(angle) > KEYPOINT_QUALITY_THRESHOLD:
h, w = face_img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, -angle, 1.0)
enhanced_face = cv2.warpAffine(face_img, M, (w, h))
# 表情推定
face_rgb = cv2.cvtColor(enhanced_face, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(face_rgb).resize((IMAGE_SIZE, IMAGE_SIZE), Image.Resampling.LANCZOS)
results = emotion_pipeline(pil_img)
emotions = {r['label'].lower(): r['score'] for r in results}
top = max(emotions.items(), key=lambda x: x[1])
# 信頼度調整
kp_count = len(kp[kp[:, 0] > 0]) if kp is not None else 0
bonus = 0.0
if kp_count >= HIGH_QUALITY_KEYPOINTS:
bonus = HIGH_QUALITY_BONUS
elif kp_count >= MEDIUM_QUALITY_KEYPOINTS:
bonus = MEDIUM_QUALITY_BONUS
return {
'box': (x1, y1, x2, y2),
'emotion': top[0],
'emotion_conf': min(1.0, top[1] + bonus),
'all_emotions': emotions,
'keypoint_count': kp_count
}
def update_emotion_chart(faces):
global last_chart_update
if not CHART_ENABLED or not faces:
return
current_time = time.time()
if current_time - last_chart_update < CHART_UPDATE_INTERVAL:
return
# 複数顔がある場合は平均値を計算
emotion_averages = defaultdict(float)
for face in faces:
for emotion, score in face['all_emotions'].items():
emotion_averages[emotion] += score
# 平均化
for emotion in emotion_averages:
emotion_averages[emotion] /= len(faces)
# グラフにデータを追加
emotion_chart.add_data(dict(emotion_averages))
last_chart_update = current_time
def video_processing(frame):
global frame_count, last_print_time, results_log
frame_count += 1
# 顔検出(信頼度閾値を指定、deviceパラメータは削除)
results = face_model(frame, conf=FACE_CONFIDENCE_THRESHOLD, verbose=False)
faces = []
if results[0].boxes is not None:
boxes = results[0].boxes.xyxy.cpu().numpy()
confs = results[0].boxes.conf.cpu().numpy()
kps = None
if hasattr(results[0], 'keypoints') and results[0].keypoints is not None:
kps = results[0].keypoints.xy.cpu().numpy()
# 各顔の処理
for i, (box, conf) in enumerate(zip(boxes, confs)):
kp = kps[i] if kps is not None and i < len(kps) else None
face_data = process_face(frame, box, kp)
if face_data:
face_data['detection_conf'] = conf
face_data['keypoints'] = kp
faces.append(face_data)
# 感情グラフ更新
update_emotion_chart(faces)
# 1秒間隔での出力
current_time = time.time()
if current_time - last_print_time >= PRINT_INTERVAL and faces:
output = f'フレーム {frame_count}: {len(faces)}顔 -> '
for i, f in enumerate(faces):
emotion_jp = EMOTION_JAPANESE.get(f['emotion'], f['emotion'])
output += f"顔{i+1}:{emotion_jp}({f['emotion_conf']:.0%}) "
print(output)
# 6基本感情の完全スコア表示
for i, f in enumerate(faces):
print(f' 顔{i+1}詳細スコア: ', end='')
basic_scores = []
for emotion in BASIC_EMOTIONS:
score = f['all_emotions'].get(emotion, 0.0)
emotion_jp = EMOTION_JAPANESE.get(emotion, emotion)
basic_scores.append(f'{emotion_jp}:{score:.1%}')
print(' | '.join(basic_scores))
results_log.append(output)
last_print_time = current_time
# 描画処理
for i, f in enumerate(faces):
x1, y1, x2, y2 = f['box']
color = EMOTION_COLORS.get(f['emotion'], (255, 255, 255))
# バウンディングボックス
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# キーポイント描画
if f['keypoints'] is not None:
for kx, ky in f['keypoints']:
if kx > 0 and ky > 0:
cv2.circle(frame, (int(kx), int(ky)), 1, color, -1)
# ラベル表示
emotion_jp = EMOTION_JAPANESE.get(f['emotion'], f['emotion'])
label1 = f"{emotion_jp}: {f['emotion_conf']:.1%}"
label2 = f"Detect:{f['detection_conf']:.1%} | KP:{f['keypoint_count']}"
cv2.putText(frame, label1, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
cv2.putText(frame, label2, (x1, y2+15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
cv2.putText(frame, f'Face {i+1}', (x2-60, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
# システム情報表示
info1 = f'YOLOv11-face + ViT + Chart | Frame: {frame_count} | Faces: {len(faces)}'
info2 = f'Press: q=Quit, c=Save Chart, g=Show Chart'
cv2.putText(frame, info1, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(frame, info2, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1)
return frame, faces
# 入力選択
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, WINDOW_WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, WINDOW_HEIGHT)
cap.set(cv2.CAP_PROP_FPS, FPS)
elif choice == '2':
# サンプル動画ダウンロード
try:
urllib.request.urlretrieve(SAMPLE_VIDEO_URL, SAMPLE_VIDEO_NAME)
temp_file = SAMPLE_VIDEO_NAME
cap = cv2.VideoCapture(SAMPLE_VIDEO_NAME)
print('サンプル動画のダウンロードが完了しました')
except Exception as e:
print(f'動画のダウンロードに失敗しました: {SAMPLE_VIDEO_URL}')
print(f'エラー: {e}')
exit()
else:
print('無効な選択です')
exit()
# 動画処理開始メッセージ
print('\n=== 動画処理開始 ===')
print('操作方法:')
print(' q キー: プログラム終了')
print(' c キー: グラフを画像として保存')
print(' g キー: グラフを表示')
print()
# メイン処理
current_faces = []
try:
while True:
cap.grab()
ret, frame = cap.retrieve()
if not ret:
break
processed_frame, current_faces = video_processing(frame)
cv2.imshow('YOLOv11 + ViT Emotion Detection with Real-time Chart', processed_frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('c'):
# グラフ保存
if CHART_ENABLED:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f'emotion_chart_{timestamp}.png'
if emotion_chart.save_chart(filename):
print(f'感情グラフを {filename} に保存しました')
else:
print('グラフ保存に失敗しました')
else:
print('グラフ機能が無効です')
elif key == ord('g'):
# グラフ表示
if CHART_ENABLED:
emotion_chart.show_chart()
else:
print('グラフ機能が無効です')
finally:
# クリーンアップ
cap.release()
cv2.destroyAllWindows()
# グラフを閉じる
if CHART_ENABLED:
emotion_chart.close()
# 結果保存
if results_log:
with open(RESULT_FILE, 'w', encoding='utf-8') as f:
f.write('=== YOLOv11顔検出+Vision Transformer表情推定結果 ===\n')
f.write(f'処理フレーム数: {frame_count}\n\n')
f.write('\n'.join(results_log))
print(f'処理結果を{RESULT_FILE}に保存しました')
if temp_file and os.path.exists(temp_file):
os.remove(temp_file)
print('\n=== プログラム終了 ===')
使用方法
- 上記のプログラムを実行
- カメラ画面に顔を向ける
- リアルタイムで表情認識結果が表示される
s
キー:詳細情報表示q
キー:プログラム終了
表示内容の見方
- 顔周囲のボックス色:認識された表情に対応
- 表情ラベル:日本語表記での表情名と信頼度
- キーポイント:68点の顔特徴点(小さな点)
- 詳細情報(
s
キー):各表情の詳細スコア
実験・探求のアイデア
モデル選択実験
利用可能なYOLOv11-faceモデル:
yolov11n-face.pt
(軽量・高速)yolov11s-face.pt
(標準)yolov11m-face.pt
(中程度)yolov11l-face.pt
(高精度・重い)
プログラム内のMODEL_PATH
を変更して各モデルの性能差を比較できる。
追加要素
- 精度比較実験
- 異なるモデルサイズでの認識精度の違い
- 同一人物での複数回測定による一貫性確認
- 複数人同時認識時の処理性能比較
- キーポイント効果検証
- 顔の角度を変えた際の認識精度変化
- 表情遷移分析
- 表情変化時の認識応答速度測定
- 中間表情での各感情スコア分布観察
- 表情の意図的演技と自然表情の認識差異
体験・実験・探求のアイデア
- 表情推定技術の特性理解
- 基本7感情の認識精度確認(意図的表情と自然表情の比較)
- 表情強度変化に対する認識スコアの遷移観察
- 別の人が同じ表情をした際の個人差による認識結果比較
- 技術的新発見の促進
- 表情推定における顔向き・照明の影響確認
- 応用可能性の探求
- 感情変化パターンの記録・分析による心理状態モニタリング
- グループ内感情分布の可視化による集団心理分析
- 表情トレーニング,感情認識教育ツール