행복한 하루

[AI] mediapipe를 이용한 손 제스처 인식하기 2 – PyCharm + mediapipe + OpenCV + PySide2 본문

Artificial Intelligence/Vision

[AI] mediapipe를 이용한 손 제스처 인식하기 2 – PyCharm + mediapipe + OpenCV + PySide2

변화의 물결 2023. 6. 18. 00:05

 

안녕하세요.

 

 이전 글에서 설치하는 내용을 확인하였다면 이제 제스처를 활용한 프로그램을 만들 수 있습니다.

 https://remnant24c.tistory.com/534


1. 미리 이해하면 좋은 내용

 - Label을 생성해서 영상을 출력하는가에 대한 궁금증이 있었는데, 참고 사이트에 나와 있어서 가져와보았습니다.  “QPixmap 지금까지 다뤘던 것들과 다르게 자체적인 위젯이 없어서 Label 이용하여 이미지를 표현합니다. “

 

 - 테스트 소스는 .ui (Qt Designer)를 사용하지 않았기 때문에 code 상에서 ui 생성해서 구성하고 있습니다.

 

 - Thread를 하나 생성하고 실시간 영상을 mediapipe에서 제공해 주는 Hand Gesture 알고리즘에 프레임을 대입합니다. 그리고 반환되는 정보에서 손가락 마디를 파악해서 표시해 주 등 처리 계속해서 반복합니다.

2. 전체 소스 

 - 부분내용으로 하려고 했는데 그러면 연결이 안 될 것 같아서 코드 설명은 소스의 주석으로 표시하였습니다. 위에서 아래로 한번 보시고, 아래서 위로 한번 다시 보시면 대략 이해하실 수 있으리라 생각됩니다. 더 많은 예외처리 등 작업을 해야 하는데 동작확인 차원의 코드라고 보시면 될 듯합니다.

# HandTracking.py
# 실행에 필요한 패키지를 임포트
import os
import sys
import time

import cv2
import mediapipe as mp

# from PySide2.QtWidgets import QApplication, QLabel
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtGui import QPixmap, QImage
from PySide2.QtCore import QSize

#비디오 재생을 위해 스레드 패키지
import threading

