본문 바로가기

개발/졸업프로젝트

[Mediapipe] mediapipe holistic으로 동영상에서 관절 좌표 뽑기

https://github.com/google/mediapipe/blob/master/docs/solutions/holistic.md

 

mediapipe/docs/solutions/holistic.md at master · google/mediapipe

Cross-platform, customizable ML solutions for live and streaming media. - google/mediapipe

github.com

포즈, 양 손 좌표까지 뽑는 것이 목적이었으나, 해당 공식 문서 내에 이미지와 웹캠에 대해 좌표를 뽑는 것만 나와있어서 동영상에서 좌표 뽑는 것으로 코드 수정

 

데이터는 ai hub 내 수어영상 데이터를 사용하였다.

 

1. mediapipe 설치

#mdeiapipe 다운로드
#로컬 환경에서 돌릴 경우: pip install mediapipe opencv-python
!pip install mediapipe

2. 동영상 위에서 mediapipe 돌리기 (pose만) : 예제에는 pose 좌표를 활용하는 법만 나와있었다.

import cv2
import mediapipe as mp
import numpy as np
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_pose = mp.solutions.pose

#파일 위치 지정
input_video_path = "/content/drive/MyDrive/NIA_SL_WORD1501_REAL17_F"
save_video_path = '/content/drive/MyDrive/result_NIA_SL_WORD1501_REAL17_F_onlypose.mp4'

cap = cv2.VideoCapture(input_video_path)

#재생할 파일의 넓이와 높이
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
#video controller
fourcc = cv2.VideoWriter_fourcc(*'DIVX')
out = cv2.VideoWriter(save_video_path, fourcc, 30.0, (int(width), int(height)))


