수화 인식 프로그램 <1> (Python, mediapipe, sklearn)

2023. 3. 10. 21:36Python+Web

 

** 딥러닝 기반 AI 수화인식 프로그램  

1) 개발 목적: 수화인식 

 

2) 사용 모듈: cv2(opencv)-웹캠 제어, mediapipe-손 인식, sklearn(머신러닝)-학습

- 모듈 설치

 !pip install opencv-python
 !pip install keyboard
 !pip install mediapipe

 

3) 코드 내용:

 

요약: 

 

 1. 사용되는 수화(변수 'guesture')는 dictionary로 활용 
 2. MediaPipe 활용한 유틸리티 사용 
 3. OpenCV 활용하여, 웹캠 제어 및 웹상 위 수화 내용 기입

상세:

max_num_hands=1 # 최대 손 인식: 1
gesture = {
    0:'a',1:'b',2:'c',3:'d',4:'e',5:'f',6:'g',7:'h',
    8:'i',9:'j',10:'k',11:'l',12:'m',13:'n',14:'o',
    15:'p',16:'q',17:'r',18:'s',19:'t',20:'u',21:'v',
    22:'w',23:'x',24:'y',25:'z',26:'spacing',27:'clear',28:'delete'
}

- gesture은 dictionary로 Key:Value 데이터 타입으로 관리  
 > key는 임의의 라벨링 번호(학습 시킨 라벨링 번호) 
  value는 해당하는 알파벳 내용
 > gesture는 keyboard 모듈 활용(delete 기능 등..) 

 

# MediaPipe hands model
mp_hands=mp.solutions.hands # 웹캠 손 위치 정보 유틸리티
mp_drawing = mp.solutions.drawing_utils # 손 정보 기반으로 그리기

# 손가락 detection 모듈을 초기화
hands=mp_hands.Hands(
    max_num_hands=max_num_hands, # 최대 손 인식
    min_detection_confidence=0.5, # 최소 탐지 신뢰도 기본값=0.5
    min_tracking_confidence=0.5 # 최소 추적 신뢰도 기본값=0.5  
)

- mediapipe를 사용하기 위한 기본 설정

 

 

# 제스처 인식 모델
f=open('test.txt','w') # 학습시킬 dataset 저장하기
file=np.genfromtxt('dataSet.txt',delimiter=',')
angle = file[:,:-1].astype(np.float32) # 각도
label = file[:, -1].astype(np.float32) # 라벨
knn=cv2.ml.KNearest_create() # knn(k-최근접 알고리즘)  
knn.train(angle,cv2.ml.ROW_SAMPLE,label) # 학습!

- mediapipe에 존재하는 정보 유틸리티 활용 
- mediapipe 옵션 설정
- gesture 인식 모델은 추가적인 기능을 학습시킬 때 사용 
 > 해당 코드에서는 delete 기능 추가 
- 제스쳐 학습을 위해 knn 알고리즘 사용

 

 

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

# 웹캠에서 한 프레임씩 이미지를 읽어옴
startTime = time.time()
prev_index = 0
sentence = ''
recognlzeDelay = 1
while True:
    ret,img = cap.read()
    if not ret:
        break
 

- 수화 인식을 하기 위해 opencv 모듈로 웹캡을 연다.  
- 변수 ret, img 사용 목적
 > ret : 프레임 읽기 성공 또는 실패 여부, True / False
  img : 프레임 이미지, Numpy 배열 또는 None

 

 

    img = cv2.flip(img, 1)
    imgRGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
    result = hands.process(imgRGB)

    # 각도를 인식하고 제스처를 인식하는 부분
    if result.multi_hand_landmarks is not None: # 만약 손을 인식하면
            for res in result.multi_hand_landmarks:
                # joint == 랜드마크에서 빨간 점
                # joint는 21개가 있고 x,y,z 좌표니까 21,3
                joint = np.zeros((21,3))
           
            for j,lm in enumerate(res.landmark):
                joint[j] = [lm.x,lm.y,lm.z] # 각 joint마다 x,y,z 좌표 저장  
   

