본문 바로가기

Python/인공지능

미디어파이프 Hand landmark 모델로 아두이노 로봇팔 제어하기

1. 미디어 파이프 스튜디오 확인

https://mediapipe-studio.webapps.google.com/home

 

로그인 - Google 계정

이메일 또는 휴대전화

accounts.google.com

 

 

 

2. 환경 구성하기 : VScode 에디터 설치 및 아나콘다 설치

1) vscode 설치 링크

https://code.visualstudio.com/download

 

Download Visual Studio Code - Mac, Linux, Windows

Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows. Download Visual Studio Code to experience a redefined code editor, optimized for building and debugging modern web and cloud applications.

code.visualstudio.com

 

 

 

 

 

 

 

 

 

 

2) 아나콘다 설치 링크

https://www.anaconda.com/download/success

 

Download Now | Anaconda

Anaconda is the birthplace of Python data science. We are a movement of data scientists, data-driven enterprises, and open source communities.

www.anaconda.com

 

 

설치할때 중간에 반드시 모두 체크 

 

3. vscode 파이썬 기본 사용법

- 파이썬 설치

 

-  한국어 팩 설치 

 

- 설치 후 자동 설정 변경

 

 

상단 메뉴 - 파일 - 폴더 열기 선택

 

새폴더 만들고 폴더 선택

 

새파일 만들기

기본 코드를 작성하고 컨트롤 에스를 눌러 반드시 저장하고 우측 상단의 재생 버튼을 눌러 코드를 동작시켜 보자

 

터미널에서 동작 확인

 

 

4. chatgpt로 부터 미디어 파이프 AI 구동 프로그램 받아오기

- gpt 접속 후 로그인

https://chatgpt.com/

 

 

 

import cv2
import mediapipe as mp

# MediaPipe 손 인식 초기화
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,  # 연속된 이미지에서 손을 인식
    max_num_hands=2,           # 최대 손 인식 개수
    min_detection_confidence=0.5,  # 손 인식 확률 임계값
    min_tracking_confidence=0.5)   # 손 추적 확률 임계값

# 그리기 도구 초기화
mp_drawing = mp.solutions.drawing_utils

# 웹캠 캡처 초기화
cap = cv2.VideoCapture(0)

# 비디오 프레임 루프
while cap.isOpened():
    success, image = cap.read()
    if not success:
        print("웹캠에서 이미지를 읽어오는 데 실패했습니다.")
        continue

    # 이미지를 RGB로 변환
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    # 결과를 원래 이미지에 표시하기 위해 이미지 쓰기를 비활성화
    image.flags.writeable = False

    # 손 인식 수행
    results = hands.process(image)

    # 이미지 쓰기 활성화
    image.flags.writeable = True

    # 이미지를 BGR로 다시 변환
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

    # 인식된 손가락 랜드마크 표시
    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            mp_drawing.draw_landmarks(
                image, hand_landmarks, mp_hands.HAND_CONNECTIONS)

    # 결과 이미지 표시
    cv2.imshow('MediaPipe Hands', image)

    # 'q' 키를 누르면 종료
    if cv2.waitKey(5) & 0xFF == 113:
        break

# 웹캠 해제
cap.release()

우선 코드를 돌리기 전에 인터프린터(코드 해석기)가 아나콘다 base로 잡혀 있는지 확인해 준다.

 

gpt가 만들어준 코드를 돌리고 vscode에 붙여넣기 후 저장 한뒤 코드를 돌려 본다.

 

해당 코드를 동작시기키 위해 필요한 

opencv-python 모듈과 mediapipe 모듈이 없다고 에러가 날것이다.

 

pip install mediapipe opencv-python

터미널에 위 명령어를 넣어서 해당 패키지들을 모두 설치하고 다시 동작 시켜 보자. 

 

잘동작 할것이다.

 

5. AI 모델(파이썬) - 아두이노 시리얼 통신 연결

아두이노를 노트북에 연결하고 아두이노 프로그램을 연다 

 

아두이노 연결 com번호 확인한다. 

 

아두이노 코드를 업로드 한다. (기존에 이미 코드가 업로드 되어 있다면 다시 업로드 안해도 된다.)

