본문 바로가기
Study/Python

[Python]OpenCV - 10 Mediapipe-hand-face

by YoungD 2023. 11. 2.
# 1. 손 동작중에서 spider or Rock을 인식 / Fist = 
# 2. 얼굴을 인식
# 3. 얼굴에 스파이더맨 가면 씌우기
# 4. 손 동작이 Fist일 때는 호랑이 가면 씌우기
In [2]:
## 동영상과 mediapipe-hand를 연결하기
## 한손의 동작 인식하기
import cv2
import mediapipe as mp

gesture = {
    0:'fist', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five',
    6:'six', 7:'rock', 8:'spiderman', 9:'yeah', 10:'ok',
}

# gesture_train를 머신러닝 모델에 학습
import numpy as np
file = np.genfromtxt('images/gesture_train.csv',delimiter = ',')
angle = file[:, :-1].astype(np.float32) # 문제데이터
label = file[:, -1].astype(np.float32) # 정답데이터
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors = 3)
knn.fit(angle,label)

# 손을 찾는 기능 불러오기
mp_hands = mp.solutions.hands
# 특징점 그리기 설정
mp_drawing = mp.solutions.drawing_utils
# 손 특징점 찾기 관련 설정
hands = mp_hands.Hands(
    max_num_hands = 1,
    min_detection_confidence = 0.5, # 손 검출 확률(자체 판단) 50% 이상인것들만 출력하기 
    min_tracking_confidence = 0.5# 특징점 검출 확률(자체 판단) 50% 이상인것들만 출력하기
)

# 얼굴에서 특징점을 찾을 수 있는 기능 불러오기
mp_face_mesh = mp.solutions.face_mesh
# 찾은 특징점 표현하기
mp_drawing = mp.solutions.drawing_utils
# 특징점 그리기 속성
drawing_spec = mp_drawing.DrawingSpec(thickness = 1, color = (0,0,255))
# 얼굴에서 특징점 찾기 속성
face_mesh = mp_face_mesh.FaceMesh(
    min_detection_confidence = 0.5, # 얼굴 검출 확률(자체 판단) 50% 이상인것들만 출력하기
    min_tracking_confidence = 0.5 # 특징점 검출 확률(자체 판단) 50% 이상인것들만 출력하기
)

# 한번만 실행하면 되는 코드
spider = cv2.imread('images/spiderman.jpg')
spider = cv2.resize(spider, (300,300)) # 123,132 > 250,250으로 크기 변경
mask2gray_spider = cv2.cvtColor(spider , cv2.COLOR_BGR2GRAY)
_, mask_b_spider = cv2.threshold(mask2gray_spider, 220, 255, cv2.THRESH_BINARY)
mask_b_inv_spider = cv2.bitwise_not(mask_b_spider)

tiger = cv2.imread('images/tiger.png')
tiger = cv2.resize(tiger, (200,200)) # 123,132 > 250,250으로 크기 변경
mask2gray_tiger = cv2.cvtColor(tiger , cv2.COLOR_BGR2GRAY)
_, mask_b_tiger = cv2.threshold(mask2gray_tiger, 220, 255, cv2.THRESH_BINARY)
mask_b_inv_tiger = cv2.bitwise_not(mask_b_tiger)

cap = cv2.VideoCapture(0)