- cv2.flip은 이미지 반전효과기능 (웹캠 사용 시 반대로 인식하기 때문)
- cv2.cvtColor(img,cv2.COLOR_BGR2RGB) 
 > openCV 는 기본적으로 BGR의 형태로 변환하여 데이터를 가져오고, 
 PIL 은 이미지가 가지고있는 형식 그대로를 가져온다.
 > 색감을 적용하는 모듈에 맞춰 형태를 변경해줘야 때문 
- 위에서 설정해준 mp 활용하여 만든 변수 hands로 해당 이미지가 어떤 의미인지 파악 

 

 

            # Compute angles between joints joint마다 각도 계산
            # **공식문서 들어가보면 각 joint 번호의 인덱스가 나옴**  
            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 정규화(식 축소, api가 간단해짐)
            v = v / np.linalg.norm(v, axis=1)[:, np.newaxis]

            # joint(빨간점)을 통해 역삼각함수
            # **내적(벡터 곱 종류) 후 arccos으로 각도를 구해줌**
            compareV1 = v[[0,1,2,4,5,6,7,8,9,10,12,13,14,16,17],:]
            compareV2 = v[[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19],:]
            angle = np.arccos(np.einsum('nt,nt->n',compareV1,compareV2))
            angle = np.degrees(angle)

            # a 누르면 저장하기
            # 학습 모델을 만들어야 할때 사용  
            if keyboard.is_pressed('a'):
                for num in angle:
                    num = round(num,6)
                    f.write(str(num))
                    f.write(',')
                f.write('28.00000') # gesture에 들어갈 key 넣어주기
                f.write('\n')
                print("next")
                print('---------------------')
 
            # 학습시킨 제스쳐 모델에 참조
            # 참고: http://www.gisdeveloper.co.kr/?p=6973
            data = np.array([angle], dtype=np.float32)
            ret, result, neighbors,dist = knn.findNearest(data,3)
            index = int(result[0][0])

            cv2.putText(img, text=gesture[index].upper(), 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)

- 관절에 대한 좌표를 뜻하는 거고, 관절 vector를 활용하여 실질적으로 각도를 계산 
- 계산한 결과값으로, a를 눌렀을때 좌표를 저장할 수 있도록 하여, 데이터 학습

-  putText 함수로 감지된 손 위에 알파벳 혹은 제스쳐 입력

 

 

            if index in gesture.keys(): # 만약 index가 gesture.key에 있으면
                # prev_index=0, startTime = time.time()
                if index != prev_index:
                    startTime = time.time()
                    prev_index = index
                else:
                    if time.time() - startTime > recognlzeDelay:
                        if index == 26: # spacing
                            sentence += ' '
                        elif index == 27: # clear
                            sentence = ''
                        elif index == 28: # delete
                            sentence = sentence[:-1]
                        else:
                            sentence += gesture[index]
                        startTime = time.time()
 

- #Draw gesture result : gesture 결과값을 만드는 부분 

 

 

            # 손에 랜드마크를 그려줌  
            mp_drawing.draw_landmarks(img,res,mp_hands.HAND_CONNECTIONS)
    # 화면에 글자 보이게 하기  
    cv2.putText(img,sentence,(20,440),cv2.FONT_HERSHEY_SIMPLEX,2,(255,255,255),3)
 

- 랜드마크는 손에 실제로 어떤 수화가 인식되었는지 시각적으로 보여주는 기능이다 

- putText 함수로 최하단에 인식한 내용 입력 
- 크게 2가지 putText를 활용 
 1) 손 위에 알파벳 혹은 제스쳐 입력 
 2) 최하단에 인식한 내용 입력 

 

 

    cv2.imshow('HandTracking',img)

    # q키 누르면 중단하기
    if cv2.waitKey(1) == ord('q'):
            break
   
cv2.destroyAllWindows()

- opencv의 imshow 이용해서 윈도우창에서 보이게 했다  

- q키를 누르면 break (프로그램 강제 종료)

 


# 다음에는 해당 소스와 flask를 이용해서 웹으로 여는 과정까지 추가할 것이다