#include <Servo.h>

// 서보모터 객체 생성
Servo servo1;
Servo servo2;
Servo servo3;

// 서보모터 초기 각도 설정
int angle1 = 90;
int angle2 = 90;
int angle3 = 90;
bool servo3At90 = true; // 서보모터3의 현재 위치를 추적

void setup() {
  // 서보모터 핀 설정
  servo1.attach(9);
  servo2.attach(10);
  servo3.attach(11);

  // 초기 각도로 서보모터 설정
  servo1.write(angle1);
  servo2.write(angle2);
  servo3.write(angle3);

  // 시리얼 통신 시작
  Serial.begin(9600);
}

void loop() {
  // 시리얼 입력이 있는지 확인
  if (Serial.available() > 0) {
    char input = Serial.read();

    // 서보모터1 제어
    if (input == '4') {
      angle1 += 5;
      if (angle1 > 180) angle1 = 180;
      servo1.write(angle1);
    } else if (input == '6') {
      angle1 -= 5;
      if (angle1 < 0) angle1 = 0;
      servo1.write(angle1);
    }

    // 서보모터2 제어
    if (input == '8') {
      angle2 -= 5;
      if (angle2 < 0) angle2 = 0;
      servo2.write(angle2);
    } else if (input == '2') {
      angle2 += 5;
      if (angle2 > 180) angle2 = 180;
      servo2.write(angle2);
    }

    // 서보모터3 제어
    if (input == '5') {
      if (servo3At90) {
        angle3 = 135;
      } else {
        angle3 = 100;
      }
      servo3.write(angle3);
      servo3At90 = !servo3At90;
    }
  }
}

 

시리얼 모니터를 열고 모터 제어 코드인 8,4,5,6,2 등을 넣어 본다. 모터 3개가 정상적으로 동작하는지 확인한다. 

 

테스트를 마쳤으면 반드시 시리얼 모니터를 끄자. 나중에 파이썬 시리얼 통신과 충돌이 날수 도 있다.

 

 

챗지피티 명령을 준다.

 

결과

 

import cv2
import mediapipe as mp
import serial
import time

# 시리얼 포트 초기화 (COM 포트와 보드레이트는 환경에 맞게 설정)
ser = serial.Serial('COM3', 9600, timeout=1)
time.sleep(2)  # 아두이노 리셋 방지를 위한 지연

# MediaPipe 초기화
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5)
mp_drawing = mp.solutions.drawing_utils

# 웹캠 캡처
cap = cv2.VideoCapture(0)

def send_command(command):
    ser.write(f'{command}\n'.encode('utf-8'))

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

    height, width, _ = frame.shape
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame.flags.writeable = False
    results = hands.process(frame)
    frame.flags.writeable = True
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            # 손 중심 위치 계산
            cx = int(hand_landmarks.landmark[mp_hands.HandLandmark.WRIST].x * width)
            cy = int(hand_landmarks.landmark[mp_hands.HandLandmark.WRIST].y * height)

            # 화면 분할하여 위치 판단
            if cx < width // 2 and cy < height // 2:
                send_command(8)  # 위
            elif cx < width // 2 and cy >= height // 2:
                send_command(4)  # 왼쪽
            elif cx >= width // 2 and cy < height // 2:
                send_command(6)  # 오른쪽
            elif cx >= width // 2 and cy >= height // 2:
                send_command(2)  # 아래
            
            # 주먹 쥠 판단
            finger_tips = [mp_hands.HandLandmark.INDEX_FINGER_TIP, mp_hands.HandLandmark.MIDDLE_FINGER_TIP,
                           mp_hands.HandLandmark.RING_FINGER_TIP, mp_hands.HandLandmark.PINKY_TIP]
            fist = True
            for tip in finger_tips:
                fingertip = hand_landmarks.landmark[tip]
                mcp = hand_landmarks.landmark[tip - 2]  # TIP에서 2번 뒤가 MCP
                if ((fingertip.x - mcp.x) ** 2 + (fingertip.y - mcp.y) ** 2) ** 0.5 > 0.1:
                    fist = False
                    break
            if fist:
                send_command(5)  # 주먹 쥠
            
            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

    cv2.imshow('MediaPipe Hands', frame)
    if cv2.waitKey(5) & 0xFF == 113:  # 'q' 키를 누르면 종료
        break

