행복한 하루
[AI] mediapipe를 이용한 손 제스처 인식하기 2 – PyCharm + mediapipe + OpenCV + PySide2 본문
[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
4. [파이썬] MediaPipe 손 인식(Hands)
https://puleugo.tistory.com/10
5. [파이썬 OpenCV] 영상의 대칭 변환 - cv2.flip
https://deep-learning-study.tistory.com/186