class Ui_MainWindow(object):
    #기본적으로 창만드는 작업
    def setupUi(self, MainWindow):
        # 메인창 위치 및 사이즈
        MainWindow.setObjectName("MainWindow")
        # MainWindow.resize(800, 600) #창 사이즈
        # resize를 사용해도 되지만, 현재 영상 재생시 드래그해서 사이즈를 조절하면  (0xC000041D) 에러 발생해서 고정사이즈로 함
        MainWindow.setFixedSize(QSize(800,600))
        MainWindow.move(100,100) # 창 뜰 때 위치

        # QT designer를 사용하는 것이 아니기 때문에 코드로 폼에 생성될 UI를 만들어주는 내용입니다.
        # 영상을 출력해주는 Label 생성, (영상을 출력하기 위함)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.video_viewer_label = QtWidgets.QLabel(self.centralwidget)
        self.video_viewer_label.setGeometry(QtCore.QRect(0, 0, 800, 600))

        # 상단 메뉴바 생성
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        self.menubar.addMenu("&File")   # 나중을 메뉴 선택하기 위해서 하나 생성
        # self.menubar.addAction(newAct)    # 메뉴를 사용하려면 Action을 별도로 만들어야 실행될 함수와 연결됨
        MainWindow.setMenuBar(self.menubar)

        # 하단 상태바 생성
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def Video_to_frame(self, MainWindow):

        mpHands = mp.solutions.hands
        hands = mpHands.Hands( static_image_mode = False,   # 정적 이미지 모드
                               max_num_hands = 2,           # 손 최대 갯수
                               model_complexity = 1,        # 모델 복잡성
                               min_detection_confidence = 0.5,  # 최소 탐지 신뢰도
                               min_tracking_confidence = 0.5 )  # 최소 추척 신뢰도

        mpDraw = mp.solutions.drawing_utils     # landmark를 그려주는 함수 패키지를 불러옴

        pTime = 0
        cTime = 0
        cap = cv2.VideoCapture(0) #저장된 영상 가져오기 프레임별로 계속 가져오는 듯

        ###cap으로 영상의 프레임을 가지고와서 전처리 후 화면에 띄움###
        while cap.isOpened():
            self.ret, self.frame = cap.read() # 한 프레임 씩 읽기
            if self.ret:
                self.rgbImage = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB) # BGR -> RGB로 변환

                ############## Gesture API START
                # 입력된 RGB 이미지를 처리하고 반환 값으로는 A NamedTuple object 형태로 반환합니다.
                # 반환된 필드는 multi_hand_landmarks, multi_hand_world_landmarks, multi_handedness를 가지고 있습니다.

                results = hands.process(self.rgbImage)
                # print('Handedness:', results.multi_handedness) // 왼손인지 오른손인지 파악

                if results.multi_hand_landmarks:
                    for handLms in results.multi_hand_landmarks:
                        for id, lm in enumerate(handLms.landmark):
                            h, w, c = self.frame.shape
                            cx, cy = int(lm.x * w), int(lm.y * h)
                            # print(id, cx, cy)

                            # 지점마다 원을 그린다.
                            cv2.circle(self.frame, (cx, cy), 5, (255, 0, 255), cv2.FILLED)
                        # 점마다 라인을 연결해줍니다.
                        mpDraw.draw_landmarks(self.frame, handLms, mpHands.HAND_CONNECTIONS)

                # FPS 계산 - 1초당 그리는 프레임 수를 계산
                cTime = time.time()
                fps = 1 / (cTime - pTime)
                pTime = cTime

                # 좌우대칭(1), 상하대칭(0), 좌우상하대칭(-1)
                self.frame = cv2.flip(self.frame, 1)
                # 상단에 FPS를 표시
                cv2.putText(self.frame, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 255), 3)
                ############## Gesture API END

                self.bgrImage = cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR) # BGR -> RGB로 변환
                self.convertToQtFormat = QImage(self.bgrImage.data, self.bgrImage.shape[1], self.bgrImage.shape[0],QImage.Format_RGB888)

                self.pixmap = QPixmap(self.convertToQtFormat)
                self.p = self.pixmap.scaled(800, 600, QtCore.Qt.IgnoreAspectRatio) #프레임 크기 조정

                self.video_viewer_label.setPixmap(self.p)
                self.video_viewer_label.update() #프레임 띄우기

                # OpenCV에서는 영상을 프레임단위로 가져오기 때문에 sleep을 통해서 프레임을 연결시켜주어 영상으로 보여줌
                time.sleep(0.01)  # 영상 1프레임당 대략 0.01초로, 영상 재생속도 조절

            else:
                break

        cap.release()
        cv2.destroyAllWindows()

    # 창 이름 설정
    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("PySide&OpenCV&mediapipe", "PySide&OpenCV&mediapipe"))

    # video_to_frame을 영상 재생 스레드
    def video_thread(self, MainWindow):
        thread = threading.Thread(target=self.Video_to_frame, args=(self,))
        thread.daemon = True  # 프로그램 종료시 해당 스레드도 함께 종료 (백그라운드 실행 X)
        thread.start()

# 메인문
if __name__ == "__main__":

    # 화면 만들려면 기본으로 있어야 하는 코드
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)

    # 영상 스레드 시작
    ui.video_thread(MainWindow)

    # 창 띄우기
    MainWindow.show()

    sys.exit(app.exec_())

  

3. 실행 결과

 -  실행하면 창에는 영상이 나오고, PyCharm Terminal 창에는 포인트 지점과 좌표가 표시됩니다.  

 

 

 - 프레임 수와 손가락 마디마다 점이 표시되고 손을 추적하는 것을 확인할 수 있습니다.

 - 이제는 PySide(Qt) 프로그래밍을 통해  다른 아이디어로 프로그램을 만들 수 있을 것입니다.

 

 - 참고사항, 창(MainWindow) 사이즈를 조절하면 아래와 같은 에러 발생해서 크기를 고정하였습니다. 현재는 사이즈 조절만 하지 않는다면 프로그램이 죽는 현상은 없었습니다.

  exit code -1073740771 (0xC000041D)

 

 

감사합니다.

 

 

<참고 사이트>

1. Hand landmarks detection guide

https://developers.google.com/mediapipe/solutions/vision/hand_landmarker

2. PyQt5 , Open CV라이브러리를 활용해서 GUI로 동영상 재생하기

https://for-sign.tistory.com/40

3. 02.15 Display - QPixmap

https://wikidocs.net/38038

4. [파이썬] MediaPipe 손 인식(Hands)

https://puleugo.tistory.com/10

5. [파이썬 OpenCV] 영상의 대칭 변환 - cv2.flip

https://deep-learning-study.tistory.com/186  

 

 

Comments