with mp_pose.Pose(
        min_detection_confidence=0.7,
        min_tracking_confidence=0.7) as pose:
    while cap.isOpened():
        success, image = cap.read()
        if not success:
            print("카메라를 찾을 수 없습니다.")
            # 웹캠을 불러올 경우는 'continue', 동영상을 불러올 경우 'break'를 사용합니다.
            break

        # 필요에 따라 성능 향상을 위해 이미지 작성을 불가능함으로 기본 설정합니다.
        image.flags.writeable = False
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        results = pose.process(image)

        # 포즈 주석을 이미지 위에 그립니다.
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        mp_drawing.draw_landmarks(
            image,
            results.pose_landmarks,
            mp_pose.POSE_CONNECTIONS,
            landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
        # 보기 편하게 이미지를 좌우 반전합니다.
        out.write(image)
        if cv2.waitKey(5) & 0xFF == 27:
            break
cap.release()
out.release()
cv2.destroyAllWindows()

3. 동영상 위에서 mediapipe 돌리기 - pose, lefthand, righthand

(face까지 하려했으나, 지금의 좌표만으로도 충분하다고 생각해서 face는 X)

import cv2
import mediapipe as mp
import numpy as np
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_holistic = mp.solutions.holistic

#파일 위치 미리 지정
input_video_path = "/content/drive/MyDrive/data preprocessing/testdata/NIA_SL_WORD1501_REAL17_F.mp4"
save_video_path = '/content/drive/MyDrive/result_test_NIA_SL_WORD1501_REAL17_F_withHands.mp4'

cap = cv2.VideoCapture(input_video_path)

#재생할 파일의 넓이와 높이
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
#video controller
fourcc = cv2.VideoWriter_fourcc(*'DIVX')
out = cv2.VideoWriter(save_video_path, fourcc, 30.0, (int(width), int(height)))


with mp_holistic.Holistic(
        min_detection_confidence=0.7,
        min_tracking_confidence=0.7) as pose:
    while cap.isOpened():
        success, image = cap.read()
        if not success:
            print("카메라를 찾을 수 없습니다.")
            # 웹캠을 불러올 경우는 'continue', 동영상을 불러올 경우 'break'를 사용합니다.
            break

        # 필요에 따라 성능 향상을 위해 이미지 작성을 불가능함으로 기본 설정합니다.
        image.flags.writeable = False
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        results = pose.process(image)

        # 포즈 주석을 이미지 위에 그립니다.
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        #포즈
        #코드 합쳐도 나오는지 보기, 좌표 출력해서 텍스트 or csv or json 파일 만들어보기, 로컬에서 돌리면 10gb짜리 동영상 돌렸을 때 얼마나 나오는지
        mp_drawing.draw_landmarks(
            image,
            results.pose_landmarks,
            mp_pose.POSE_CONNECTIONS,
            landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
        mp_drawing.draw_landmarks(
            image,
            results.left_hand_landmarks,
            mp_holistic.HAND_CONNECTIONS,
            landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style())
        mp_drawing.draw_landmarks(
            image,
            results.right_hand_landmarks,
            mp_holistic.HAND_CONNECTIONS,
            landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style())
        # 보기 편하게 이미지를 좌우 반전합니다.
       # cv2.imshow('MediaPipe Pose', image) #코랩에서 돌릴거면 imshow()문은 주석처리할 것.
        out.write(image)
        if cv2.waitKey(5) & 0xFF == 27:
            break
cap.release()
out.release()
cv2.destroyAllWindows()

Hand관련 메서드들은 https://github.com/google/mediapipe/blob/master/docs/solutions/hands.md 여기서 확인 가능

 

차례대로 원본, pose만, hand까지 좌표를 뽑은 동영상들이다.

 

4. 좌표 출력

import cv2
import mediapipe as mp
import numpy as np
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_holistic = mp.solutions.holistic

#파일 위치 미리 지정
input_video_path = "/content/drive/MyDrive/data preprocessing/testdata/NIA_SL_WORD1501_REAL17_F.mp4"
save_video_path = '/content/drive/MyDrive/result_NIA_SL_WORD1501_REAL17_F.mp4'

cap = cv2.VideoCapture(input_video_path)

#재생할 파일의 넓이와 높이
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
#video controller
fourcc = cv2.VideoWriter_fourcc(*'DIVX')


with mp_holistic.Holistic(
        min_detection_confidence=0.7,
        min_tracking_confidence=0.7) as pose:
    while cap.isOpened():
        success, image = cap.read()
        if not success:
            print("카메라를 찾을 수 없습니다.") #웹캠 사용 안 할 경우 출력
            # 웹캠을 불러올 경우는 'continue', 동영상을 불러올 경우 'break'를 사용합니다.
            break

        # 필요에 따라 성능 향상을 위해 이미지 작성을 불가능함으로 기본 설정합니다.
        image.flags.writeable = False
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        results = pose.process(image)

        if results.pose_landmarks:
          print(
              f'Nose coordinates: ('
              f'{results.pose_landmarks.landmark[mp_holistic.PoseLandmark.NOSE].x * width}, '
              f'{results.pose_landmarks.landmark[mp_holistic.PoseLandmark.NOSE].y * height})'
              )
          #포즈 좌표 출력
        if results.pose_landmarks:
          print("Pose Landmarks:")
          for landmark in results.pose_landmarks.landmark:
            landmark_x = landmark.x * width
            landmark_y = landmark.y * height
            print(f"({landmark_x}, {landmark_y})")

        #   #얼굴 좌표출력 -> pose에서 추출되는 얼굴 좌표만으로도 될 것 같은데
        # if results.face_landmarks:
        #   print("Face Landmarks:")
        #   for landmark in results.face_landmarks.landmark:
        #     landmark_x = landmark.x * width
        #     landmark_y = landmark.y * height
        #     print(f"({landmark_x}, {landmark_y})")

            #왼손 좌표 출력
        if results.left_hand_landmarks:
          print("Left Hand Landmarks:")
          for landmark in results.left_hand_landmarks.landmark:
            landmark_x = landmark.x * width
            landmark_y = landmark.y * height
            print(f"({landmark_x}, {landmark_y})")

            #오른손 좌표 출력
        if results.right_hand_landmarks:
          print("Right Hand Landmarks:")
          for landmark in results.right_hand_landmarks.landmark:
            landmark_x = landmark.x * width
            landmark_y = landmark.y * height
            print(f"({landmark_x}, {landmark_y})")

cap.release()
out.release()
cv2.destroyAllWindows()

이런 형태로 좌표가 쭉 출력된다.

5. morpheme 파일(영상 의미가 mapping되어있는 json파일)에서 의미 뽑고, 영상에서 키포인트를 뽑아서 json파일로 저장

import json
import cv2
import mediapipe as mp
import numpy as np
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_holistic = mp.solutions.holistic

# JSON 파일에 저장할 데이터를 담을 딕셔너리 초기화
data_dict = {
    "pose_keypoint": [],
    "left_hand_keypoint": [],
    "right_hand_keypoint": [],
    "meaning": ""
}

# 동일 파일이름_morpheme.json에서 의미를 가져와서 data_dict에 추가하는 함수
def get_meaning(file_name):
    morpheme_file_path = "/content/drive/MyDrive/" + file_name.split('.')[0] + "_morpheme.json"
    try:
        with open(morpheme_file_path, 'r') as f:
            morpheme_data = json.load(f)
            # 여기서는 첫 번째 attributes에 있는 첫 번째 name 값을 가져옵니다.
            meaning = morpheme_data["data"][0]["attributes"][0]["name"]
            return meaning
    except FileNotFoundError:
        print(f"Warning: {morpheme_file_path} 파일이 존재하지 않습니다.")
        return None

# MediaPipe 결과에서 좌표값을 JSON 형태로 변환하여 data_dict에 추가하는 함수
def add_landmarks_to_data(landmarks, keypoint_name):
    keypoints = []
    for landmark in landmarks:
        keypoints.append({"x": landmark.x, "y": landmark.y})
    data_dict[keypoint_name] = keypoints

# 파일 경로 설정
input_video_path = "/content/drive/MyDrive/NIA_SL_WORD1501_REAL17_F.mp4"
output_json_path = '/content/drive/MyDrive/NIA_SL_WORD1501_REAL17_F_keypoints.json'

cap = cv2.VideoCapture(input_video_path)

# MediaPipe 초기화 및 동영상 불러오기
with mp_holistic.Holistic(min_detection_confidence=0.7, min_tracking_confidence=0.7) as holistic:
    while cap.isOpened():
        success, image = cap.read()
        if not success:
            print("카메라를 찾을 수 없습니다.")
            break

        image.flags.writeable = False
        
        results = holistic.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

        # 좌표값 추출 및 JSON에 추가
        if results.pose_landmarks:
            add_landmarks_to_data(results.pose_landmarks.landmark, "pose_keypoint")
        if results.left_hand_landmarks:
            add_landmarks_to_data(results.left_hand_landmarks.landmark, "left_hand_keypoint")
        if results.right_hand_landmarks:
            add_landmarks_to_data(results.right_hand_landmarks.landmark, "right_hand_keypoint")

        # 동일 파일이름_morpheme.json에서 의미 가져와서 추가
        file_name = input_video_path.split('/')[-1]
        meaning = get_meaning(file_name)
        if meaning:
            data_dict["meaning"] = meaning

        # JSON 파일로 저장
        with open(output_json_path, 'w', encoding='utf-8') as json_file:
            json.dump(data_dict, json_file, indent=4, ensure_ascii=False)

        if cv2.waitKey(5) & 0xFF == 27:
            break

cap.release()
cv2.destroyAllWindows()

*한글로 의미가 되어있기에, utf-8로 인코딩을 꼭 진행해주어야한다.

이런 형태로 json파일이 잘 만들어진 것을 확인가능!