본문 바로가기

PY(Python Image Processing)

hand detect & handling cube image - opencv python mediapipe pygame PyOpenGL PyOpenGL_accelerate numpy / one hand & icosahedron_vertices

728x90
import cv2
import mediapipe as mp
import numpy as np
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import random

# MediaPipe 손 검출 초기화
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(static_image_mode=False, max_num_hands=1, min_detection_confidence=0.5)

# OpenGL 텍스처 ID 저장
texture_id = None

# 3D 큐브 데이터
vertices = [
    [-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5],
    [-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]
]
faces = [
    (0, 1, 2, 3), (4, 5, 6, 7),
    (0, 1, 5, 4), (2, 3, 7, 6),
    (0, 3, 7, 4), (1, 2, 6, 5)
]
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), (1, 0, 1), (0, 1, 1)]

def lerp(a, b, t):
    """선형 보간 함수 (부드러운 크기 변화)"""
    return a + (b - a) * t

def init_texture(width, height):
    """OpenGL 텍스처 초기화 (웹캠 배경)"""
    global texture_id
    texture_id = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, texture_id)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, None)

def update_texture(frame):
    """웹캠 프레임을 OpenGL 텍스처로 업데이트"""
    if frame is None or frame.shape[0] == 0 or frame.shape[1] == 0:
        return  # 프레임이 비어있으면 업데이트 안함

    glBindTexture(GL_TEXTURE_2D, texture_id)
    frame = cv2.flip(frame, 0)  # OpenGL 좌표계에 맞추기 위해 상하 반전
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # BGR → RGB 변환
    frame = cv2.resize(frame, (640, 480))  # OpenGL과 맞추기 위해 크기 조정
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 640, 480, GL_RGB, GL_UNSIGNED_BYTE, frame)

def detect_hand(frame):
    """손 검출 및 손가락 간 거리 계산"""
    if frame is None:
        return None  # 프레임이 없을 경우

    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = hands.process(frame_rgb)

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            # 손바닥 중심점
            x = int(hand_landmarks.landmark[9].x * frame.shape[1])
            y = int(hand_landmarks.landmark[9].y * frame.shape[0])

            # 손가락 간 거리 계산 (손을 쥐었는지 여부)
            distances = [abs(hand_landmarks.landmark[i].y - hand_landmarks.landmark[9].y) for i in [8, 12, 16, 20]]
            avg_distance = sum(distances) / len(distances)

            # 주먹 감지 (손가락이 손바닥에 가까운 경우)
            is_fist = avg_distance < 0.07  

            return x, y, avg_distance, is_fist
    return None

def draw_background():
    """웹캠 영상을 OpenGL 배경으로 렌더링"""
    glEnable(GL_TEXTURE_2D)
    glBindTexture(GL_TEXTURE_2D, texture_id)

    glBegin(GL_QUADS)
    glTexCoord2f(0, 0); glVertex3f(-3, -2, -5)
    glTexCoord2f(1, 0); glVertex3f(3, -2, -5)
    glTexCoord2f(1, 1); glVertex3f(3, 2, -5)
    glTexCoord2f(0, 1); glVertex3f(-3, 2, -5)
    glEnd()

    glDisable(GL_TEXTURE_2D)

# 📌 정이십면체(20면체) 정점 생성
PHI = (1 + np.sqrt(5)) / 2  # 황금비율
icosahedron_vertices = [
    (-1, PHI, 0), (1, PHI, 0), (-1, -PHI, 0), (1, -PHI, 0),
    (0, -1, PHI), (0, 1, PHI), (0, -1, -PHI), (0, 1, -PHI),
    (PHI, 0, -1), (PHI, 0, 1), (-PHI, 0, -1), (-PHI, 0, 1)
]
icosahedron_vertices = np.array(icosahedron_vertices) / np.linalg.norm(icosahedron_vertices[0])  # 정규화

# 20개의 삼각형 면 정의
icosahedron_faces = [
    (0, 11, 5), (0, 5, 1), (0, 1, 7), (0, 7, 10), (0, 10, 11),
    (1, 5, 9), (5, 11, 4), (11, 10, 2), (10, 7, 6), (7, 1, 8),
    (3, 9, 4), (3, 4, 2), (3, 2, 6), (3, 6, 8), (3, 8, 9),
    (4, 9, 5), (2, 4, 11), (6, 2, 10), (8, 6, 7), (9, 8, 1)
]

