🔗소프트웨어 링크바로가기

핵심 기능

사용 방법

  1. 작업 공간의 언어 설정이 Python으로 설정돼 있는지 확인합니다.

    image.png

  2. 실행 버튼을 클릭하고 카메라 권한을 허용해줍니다.

    image.png

  3. Python Runner 공간에 화면이 정상적으로 보이는지 확인합니다.

    image.png

전체 코드

import cv2
import mediapipe as mp
import math
import serial
import time

# ============================
# 1. 보드 연결
# ============================
board = serial.Serial('COM14', 9600)  # 포트는 환경에 맞게 수정
time.sleep(2)

# ============================
# 2. MediaPipe Pose 설정
# ============================
mp_pose = mp.solutions.pose  # Pose 솔루션 모듈 가져오기
pose = mp_pose.Pose()

# ============================
# 3. 포즈 랜드마크 번호
# ============================
# ----- 왼쪽 팔 -----
BP1_ID = 23   # 왼쪽 골반
BP2_ID = 11   # 왼쪽 어깨 (각도 기준점)
BP3_ID = 13   # 왼쪽 팔꿈치

# ----- 오른쪽 팔 -----
BP4_ID = 24   # 오른쪽 골반
BP5_ID = 12   # 오른쪽 어깨 (각도 기준점)
BP6_ID = 14   # 오른쪽 팔꿈치

# ============================
# 4. 각도 계산 함수
# ============================
def calculate_angle(a, b, c):
    """
    세 점 a, b, c가 있을 때
    b를 기준으로 한 각도를 계산
    """
    ba = (a[0] - b[0], a[1] - b[1])
    bc = (c[0] - b[0], c[1] - b[1])

    cosine_angle = (ba[0]*bc[0] + ba[1]*bc[1]) / \\
                   (math.hypot(*ba) * math.hypot(*bc))

    # 수치 오차 방지
    cosine_angle = max(-1.0, min(1.0, cosine_angle))

    angle = math.degrees(math.acos(cosine_angle))
    return int(angle)

# ============================
# 5. 정규화 좌표 → 픽셀 좌표 변환
# ============================
def to_pixel(point, width, height):
    return int(point[0] * width), int(point[1] * height)

# ============================
# 6. 웹캠 열기
# ============================
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break

    h, w, _ = frame.shape

    # MediaPipe는 RGB 이미지 사용
    frame = cv2.flip(frame, 1)
    image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = pose.process(image)

    if results.pose_landmarks:
        landmarks = results.pose_landmarks.landmark

        # ============================
        # 왼쪽 팔 (엉덩이-어깨-팔꿈치)
        # ============================
        P1 = [landmarks[BP1_ID].x, landmarks[BP1_ID].y]  # 엉덩이
        P2 = [landmarks[BP2_ID].x, landmarks[BP2_ID].y]  # 어깨
        P3 = [landmarks[BP3_ID].x, landmarks[BP3_ID].y]  # 팔꿈치

        left_angle = 180 - calculate_angle(P1, P2, P3)

        # ============================
        # 오른쪽 팔
        # ============================
        P4 = [landmarks[BP4_ID].x, landmarks[BP4_ID].y]
        P5 = [landmarks[BP5_ID].x, landmarks[BP5_ID].y]
        P6 = [landmarks[BP6_ID].x, landmarks[BP6_ID].y]

        right_angle = 180 - calculate_angle(P4, P5, P6)

        # ============================
        # 보드로 각도 전송
        # ============================
        data = f"{left_angle},{right_angle}\\n"
        board.write(data.encode())

        # ============================
        # 화면 시각화 (점 + 선만)
        # ============================

        # --- 왼쪽 팔 (파란색) ---
        LP1 = to_pixel(P1, w, h)
        LP2 = to_pixel(P2, w, h)
        LP3 = to_pixel(P3, w, h)

        cv2.circle(frame, LP1, 8, (255, 0, 0), -1)
        cv2.circle(frame, LP2, 8, (255, 0, 0), -1)
        cv2.circle(frame, LP3, 8, (255, 0, 0), -1)

        cv2.line(frame, LP1, LP2, (255, 0, 0), 3)  # 몸통
        cv2.line(frame, LP2, LP3, (255, 0, 0), 3)  # 팔

        # --- 오른쪽 팔 (빨간색) ---
        RP1 = to_pixel(P4, w, h)
        RP2 = to_pixel(P5, w, h)
        RP3 = to_pixel(P6, w, h)

        cv2.circle(frame, RP1, 8, (0, 0, 255), -1)
        cv2.circle(frame, RP2, 8, (0, 0, 255), -1)
        cv2.circle(frame, RP3, 8, (0, 0, 255), -1)

        cv2.line(frame, RP1, RP2, (0, 0, 255), 3)
        cv2.line(frame, RP2, RP3, (0, 0, 255), 3)

        # ============================
        # 각도 텍스트 표시
        # ============================
        cv2.putText(frame, f"L: {left_angle}", (30, 40),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
        cv2.putText(frame, f"R: {right_angle}", (30, 80),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)

    cv2.imshow("Robot Control", frame)

    # ESC 키로 종료. 브라우저(PyRunner)에서는 waitKey가 짧을수록 imshow/렌더가 과도하게 호출되어
    # 화면이 깜빡이기 쉬우므로 ~30fps에 가깝게 맞춤(로컬 OpenCV에서도 무방한 값).
    if cv2.waitKey(30) & 0xFF == 27:
        break

# ============================
# 종료 처리
# ============================
cap.release()
cv2.destroyAllWindows()
board.close()

주요 코드 설명

양쪽 팔의 각도 계산

# ============================
# 4. 각도 계산 함수
# ============================
def calculate_angle(a, b, c):
    """
    세 점 a, b, c가 있을 때
    b를 기준으로 한 각도를 계산
    """
    ba = (a[0] - b[0], a[1] - b[1])
    bc = (c[0] - b[0], c[1] - b[1])

    cosine_angle = (ba[0]*bc[0] + ba[1]*bc[1]) / \\
                   (math.hypot(*ba) * math.hypot(*bc))

    # 수치 오차 방지
    cosine_angle = max(-1.0, min(1.0, cosine_angle))

    angle = math.degrees(math.acos(cosine_angle))
    return int(angle)
    

# 사용처
left_angle = 180 - calculate_angle(P1, P2, P3)
right_angle = 180 - calculate_angle(P4, P5, P6)

데이터 전송 규약