ah4485260
@ah4485260

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

webカメラの認識した動きをbvhに変換するプログラムの作成に行き詰まっている

解決したいこと

タイトル通りwebカメラで認識した動きをbvhに変換するpythonのプログラムを作成しているのですがbvhファイルを出力するところまではできたのですがそのbvhファイルをいざblenderにインポートしてみたところ明らかに人型ではない3dモデルが出力されてしまいます。色々と修正したのですが一向に治りません
どなたか解決方法を教えて下さい。

以下ソースコード

import cv2
import mediapipe as mp
import numpy as np
from datetime import datetime
import time
import tkinter as tk
from tkinter import ttk
import threading
from PIL import Image, ImageTk
from scipy.signal import savgol_filter
import ssl
import os
import logging

# 警告メッセージの抑制 
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
logging.getLogger('mediapipe').setLevel(logging.ERROR)

# SSL証明書の検証をスキップ
ssl._create_default_https_context = ssl._create_unverified_context


class MotionToBVH:
    def __init__(self):
        self.mp_pose = mp.solutions.pose
        self.pose = mp.solutions.pose.Pose(
            min_detection_confidence=0.5,
            min_tracking_confidence=0.5,
            model_complexity=2,
            enable_segmentation=True,
            smooth_landmarks=True
        )
        self.landmarks_history = []
        self.smoothed_landmarks = []
        self.is_recording = False
        self.calibrated = False
        self.base_rotation = None
        self.gui = None
        self.video_thread = None
        self.camera_active = True
        self.frame_count = 0
        
    def setup_gui(self):
        self.gui = tk.Tk()
        self.gui.title("Motion Capture to BVH")
        
        main_frame = ttk.Frame(self.gui, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        self.camera_label = ttk.Label(main_frame)
        self.camera_label.grid(row=0, column=0, columnspan=3, padx=5, pady=5)

        control_frame = ttk.Frame(main_frame)
        control_frame.grid(row=1, column=0, columnspan=3, pady=5)

        self.start_button = tk.Button(
            control_frame, 
            text="Start Recording",
            command=self.toggle_recording,
            width=15,
            height=2
        )
        self.start_button.grid(row=0, column=0, padx=5)

        self.calibrate_button = tk.Button(
            control_frame,
            text="Calibrate",
            command=self.calibrate,
            width=15,
            height=2
        )
        self.calibrate_button.grid(row=0, column=1, padx=5)

        self.save_button = tk.Button(
            control_frame,
            text="Save BVH",
            command=self.save_bvh,
            width=15,
            height=2
        )
        self.save_button.grid(row=0, column=2, padx=5)

        self.frame_label = ttk.Label(main_frame, text="Frames: 0", font=('Arial', 12, 'bold'))
        self.frame_label.grid(row=2, column=0, columnspan=3, pady=5)

        self.status_label = ttk.Label(main_frame, text="Ready")
        self.status_label.grid(row=3, column=0, columnspan=3, pady=5)

        self.progress = ttk.Progressbar(
            main_frame,
            length=300,
            mode='determinate'
        )
        self.progress.grid(row=4, column=0, columnspan=3, pady=5)

        self.gui.update_idletasks()
        self.gui.after(100, self.update_gui)

        self.video_thread = threading.Thread(target=self.update_camera)
        self.video_thread.daemon = True
        self.video_thread.start()

    def toggle_recording(self):
        self.is_recording = not self.is_recording
        if self.is_recording:
            self.start_button.config(text="Stop Recording", bg='red')
            self.landmarks_history = []
            self.frame_count = 0
            self.status_label.config(text="Recording...")
        else:
            self.start_button.config(text="Start Recording", bg='SystemButtonFace')
            self.status_label.config(text=f"Recorded {self.frame_count} frames")
            self.update_gui()

    def update_gui(self):
        self.gui.update_idletasks()
        self.gui.after(100, self.update_gui)

    def update_camera(self):
        try:
            cap = cv2.VideoCapture(0, cv2.CAP_AVFOUNDATION)
            
            if not cap.isOpened():
                self.status_label.config(text="カメラを開けませんでした")
                return

            cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
            cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
            cap.set(cv2.CAP_PROP_FPS, 30)

            time.sleep(1)

            while self.camera_active and cap.isOpened():
                try:
                    ret, frame = cap.read()
                    if not ret:
                        continue

                    image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    results = self.pose.process(image)

                    if results.pose_landmarks:
                        self.draw_landmarks(image, results.pose_landmarks)
                        if self.is_recording:
                            self.landmarks_history.append(results.pose_landmarks.landmark)
                            self.frame_count += 1
                            if len(self.landmarks_history) >= 5:
                                self.smoothed_landmarks = self.smooth_landmarks(
                                    self.landmarks_history[-5:]
                                )
                            self.gui.after(0, lambda: self.frame_label.config(
                                text=f"Frames: {self.frame_count}",
                                foreground='red' if self.is_recording else 'black'
                            ))

                    self.update_camera_frame(image)

                except Exception as e:
                    print(f"フレーム処理エラー: {e}")
                    continue

        except Exception as e:
            print(f"カメラエラー: {e}")
            self.status_label.config(text=f"カメラエラー: {e}")

        finally:
            if 'cap' in locals():
                cap.release()

    def calibrate(self):
        self.calibrate_button.config(state='disabled', bg='yellow')
        self.status_label.config(text="Calibrating... Stand in T-pose")
        
        def calibration_thread():
            self.calibrated = False
            time.sleep(3)

            if hasattr(self, 'pose') and self.pose is not None:
                cap = cv2.VideoCapture(0)
                ret, frame = cap.read()
                cap.release()
                
                if ret:
                    results = self.pose.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
                    if results and results.pose_landmarks:
                        self.base_rotation = self.calculate_base_rotation(
                            results.pose_landmarks.landmark
                        )
                        self.calibrated = True
                        self.gui.after(0, lambda: self.status_label.config(
                            text="Calibration Complete"
                        ))
                    else:
                        self.gui.after(0, lambda: self.status_label.config(
                            text="Calibration Failed"
                        ))
            
            self.gui.after(0, lambda: self.calibrate_button.config(
                state='normal', bg='SystemButtonFace'
            ))

        threading.Thread(target=calibration_thread, daemon=True).start()

    def update_camera_frame(self, frame):
        try:
            image = Image.fromarray(frame)
            image = image.resize((640, 480), Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(image=image)
            self.camera_label.configure(image=photo)
            self.camera_label.image = photo
        except Exception as e:
            print(f"フレーム更新エラー: {e}")

    def draw_landmarks(self, image, landmarks):
        mp_drawing = mp.solutions.drawing_utils
        mp_drawing.draw_landmarks(
            image,
            landmarks,
            self.mp_pose.POSE_CONNECTIONS,
            mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=2),
            mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2)
        )

    def calculate_base_rotation(self, landmarks):
        left_hip = np.array([landmarks[23].x, landmarks[23].y, landmarks[23].z])
        right_hip = np.array([landmarks[24].x, landmarks[24].y, landmarks[24].z])
        hip_vector = right_hip - left_hip
        angle = np.arctan2(hip_vector[1], hip_vector[0])
        return angle

    def normalize_vector(self, v):
        norm = np.linalg.norm(v)
        return v / norm if norm > 0 else v

    def smooth_landmarks(self, landmarks_history, window=5):
        if len(landmarks_history) < window:
            return landmarks_history

        smoothed = []
        num_landmarks = len(landmarks_history[0])
        
        for i in range(num_landmarks):
            x_data = [frame[i].x for frame in landmarks_history]
            y_data = [frame[i].y for frame in landmarks_history]
            z_data = [frame[i].z for frame in landmarks_history]
            
            if len(x_data) >= window:
                x_smooth = savgol_filter(x_data, window, 3)
                y_smooth = savgol_filter(y_data, window, 3)
                z_smooth = savgol_filter(z_data, window, 3)
                
                current_landmark = type('', (), {})()
                current_landmark.x = float(x_smooth[-1])
                current_landmark.y = float(y_smooth[-1])
                current_landmark.z = float(z_smooth[-1])
                
                smoothed.append(current_landmark)
            else:
                smoothed.append(landmarks_history[-1][i])
        
        return smoothed

    def create_hierarchy(self):
        hierarchy = [
            "HIERARCHY",
            "ROOT Hips",
            "{",
            "\tOFFSET 0.00 0.00 0.00",
            "\tCHANNELS 3 Zrotation Yrotation Xrotation",
            "\tJOINT LeftUpLeg",
            "\t{",
            "\t\tOFFSET 3.91 0.00 0.00",
            "\t\tCHANNELS 3 Zrotation Yrotation Xrotation",
            "\t\tJOINT LeftLeg",
            "\t\t{",
            "\t\t\tOFFSET 0.00 -18.34 0.00",
            "\t\t\tCHANNELS 3 Zrotation Yrotation Xrotation",
            "\t\t\tEnd Site",
            "\t\t\t{",
            "\t\t\t\tOFFSET 0.00 -17.37 0.00",
            "\t\t\t}",
            "\t\t}",
            "\t}",
            "\tJOINT RightUpLeg",
            "\t{",
            "\t\tOFFSET -3.91 0.00 0.00",
            "\t\tCHANNELS 3 Zrotation Yrotation Xrotation",
            "\t\tJOINT RightLeg",
            "\t\t{",
            "\t\t\tOFFSET 0.00 -18.34 0.00",
            "\t\t\tCHANNELS 3 Zrotation Yrotation Xrotation",
            "\t\t\tEnd Site",
            "\t\t\t{",
            "\t\t\t\tOFFSET 0.00 -17.37 0.00",
            "\t\t\t}",
            "\t\t}",
            "\t}",
            "}"
        ]
        return "\n".join(hierarchy)

    def calculate_joint_angles(self, landmarks):
        angles = []
        
        # Hips rotation
        angles.extend([0.0, 0.0, 0.0])
        
        # LeftUpLeg
        hip_left = np.array([landmarks[23].x, landmarks[23].y, landmarks[23].z])
        knee_left = np.array([landmarks[25].x, landmarks[25].y, landmarks[25].z])
        hip_knee_left = self.normalize_vector(knee_left - hip_left)
        angles.extend([45.0, 0.0, 0.0])
        
        # LeftLeg
        ankle_left = np.array([landmarks[27].x, landmarks[27].y, landmarks[27].z])
        knee_ankle_left = self.normalize_vector(ankle_left - knee_left)
        knee_angle_left = np.clip(np.degrees(np.arccos(
            np.dot(hip_knee_left, knee_ankle_left))), 0, 160)
        angles.extend([knee_angle_left, 0.0, 0.0])
        
        # RightUpLeg
        hip_right = np.array([landmarks[24].x, landmarks[24].y, landmarks[24].z])
        knee_right = np.array([landmarks[26].x, landmarks[26].y, landmarks[26].z])
        hip_knee_right = self.normalize_vector(knee_right - hip_right)
        angles.extend([-45.0, 0.0, 0.0])
        
        # RightLeg
        ankle_right = np.array([landmarks[28].x, landmarks[28].y, landmarks[28].z])
        knee_ankle_right = self.normalize_vector(ankle_right - knee_right)
        knee_angle_right = np.clip(np.degrees(np.arccos(
            np.dot(hip_knee_right, knee_ankle_right))), 0, 160)
        angles.extend([-knee_angle_right, 0.0, 0.0])
        
        return angles

    def create_bvh(self, output_file):
        if not self.landmarks_history:
            return False

        try:
            with open(output_file, 'w') as f:
                f.write(self.create_hierarchy())
                f.write("\nMOTION\n")
                f.write(f"Frames: {len(self.landmarks_history)}\n")
                f.write("Frame Time: 0.033333\n")
                
                total_frames = len(self.landmarks_history)
                for i, frame_landmarks in enumerate(self.landmarks_history):
                    angles = self.calculate_joint_angles(frame_landmarks)
                    f.write(" ".join([f"{angle:.6f}" for angle in angles]) + "\n")
                    
                    self.progress['value'] = (i + 1) / total_frames * 100
                    self.gui.update_idletasks()
                    
            return True
        except Exception as e:
            print(f"Error creating BVH file: {e}")
            return False

    def save_bvh(self):
        self.save_button.config(state='disabled')
        if not self.landmarks_history:
            self.status_label.config(text="No motion data to save")
            self.save_button.config(state='normal')
            return

        def save_thread():
            filename = f"motion_capture_{datetime.now().strftime('%Y%m%d_%H%M%S')}.bvh"
            if self.create_bvh(filename):
                self.gui.after(0, lambda: self.status_label.config(text=f"Saved as {filename}"))
            else:
                self.gui.after(0, lambda: self.status_label.config(text="Failed to save BVH file"))
            self.gui.after(0, lambda: self.save_button.config(state='normal'))

        threading.Thread(target=save_thread, daemon=True).start()

    def run(self):
        self.setup_gui()
        self.gui.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.gui.mainloop()

    def on_closing(self):
        self.camera_active = False
        if self.video_thread:
            self.video_thread.join(timeout=1.0)
        self.gui.destroy()


def main():
    app = MotionToBVH()
    app.run()


if __name__ == "__main__":
    main()

スクリーンショット 2024-12-11 22.45.57.png

0

No Answers yet.

Your answer might help someone💌