while cap.isOpened():
    ret, img = cap.read()
    img = cv2.flip(img, 1)
    if not ret:
        break
    # 이미지에서 원하는 대상(손) 찾기
    hand_result = hands.process(img)
    # 손을 검출 했다면 표현하기(21개의 특징점을 찾음)
    if hand_result.multi_hand_landmarks is not None:
        # 21개 특징점을 하나씩 그려주기
        for res in hand_result.multi_hand_landmarks:
            # 21개의 특징점 위치 저장할 용도(x,y,z)
            joint = np.zeros((21,3))
            # 21개의 특징점 저장
            # enumerate : 반복문의 순서를 알려줌, 추가적인 변수 하나가 더 필요                
            for j , lm in enumerate(res.landmark):
                joint[j] = [lm.x, lm.y, lm.z]

            # 특징점을 연결해서 뼈의 값 구하기(좌표값 기반-x,y,z)
            v1 = joint[[0,1,2,3,0,5,6,7,0,9,10,11,0,13,14,15,0,17,18,19],:]
            v2 = joint[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],:]
            v = v2 - v1 # [20,3]
            # 뼈의 직선 구하기
            v = v / np.linalg.norm(v, axis=1)[:, np.newaxis]
            # 뼈의 직선을 통해서 각도(radian) 구하기(각도의 변화가 큰 15개만 사용)
            angle = np.arccos(np.einsum('nt,nt->n',
                v[[0,1,2,4,5,6,8,9,10,12,13,14,16,17,18],:], 
                v[[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19],:]))
            # radian각도를 degree각도로 변경하기
            angle = np.degrees(angle)

            # 학습된 KNN 모델에 동작 예측시키기
            # 예측시킬 데이터 전처리하기
            data = np.array([angle], dtype=np.float32)
            # 예측하기
            results = knn.predict(data)
            idx = int(results)
            # cv2.putText : 이미지 위에 글씨 쓰기
            # 사용할 이미지, 쓸 글씨(영어), 글씨 위치, 폰트, 글씨 크기, 글씨 색깔, 글씨 두께
            mp_drawing.draw_landmarks(img, res, mp_hands.HAND_CONNECTIONS)
            if idx == 7 or idx == 8: # Rock or Spiderman 동작일때 실행
                cv2.putText(img, text='SpiderMan', org=(int(res.landmark[0].x * img.shape[1]), int(res.landmark[0].y * img.shape[0] + 20)), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(255, 255, 255), thickness=2)

                # 손동작이 스파이더맨 동작일때만 얼굴 검출 후 얼굴 검출되면 가면 씌우기
                # 사진에서 얼굴 검출하기
                face_result = face_mesh.process(img)
                if face_result.multi_face_landmarks is not None:
                    # print(results.multi_face_landmarks)
                    #for res in results.multi_face_landmarks:
                    #   mp_drawing.draw_landmarks(img, res, mp_face_mesh.FACEMESH_TESSELATION)
                    # 478개 점을 사용하기가 편함
                    face_landmarks = face_result.multi_face_landmarks[0]
                    
                    # 코의 좌표 찾기
                    x = int(face_landmarks.landmark[4].x * img.shape[1])
                    y = int(face_landmarks.landmark[4].y * img.shape[0])
                    # cv2.circle(img, (x,y) , 20, (0,0,255), cv2.FILLED)
                    # 이미지를 집어넣을 중심 좌표(코의 위치) > x,y
                    # 마스크 이미지의 크기 > 123,132(tiger.shape) > 200,200
                    try:
                        sub_img = img[y-150 : y+150 , x-150: x+150]
                        img_bg = cv2.bitwise_and(sub_img, sub_img, mask = mask_b_spider)
                        img_fg = cv2.bitwise_and(spider,spider, mask = mask_b_inv_spider)
                        bg_fg = cv2.add(img_bg,img_fg)
                        img[y-150 : y+150 , x-150: x+150] = bg_fg     
                    except:
                        pass
            elif idx == 0: # Fist 동작일때 실행
                cv2.putText(img, text='Tiger', org=(int(res.landmark[0].x * img.shape[1]), int(res.landmark[0].y * img.shape[0] + 20)), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(255, 255, 255), thickness=2)

            # 손동작이 스파이더맨 동작일때만 얼굴 검출 후 얼굴 검출되면 가면 씌우기
            # 사진에서 얼굴 검출하기
                face_result = face_mesh.process(img)
                if face_result.multi_face_landmarks is not None:
                    # print(results.multi_face_landmarks)
                    #for res in results.multi_face_landmarks:
                    #   mp_drawing.draw_landmarks(img, res, mp_face_mesh.FACEMESH_TESSELATION)
                    # 478개 점을 사용하기가 편함
                    face_landmarks = face_result.multi_face_landmarks[0]
                    
                    # 코의 좌표 찾기
                    x = int(face_landmarks.landmark[4].x * img.shape[1])
                    y = int(face_landmarks.landmark[4].y * img.shape[0])
                    # cv2.circle(img, (x,y) , 20, (0,0,255), cv2.FILLED)
                    # 이미지를 집어넣을 중심 좌표(코의 위치) > x,y
                    # 마스크 이미지의 크기 > 123,132(tiger.shape) > 200,200
                    try:
                        sub_img = img[y-100 : y+100 , x-100: x+100]
                        img_bg = cv2.bitwise_and(sub_img, sub_img, mask = mask_b_tiger)
                        img_fg = cv2.bitwise_and(tiger,tiger, mask = mask_b_inv_tiger)
                        bg_fg = cv2.add(img_bg,img_fg)
                        img[y-100 : y+100 , x-100: x+100] = bg_fg     
                    except:
                        pass
    
    cv2.imshow('video',img)
    if cv2.waitKey(33) == 49:
        break
cap.release()
cv2.destroyAllWindows()