def draw_cube(scale_factor, x_offset, y_offset, angle_x, angle_y):
    """💡 외부 투명 큐브 + 내부 정이십면체(20면체) + 네온 효과"""

    # 🌀 **네온 광채 효과 (시간에 따라 서서히 밝아졌다가 어두워짐)**
    time_factor = pygame.time.get_ticks() / 1000.0  
    glow_intensity = 0.3 + 0.7 * (math.sin(time_factor * 2) ** 2)  # 외부 밝기 조절
    inner_glow_intensity = 1.0 - glow_intensity  # 내부 밝기를 외부와 반대로 설정

    # 🔥 **OpenGL 변환 시작 (행렬 스택 푸시)**
    glPushMatrix()
    glTranslatef(x_offset, y_offset, -3.0)  # 큐브 위치 조정
    glScalef(scale_factor, scale_factor, scale_factor)  # 크기 조정
    glRotatef(angle_x, 1, 0, 0)  # X축 회전
    glRotatef(angle_y, 0, 1, 0)  # Y축 회전

    # 🌟 **깊이 버퍼를 변경하여 내부 도형이 더 잘 보이도록 설정**
    glDepthMask(GL_FALSE)  # 깊이 버퍼 업데이트 방지 (배경 영향 최소화)
   
    glEnable(GL_BLEND)  # 블렌딩 활성화 (투명도 조절 가능)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)  # 투명도 설정

    # **🔷 외부 큐브 (반투명 네온 블루)**
    glBegin(GL_QUADS)
    for face in faces:
        glColor4f(0.0, glow_intensity, 1.0, 0.2)  # 네온 블루 색상 + 반투명 효과
        for vertex in face:
            glVertex3fv(vertices[vertex])
    glEnd()

    # **🌟 큐브 모서리 강조 (얇은 흰색 선)**
    glEnable(GL_LINE_SMOOTH)  # 부드러운 선 설정
    glLineWidth(2.0)  # 선의 두께 조정
    glColor4f(1.0, 1.0, 1.0, 0.3)  # 흰색 반투명 모서리
    glBegin(GL_LINES)
    for face in faces:
        for i in range(4):
            glVertex3fv(vertices[face[i]])
            glVertex3fv(vertices[face[(i + 1) % 4]])
    glEnd()

    # 🌟 **깊이 버퍼 다시 활성화 (내부 도형이 정상적으로 보이게)**
    glDepthMask(GL_TRUE)

    # 🔺 **내부 정이십면체 (20면체) 추가**
    glPushMatrix()  # 내부 도형을 위한 추가 변환 시작
    glScalef(0.6, 0.6, 0.6)  # 내부 도형 크기를 외부 큐브의 60%로 축소

    # 🌀 **조명 및 광원 설정 (난반사 효과 적용)**
    glEnable(GL_LIGHTING)  # 조명 활성화
    glEnable(GL_LIGHT0)  # 기본 광원 활성화

    # 💡 **광원 속성 설정 (난반사 효과)**
    light_diffuse = [0.8, 0.8, 0.8, 1.0]  # 확산광 (난반사 효과 증가)
    light_specular = [0.3, 0.3, 0.3, 1.0]  # 반사광 (광택 효과 감소)
    light_position = [0.0, 0.0, 2.0, 1.0]  # 광원 위치 설정

    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse)
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular)
    glLightfv(GL_LIGHT0, GL_POSITION, light_position)

    # ✨ **정이십면체 표면 재질(Material) 설정**
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.1, 0.1, 0.1, 1.0])  # 주변광 (어두운 톤 추가)
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.3, 0.5, 1.0, 0.8])  # 확산광 (빛이 퍼지는 효과 증가)
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [0.5, 0.5, 0.5, 1.0])  # 반사광 (너무 밝지 않게 설정)
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, [10])  # 🔥 난반사 강도 (5 → 20으로 조정하여 난반사 효과 증가)

    # **🌟 내부 정이십면체(20면체) 그리기**
    glBegin(GL_TRIANGLES)

    # 🌀 내부 색상을 시간에 따라 변화하는 네온 효과 적용
    time_factor = pygame.time.get_ticks() / 1000.0
    r = (math.sin(time_factor * 3) + 1) / 3  # 붉은 계열
    g = (math.sin(time_factor * 3 + 2) + 1) / 3  # 녹색 계열
    b = (math.sin(time_factor * 3 + 4) + 1) / 2  # 푸른 계열

    for face in icosahedron_faces:
        glColor4f(r, g, b, 0.4)  # 🔥 투명도를 0.4로 낮춰 내부가 더 잘 보이게 설정
        for vertex in face:
            glVertex3fv(icosahedron_vertices[vertex])
    glEnd()

    # **🌟 내부 다각형 모서리 강조 (반투명 흰색 선)**
    glLineWidth(2.0)  # 선 두께 감소하여 자연스럽게
    glColor4f(1.0, 1.0, 1.0, 0.5)  # 🔥 모서리를 반투명하게 설정하여 빛 반사 효과 증가
    glBegin(GL_LINES)
    for face in icosahedron_faces:
        for i in range(3):
            glVertex3fv(icosahedron_vertices[face[i]])
            glVertex3fv(icosahedron_vertices[face[(i + 1) % 3]])
    glEnd()


    # 🚀 **내부 정이십면체 변환 종료 (스택 정리)**
    glDisable(GL_LIGHTING)  # 조명 해제
   
    # 🔥 2️⃣ 내부 도형 끝나면 POP
    glPopMatrix()  # 내부 정이십면체 변환 종료

    # 🔥 1️⃣ 큐브 전체 끝나면 POP (이거 없으면 스택 오버플로우 발생!)
    # 🚀 **전체 큐브 변환 종료 (스택 정리)**
    glPopMatrix()  # 큐브 변환 종료 #

   
