# [メタ情報] # 識別子: 自作パペット_py_exe # 補足: # [/メタ情報] 要約: このPythonスクリプトは、Pygame、OpenCV、MediaPipeを用いて、ウェブカメラとマイクからの入力に基づきリアルタイムでアニメーションするアバター(パペット)システムを実装しています。主な機能は、検出された顔の傾き、視線の方向、口の開閉をアバターの頭の傾き、目の動き、口の形に反映させることです。マイクからの音声レベルやカメラでの口の開閉もアバターの口の動きに連動します。 新たに`json`ファイルを利用して顔認識のキャリブレーション設定(顔の基準角度や目の位置)を自動的にロード・セーブする機能があります。これにより、システム起動時に前回の設定が適用され、ユーザーは`SPACE`キーで現在の顔を新たな基準として保存できます。画面左上にはカメラ映像の一部と、システムのトラッキング状態(WAITING, READY, TRACKING)が表示されます。このシステムは、特定のディレクトリでPython仮想環境を有効化した後、付属のシェルスクリプトによって実行されるように設計されています。 /Users/XXXXXX/mypuppet_system/avatar_module/run_puppet.py ``` import pygame import sys import cv2 import sounddevice as sd import numpy as np import os import random import math import mediapipe as mp import json # ★記憶ファイルを作るためのモジュールを追加 # --- 1. 基本設定 --- WINDOW_TITLE = "XXXXXX system - Auto Memory Edition" WINDOW_WIDTH = 800 WINDOW_HEIGHT = 600 FPS = 30 GREEN_SCREEN = (0, 255, 0) VOLUME_THRESHOLD = 0.8 BODY_SCALE = 0.8 HEAD_SCALE = 0.5 EYE_SCALE = 0.5 MOUTH_SCALE = 0.5 FIXED_NECK_X = 400 FIXED_NECK_Y = 380 BODY_CENTER_Y = 480 HEAD_UP_OFFSET = 25 EYE_DIST = 35 EYE_Y_OFFSET = 10 MOUTH_Y_OFFSET = 80 SMOOTH_FACTOR = 0.05 MAX_TILT = 12.5 RETURN_FORCE = 0.98 EYE_SENSITIVITY = 0.015 current_volume = 0 current_tilt = 0 def audio_callback(indata, frames, time, status): global current_volume current_volume = np.linalg.norm(indata) * 15 def enhance_for_ai(image): img_yuv = cv2.cvtColor(image, cv2.COLOR_RGB2YUV) clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(8,8)) img_yuv[:,:,0] = clahe.apply(img_yuv[:,:,0]) return cv2.cvtColor(img_yuv, cv2.COLOR_YUV2RGB) def load_and_scale(name, scale): current_dir = os.path.dirname(os.path.abspath(__file__)) path = os.path.join(current_dir, "assets", name) if os.path.exists(path): img = pygame.image.load(path).convert_alpha() w, h = img.get_size() return pygame.transform.scale(img, (int(w * scale), int(h * scale))) return None def main(): global current_tilt pygame.init() screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption(WINDOW_TITLE) clock = pygame.time.Clock() font = pygame.font.Font(None, 24) eyes = {k: load_and_scale(f"eye_{v}.png", EYE_SCALE) for k, v in {"front":"open", "up":"up", "down":"down", "left":"left", "right":"right", "blink":"close"}.items()} mouths = {k: load_and_scale(f"mouth_{v}.png", MOUTH_SCALE) for k, v in {"a":"a", "i":"i", "u":"u", "close":"close"}.items()} img_body = load_and_scale("body.png", BODY_SCALE) img_head = load_and_scale("head.png", HEAD_SCALE) stream = sd.InputStream(callback=audio_callback, channels=1, samplerate=44100) stream.start() cap = cv2.VideoCapture(1) cap.set(cv2.CAP_PROP_BRIGHTNESS, 180) mp_face_mesh = mp.solutions.face_mesh face_mesh = mp_face_mesh.FaceMesh( max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.15, min_tracking_confidence=0.15 ) # --- ★ 設定の自動ロード処理 --- current_dir = os.path.dirname(os.path.abspath(__file__)) calib_file = os.path.join(current_dir, "calibration.json") base_angle = 0 base_iris_x, base_iris_y = 0.5, 0.5 tracking_enabled = False # ファイルがあれば読み込んで、最初から同期モード(True)にする if os.path.exists(calib_file): try: with open(calib_file, "r") as f: data = json.load(f) base_angle = data.get("base_angle", 0) base_iris_x = data.get("base_iris_x", 0.5) base_iris_y = data.get("base_iris_y", 0.5) tracking_enabled = True print(">>> 前回の設定を読み込みました!") except: print(">>> 設定ファイルの読み込みに失敗しました。") last_detected_angle = 0 last_iris_x, last_iris_y = 0.5, 0.5 is_blinking = False current_mouth_type = "close" current_eye_type = "front" blink_timer = 0 mouth_change_timer = 0 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # --- ★ 設定の自動セーブ処理 --- if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: base_angle = last_detected_angle base_iris_x, base_iris_y = last_iris_x, last_iris_y current_tilt = 0 tracking_enabled = True # 新しい基準値をファイルに保存 try: with open(calib_file, "w") as f: json.dump({ "base_angle": base_angle, "base_iris_x": base_iris_x, "base_iris_y": base_iris_y }, f) print(">>> 新しい基準を保存しました!") except: print(">>> 設定の保存に失敗しました。") screen.fill(GREEN_SCREEN) ret, frame = cap.read() target_tilt = 0 target_eye = "front" camera_mouth_open = False is_detected = False if ret: frame = cv2.flip(frame, 1) rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) ai_frame = enhance_for_ai(rgb_frame) results = face_mesh.process(ai_frame) is_detected = True if results and results.multi_face_landmarks else False try: h, w, _ = rgb_frame.shape crop_w, crop_h = int(w * 0.40), int(h * 0.40) x1, y1 = (w - crop_w) // 2, (h - crop_h) // 2 zoom_img = rgb_frame[y1:y1+crop_h, x1:x1+crop_w] wipe_w, wipe_h = 180, 135 wipe_img = cv2.resize(zoom_img, (wipe_w, wipe_h)) wipe_surf = pygame.image.frombuffer(wipe_img.tobytes(), wipe_img.shape[1::-1], "RGB") screen.blit(wipe_surf, (10, 10)) pygame.draw.rect(screen, (0, 0, 0), (10, 10, wipe_w, wipe_h), 2) except: pass if is_detected: for face_landmarks in results.multi_face_landmarks: l_pt, r_pt = face_landmarks.landmark[133], face_landmarks.landmark[362] dx, dy = r_pt.x - l_pt.x, r_pt.y - l_pt.y last_detected_angle = math.degrees(math.atan2(dy, dx)) iris = face_landmarks.landmark[468] last_iris_x, last_iris_y = iris.x, iris.y if tracking_enabled: target_tilt = -(last_detected_angle - base_angle) target_tilt = max(min(target_tilt, MAX_TILT), -MAX_TILT) diff_x, diff_y = last_iris_x - base_iris_x, last_iris_y - base_iris_y if diff_x < -EYE_SENSITIVITY: target_eye = "left" elif diff_x > EYE_SENSITIVITY: target_eye = "right" elif diff_y < -EYE_SENSITIVITY: target_eye = "up" elif diff_y > EYE_SENSITIVITY: target_eye = "down" else: target_eye = "front" m_dist = abs(face_landmarks.landmark[13].y - face_landmarks.landmark[14].y) if m_dist > 0.02: camera_mouth_open = True current_tilt += (target_tilt - current_tilt) * (SMOOTH_FACTOR if is_detected else 0.05) current_tilt *= RETURN_FORCE blink_timer += 1 if not is_blinking and random.random() < 0.02: is_blinking = True; blink_timer = 0 if is_blinking and blink_timer > 4: is_blinking = False if camera_mouth_open or current_volume > VOLUME_THRESHOLD: mouth_change_timer += 1 if mouth_change_timer > 3: current_mouth_type = random.choice(["a", "i", "u"]) mouth_change_timer = 0 current_eye_type = target_eye if is_detected else "front" else: current_mouth_type = "close" current_eye_type = "front" if not is_detected else target_eye if img_body: screen.blit(img_body, img_body.get_rect(center=(400, BODY_CENTER_Y))) if img_head: hw, hh = img_head.get_size() canvas_size = 1200 temp_canvas = pygame.Surface((canvas_size, canvas_size), pygame.SRCALPHA) pivot_x, pivot_y = 600, 600 head_draw_x, head_draw_y = pivot_x - hw // 2, pivot_y - hh - HEAD_UP_OFFSET temp_canvas.blit(img_head, (head_draw_x, head_draw_y)) face_center_x, face_center_y = pivot_x, head_draw_y + hh // 2 eye_key = "blink" if is_blinking else current_eye_type cur_eye = eyes.get(eye_key, eyes["front"]) if cur_eye: ew, eh = cur_eye.get_size() temp_canvas.blit(cur_eye, (face_center_x - EYE_DIST - ew//2, face_center_y + EYE_Y_OFFSET - eh//2)) temp_canvas.blit(cur_eye, (face_center_x + EYE_DIST - ew//2, face_center_y + EYE_Y_OFFSET - eh//2)) img_mouth = mouths.get(current_mouth_type, mouths["close"]) if img_mouth: mw, mh = img_mouth.get_size() temp_canvas.blit(img_mouth, (face_center_x - mw//2, face_center_y + MOUTH_Y_OFFSET - mh//2)) rotated_head = pygame.transform.rotate(temp_canvas, current_tilt) rot_rect = rotated_head.get_rect() rot_rect.center = (FIXED_NECK_X, FIXED_NECK_Y) screen.blit(rotated_head, rot_rect) # ステータス表示 if not is_detected: status_text = "WAITING" elif not tracking_enabled: status_text = "READY (SPACE)" else: status_text = "TRACKING" bg_rect = pygame.Rect(10, 150, 180, 26) pygame.draw.rect(screen, (255, 255, 255), bg_rect) pygame.draw.rect(screen, (0, 0, 0), bg_rect, 2) text_surface = font.render(status_text, True, (0, 0, 0)) text_rect = text_surface.get_rect(center=bg_rect.center) screen.blit(text_surface, text_rect) pygame.display.flip() clock.tick(FPS) stream.stop() cap.release() face_mesh.close() pygame.quit() sys.exit() if __name__ == "__main__": main() ``` Automator 自作パペット起動.app シェル:/bin/zsh 入力の引き渡し方法 stdinへ ``` # 仮想環境に入って、プログラムを実行する cd /Users/XXXXXX/mypuppet_system source venv/bin/activate python avatar_module/run_puppet.py ```