본문 바로가기
파이썬 프로그래밍/PyQt5 공부하기

6. PyQt5와 opencv 연동하기 : 동영상 불러오기

by Majestyblue 2024. 11. 24.

이번도 악보쓰는 프로그래머 블로그를 참고하였다.

OpenCV(Python) + PyQt

 

OpenCV(Python) + PyQt

OpenCV로 영상처리나 컴퓨터 비전을 처리하고 나서 결과를 화면에 표시하려면 결국 창을 띄워야 하는데, OpenCV의 imshow() 함수 만으로는 역부족인 경우가 많습니다. 파이썬 언어에서 인기 있는 멀티

blog.xcoda.net

 

동영상은 이미지를 불러오는 방법에서 while문으로 frame 단위로 읽는 것이라 생각하면 된다. 먼저 작동화면을 보자

 

 

동영상 열기를 누르고 동영상을 불러오면 자동 재생되게 만들었다. 코드를 살펴보자

 

import sys, cv2
from PyQt5.QtWidgets import *
from PyQt5.QtGui import * 

def open_movie():
    print("동영상 불러오기를 성공하였습니다.")

class MyWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        
        self.setWindowTitle("physics Lab")
        self.setGeometry(100, 100, 1600, 800) 
        self.setWindowIcon(QIcon("physics.png"))
        
        # 상태바 생성
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        
        self.vid_label = QLabel(parent=self)
        self.vid_label.move(10, 10)
        
        self.btn = QPushButton(text = "동영상 열기", parent = self)
        self.btn.move(1500, 10)
        self.btn.clicked.connect(self.btn_clicked)
        
        self.btn2 = QPushButton(text = "이미지 삭제", parent = self)
        self.btn2.move(1500, 40)
        self.btn2.clicked.connect(self.btn2_clicked)
        
    def btn_clicked(self):
        fname, _ = QFileDialog.getOpenFileName(self, "Choose File", "", "All files(*)")
        if not fname: 
            self.status_bar.showMessage("파일 선택이 취소되었습니다.")
            return

        cap = cv2.VideoCapture(fname) # 동영상 갭쳐 객체 생성
        width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
        height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
        self.vid_label.resize(width, height)

        if cap is None:  # 이미지가 제대로 읽히지 않은 경우
            self.status_bar.showMessage("동영상을 불러오는 데 실패하였습니다.")
            return  # 에러 처리 후 함수 종료
        
        self.status_bar.showMessage("동영상을 불러오는 데 성공하였습니다.")
        
        while True:
            ret, img = cap.read() # 프레임 읽기
            if ret:
                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                h, w, c = img.shape
                qImg = QImage(img.data, w, h, w*c, QImage.Format_RGB888)
                self.pixmap = QPixmap.fromImage(qImg)
                self.vid_label.setPixmap(self.pixmap)
                cv2.waitKey(int(cap.get(cv2.CAP_PROP_FPS))) 
            else:
                self.status_bar.showMessage("동영상 재생에 실패하였습니다.")
                break
        cap.release()
        
        
    def btn2_clicked(self):
        self.vid_label.clear()
        self.status_bar.clearMessage()


app = QApplication(sys.argv)

win = MyWindow()

win.show()
app.exec_()

 

 

가장 중요한 역할을 하는 btn_clicked 함수 위주로 알아보자. 동영상을 선택하고, 해당 동영상을 화면에 표시하며 동영상의 상태를 알려주는 역할을 한다.

 

1. 파일 선택

fname, _ = QFileDialog.getOpenFileName(self, "Choose File", "", "All files(*)")
  • QFileDialog.getOpenFileName을 사용하여 파일 선택 대화상자를 띄운다. 사용자가 선택한 파일의 경로가 fname 변수에 저장된다. 
  • 사용자가 파일 선택을 취소하면 fname은 빈 문자열이 되므로, 이 경우 상태바에 취소 메시지를 표시하고 함수를 종료한다.

 

2. 비디오 캡처 객체 생성

cap = cv2.VideoCapture(fname)
  • OpenCV의 VideoCapture 클래스를 사용하여 선택한 동영상 파일을 열고, 이를 통해 동영상을 캡처할 수 있는 객체 cap을 생성한다.

 

3. 동영상의 너비와 높이 설정

width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.vid_label.resize(width, height)
  • cap.get 메서드를 사용하여 동영상의 프레임 너비와 높이를 가져온다. 이후 이 값을 기반으로 vid_label의 크기를 조정한다.

 

4. 동영상 로드 확인

if cap is None:
    self.status_bar.showMessage("동영상을 불러오는 데 실패하였습니다.")
    return
  • cap 객체가 제대로 생성되었는지 확인한다. 만약 객체가 None이라면 동영상 로드에 실패한 것이므로 오류 메시지를 상태바에 표시하고 함수를 종료한다.

 

5. 동영상 재생

while True:
    ret, img = cap.read()
    if ret:
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        h, w, c = img.shape
        qImg = QImage(img.data, w, h, w*c, QImage.Format_RGB888)
        self.pixmap = QPixmap.fromImage(qImg)
        self.vid_label.setPixmap(self.pixmap)
        cv2.waitKey(int(cap.get(cv2.CAP_PROP_FPS)))
    else:
        self.status_bar.showMessage("동영상 재생에 실패하였습니다.")
        break
  • while True 루프를 통해 동영상의 각 프레임을 반복적으로 읽어온다. cap.read() 메서드를 사용하여 프레임을 읽고, 성공적으로 읽었다면 ret이 True가 된다. 
  • 읽은 이미지를 RGB 형식으로 변환한 후, QImage로 변환하여 QPixmap으로 변환한다. 그 후 vid_label에 이 픽스맵을 설정하여 화면에 표시한다.
  • cv2.waitKey(int(cap.get(cv2.CAP_PROP_FPS)))를 사용하여 프레임 간의 간격을 조절하며, 이는 동영상의 FPS(Frame Per Second)에 맞춰 프레임을 재생하도록 한다.
  • 만약 더 이상 읽을 프레임이 없다면, 상태바에 재생 실패 메시지를 표시하고 루프를 종료한다.

 

6. 리소스 해제

cap.release()

 

  • 동영상 재생이 완료되면 cap.release()를 호출하여 캡처 객체를 해제하여 메모리를 정리한다.

 

이 코드의 치명적인 문제가 있다. 바로 MyWindow()인 객체가 loop이 돌아가고 있는데 여기에 while문으로 동영상을 불러오고 처리하다 보니 아래와 같이 이벤트가 먹히지 않는 것이다. 동영상 재생 중간에 '동영상 제거' 버튼을 눌러도 제거되지 않는다.

 

이를 해결하기 위해 동영상 작업 과정을 쓰레드로 처리해야 한다. 다음 시간에는 PyQt에서 쓰레드를 어떻게 이용할 수 있는지 알아보겠다.