def main():
    cap = cv2.VideoCapture(0)
    if not cap.isOpened():
        print("🚨 웹캠을 열 수 없습니다!")
        return

    pygame.init()
    display = (640, 480)
    pygame.display.set_mode(display, DOUBLEBUF | OPENGL)

    glEnable(GL_DEPTH_TEST)

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45, (640 / 480), 0.1, 50.0)
    glMatrixMode(GL_MODELVIEW)

    init_texture(640, 480)

    scale_factor = 0.3
    x_offset, y_offset = 0.0, 0.0
    angle_x, angle_y = 0, 0

    running = True
    while running:
        ret, frame = cap.read()
        if not ret or frame is None:
            pygame.time.wait(50)  # 손이 감지되지 않을 때 부하 감소
            continue  # 프레임이 없으면 루프 유지

        update_texture(frame)

        hand = detect_hand(frame)
        if hand:
            center_x, center_y, avg_distance, is_fist = hand
            x_offset = (center_x - 640 / 2) / (640 / 2)
            y_offset = -(center_y - 480 / 2) / (480 / 2)

            #target_scale = 0.1 if is_fist else max(0.2, min(0.5, avg_distance * 8))
            screen_max_size = 2.0  # 🔥 화면 크기에 맞춰 자동 조정
            target_scale = 0.1 if is_fist else max(0.2, min(screen_max_size, avg_distance * 8))
            scale_factor = lerp(scale_factor, target_scale, 0.2)  
        else:
            pygame.time.wait(50)  # 손이 없으면 루프 속도 낮춤

        angle_x += y_offset * 30
        angle_y += x_offset * 30  

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()

        draw_background()
        draw_cube(scale_factor, x_offset, y_offset, angle_x, angle_y)

        pygame.display.flip()

        for event in pygame.event.get():
            if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
                running = False

    cap.release()
    pygame.quit()

if __name__ == "__main__":
    main()

📌 필수 패키지 목록

패키지역할설치 명령

opencv-python OpenCV: 카메라 영상 처리 및 실시간 스트리밍 pip install opencv-python
mediapipe MediaPipe: 손 추적 및 랜드마크 감지 pip install mediapipe
pygame Pygame: OpenGL과 함께 창 생성 및 이벤트 핸들링 pip install pygame
PyOpenGL OpenGL을 통한 3D 그래픽 렌더링 pip install PyOpenGL
PyOpenGL_accelerate OpenGL 속도 가속화 (성능 향상) pip install PyOpenGL_accelerate
numpy 벡터 연산 및 좌표 데이터 처리 pip install numpy
math 기본 수학 연산 (Python 내장) 설치 불필요
random 랜덤 값 생성 (Python 내장) 설치 불필요

728x90