cap.release()
cv

 

코드를 vscode에 넣고 pyserial 설치 하고, com숫자 바꾸고, 컨트롤 에스로 저장하고 동작을 시켜보자. 

 

잘동작 할것이다.

 

하지만 동작이 불안정하다.

chatgpt와 함꼐 최적화 과정이 남았다..

 

6. chatgpt와 함께하는 AI 모델 최적화 작업 예시

최적화 작업에 정답은 없다. 하지만 여러번 시도를 통해 좀더 안정적인 동작을 구현할 수 있다. 아래는 내가 실습한 과정이다.

 

코드

import cv2
import mediapipe as mp
import serial
import time

# 시리얼 포트 초기화
ser = serial.Serial('COM3', 9600, timeout=1)
time.sleep(2)  # 아두이노 리셋 방지를 위한 지연

# MediaPipe 초기화
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5)
mp_drawing = mp.solutions.drawing_utils

# 웹캠 캡처
cap = cv2.VideoCapture(0)

def send_command(command):
    ser.write(f'{command}\n'.encode('utf-8'))
    return f'Sending: {command}'

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

    height, width, _ = frame.shape
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame.flags.writeable = False
    results = hands.process(frame)
    frame.flags.writeable = True
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    # 사분면 선 그리기
    cv2.line(frame, (width // 2, 0), (width // 2, height), (255, 0, 0), 2)
    cv2.line(frame, (0, height // 2), (width, height // 2), (255, 0, 0), 2)

    current_command = "None"
    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            cx = int(hand_landmarks.landmark[mp_hands.HandLandmark.WRIST].x * width)
            cy = int(hand_landmarks.landmark[mp_hands.HandLandmark.WRIST].y * height)

            # 위치에 따른 시리얼 커맨드 결정
            if cx < width // 2 and cy < height // 2:
                current_command = send_command(8)  # 위
            elif cx < width // 2 and cy >= height // 2:
                current_command = send_command(4)  # 왼쪽
            elif cx >= width // 2 and cy < height // 2:
                current_command = send_command(6)  # 오른쪽
            elif cx >= width // 2 and cy >= height // 2:
                current_command = send_command(2)  # 아래

            # 주먹 쥠 검사
            finger_tips = [mp_hands.HandLandmark.INDEX_FINGER_TIP, mp_hands.HandLandmark.MIDDLE_FINGER_TIP,
                           mp_hands.HandLandmark.RING_FINGER_TIP, mp_hands.HandLandmark.PINKY_TIP]
            fist = True
            for tip in finger_tips:
                fingertip = hand_landmarks.landmark[tip]
                mcp = hand_landmarks.landmark[tip - 2]
                if ((fingertip.x - mcp.x) ** 2 + (fingertip.y - mcp.y) ** 2) ** 0.5 > 0.1:
                    fist = False
                    break
            if fist:
                current_command = send_command(5)  # 주먹

            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

    # 현재 보내고 있는 시리얼 커맨드를 화면에 표시
    cv2.putText(frame, current_command, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2, cv2.LINE_AA)

    cv2.imshow('MediaPipe Hands', frame)
    if cv2.waitKey(5) & 0xFF == 113:  # 'q' 키를 누르면 종료
        break



com 숫자는 항상 변경시키든 gpt에게 코드 출력 할때 숫자를 알려주자.

 

동작 예시

 

 

import cv2
import mediapipe as mp
import serial
import time

# 시리얼 포트 초기화
ser = serial.Serial('COM3', 9600, timeout=1)
time.sleep(2)  # 아두이노 리셋 방지를 위한 지연

# MediaPipe 초기화
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5)
mp_drawing = mp.solutions.drawing_utils

# 웹캠 캡처
cap = cv2.VideoCapture(0)

def send_command(command):
    ser.write(f'{command}\n'.encode('utf-8'))
    return f'Sending: {command}'

# 방향키 그리기 함수
def draw_direction_keys(img, center_x, center_y, size):
    cv2.rectangle(img, (center_x - size, center_y - 3 * size), (center_x + size, center_y - size), (200, 200, 200), -1)  # Up
    cv2.rectangle(img, (center_x - size, center_y + size), (center_x + size, center_y + 3 * size), (200, 200, 200), -1)  # Down
    cv2.rectangle(img, (center_x - 3 * size, center_y - size), (center_x - size, center_y + size), (200, 200, 200), -1)  # Left
    cv2.rectangle(img, (center_x + size, center_y - size), (center_x + 3 * size, center_y + size), (200, 200, 200), -1)  # Right

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

    height, width, _ = frame.shape
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame.flags.writeable = False
    results = hands.process(frame)
    frame.flags.writeable = True
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    key_size = 50
    center_x, center_y = width - 300, height // 2
    draw_direction_keys(frame, center_x, center_y, key_size)

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            index_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
            index_x = int(index_tip.x * width)
            index_y = int(index_tip.y * height)

            # 방향키 위치 확인 및 커맨드 전송
            if index_x > center_x - key_size and index_x < center_x + key_size:
                if index_y > center_y - 3 * key_size and index_y < center_y - key_size:
                    current_command = send_command(8)  # Up
                elif index_y > center_y + key_size and index_y < center_y + 3 * key_size:
                    current_command = send_command(2)  # Down

            if index_y > center_y - key_size and index_y < center_y + key_size:
                if index_x > center_x - 3 * key_size and index_x < center_x - key_size:
                    current_command = send_command(4)  # Left
                elif index_x > center_x + key_size and index_x < center_x + 3 * key_size:
                    current_command = send_command(6)  # Right

            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

    cv2.imshow('MediaPipe Hands', frame)
    if cv2.waitKey(5) & 0xFF == 113:  # 'q' 키를 누르면 종료
        break

cap.release()
cv2.destroyAllWindows()

 

동작

 

 

 

 

import cv2
import mediapipe as mp
import serial
import time

# 시리얼 포트 초기화
ser = serial.Serial('COM3', 9600, timeout=1)
time.sleep(2)  # 아두이노 리셋 방지를 위한 지연

# MediaPipe 초기화
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5)
mp_drawing = mp.solutions.drawing_utils

# 웹캠 캡처
cap = cv2.VideoCapture(0)

def send_command(command):
    ser.write(f'{command}\n'.encode('utf-8'))
    return f'Sending: {command}'

# 방향키 그리기 함수
def draw_direction_keys(img, center_x, center_y, size):
    up_color = down_color = left_color = right_color = (200, 200, 200)  # Gray color
    center_color = (100, 200, 100)  # Light green for center

    # Draw keys
    cv2.rectangle(img, (center_x - size, center_y - 3 * size), (center_x + size, center_y - size), up_color, -1)  # Up
    cv2.rectangle(img, (center_x - size, center_y + size), (center_x + size, center_y + 3 * size), down_color, -1)  # Down
    cv2.rectangle(img, (center_x - 3 * size, center_y - size), (center_x - size, center_y + size), left_color, -1)  # Left
    cv2.rectangle(img, (center_x + size, center_y - size), (center_x + 3 * size, center_y + size), right_color, -1)  # Right
    cv2.rectangle(img, (center_x - size, center_y - size), (center_x + size, center_y + size), center_color, -1)  # Center

last_sent_time = time.time()
command_interval = 0.5  # command interval in seconds

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

    height, width, _ = frame.shape
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame.flags.writeable = False
    results = hands.process(frame)
    frame.flags.writeable = True
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    key_size = 50
    center_x, center_y = width - 300, height // 2
    draw_direction_keys(frame, center_x, center_y, key_size)

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            index_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
            index_x = int(index_tip.x * width)
            index_y = int(index_tip.y * height)

            current_command = None
            # 방향키 위치 확인 및 커맨드 전송
            if index_x > center_x - key_size and index_x < center_x + key_size:
                if index_y > center_y - 3 * key_size and index_y < center_y - key_size:
                    current_command = 8  # Up
                elif index_y > center_y + key_size and index_y < center_y + 3 * key_size:
                    current_command = 2  # Down
            if index_y > center_y - key_size and index_y < center_y + key_size:
                if index_x > center_x - 3 * key_size and index_x < center_x - key_size:
                    current_command = 4  # Left
                elif index_x > center_x + key_size and index_x < center_x + 3 * key_size:
                    current_command = 6  # Right
            if index_x > center_x - key_size and index_x < center_x + key_size and index_y > center_y - key_size and index_y < center_y + key_size:
                current_command = 5  # Center

            if current_command and time.time() - last_sent_time > command_interval:
                send_command(current_command)
                last_sent_time = time.time()

            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

    cv2.imshow('MediaPipe Hands', frame)
    if cv2.waitKey(5) & 0xFF == 113:  # 'q' 키를 누르면 종료
        break

cap.release()
cv2.destroyAllWindows()

 

동작

 

 

 

import cv2
import mediapipe as mp
import serial
import time

# 시리얼 포트 초기화
ser = serial.Serial('COM12', 9600, timeout=1)
time.sleep(2)  # 아두이노 리셋 방지를 위한 지연

# MediaPipe 초기화
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5)
mp_drawing = mp.solutions.drawing_utils

# 웹캠 캡처
cap = cv2.VideoCapture(0)

def send_command(command):
    ser.write(f'{command}\n'.encode('utf-8'))
    return f'Sending: {command}'

# 방향키 그리기 함수
def draw_direction_keys(img, size):
    height, width, _ = img.shape

    up_color = down_color = left_color = right_color = (200, 200, 200)  # Gray color
    center_color = (100, 200, 100)  # Light green for center

    # 각 방향의 중앙 끝에 사각형 그리기
    cv2.rectangle(img, (width // 2 - size, 0), (width // 2 + size, size * 2), up_color, -1)  # Up
    cv2.rectangle(img, (width // 2 - size, height - size * 2), (width // 2 + size, height), down_color, -1)  # Down
    cv2.rectangle(img, (0, height // 2 - size), (size * 2, height // 2 + size), left_color, -1)  # Left
    cv2.rectangle(img, (width - size * 2, height // 2 - size), (width, height // 2 + size), right_color, -1)  # Right
    cv2.rectangle(img, (width // 2 - size, height // 2 - size), (width // 2 + size, height // 2 + size), center_color, -1)  # Center

last_sent_time = time.time()
command_interval = 0.5  # command interval in seconds

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

    height, width, _ = frame.shape
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame.flags.writeable = False
    results = hands.process(frame)
    frame.flags.writeable = True
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    key_size = 50
    draw_direction_keys(frame, key_size)

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            index_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
            index_x = int(index_tip.x * width)
            index_y = int(index_tip.y * height)

            current_command = None
            # 방향키 위치 확인 및 커맨드 전송
            if width // 2 - key_size < index_x < width // 2 + key_size:
                if index_y < key_size * 2:
                    current_command = 8  # Up
                elif index_y > height - key_size * 2:
                    current_command = 2  # Down
            if height // 2 - key_size < index_y < height // 2 + key_size:
                if index_x < key_size * 2:
                    current_command = 4  # Left
                elif index_x > width - key_size * 2:
                    current_command = 6  # Right
            if width // 2 - key_size < index_x < width // 2 + key_size and height // 2 - key_size < index_y < height // 2 + key_size:
                current_command = 5  # Center

            if current_command and time.time() - last_sent_time > command_interval:
                send_command(current_command)
                last_sent_time = time.time()

            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

    cv2.imshow('MediaPipe Hands', frame)
    if cv2.waitKey(5) & 0xFF == 113:  # 'q' 키를 누르면 종료
        break

cap.release()
cv2.destroyAllWindows()

 

 

 

이런식으로 원한는 형태로 동작의 디테일을 잡아 줄 수 있다. 

 

최종 동작 영상이다.

 

 

 

미디어파이프에는 다양한 AI 모델이 있고, 아두이노에 다양한 전자부품을 물리면 다양한 작품들을 생성 할 수 있다.

끝.