Programming
OpenCV(Python) + PyQt
이세우
2019. 11. 15. 17:29
OpenCV로 영상처리나 컴퓨터 비전을 처리하고 나서 결과를 화면에 표시하려면 결국 창을 띄워야 하는데,
OpenCV의 imshow() 함수 만으로는 역부족인 경우가 많습니다.
파이썬 언어에서 인기 있는 멀티 플랫폼 GUI 프레임웤인 PyQt5로 멋지게 GUI를 구현하고 영상만 OpenCV로 처리해서 보여주고 싶을때
어떻게 하는지 설명하는 포스트입니다.
PyQt를 설치하고 사용을 어떻게 하는지는 여러 블로그와 유튜브에 올라와 있는 강좌를 이용해서 공부하시구요.
여기서는 단순히 OpenCV-Python가 읽은 이미지를 어떻게 PyQt로 출력 하는지만 설명합니다.
OpenCV-Python에서는 모든 이미지 정보는 NumPy의 ndarray 객체에 담겨 있습니다.
결국 ndarray 객체를 어떻게 PyQt로 출력하느냐가 관건인데요.
우선 PyQt5 자체적으로 이미지 파일을 화면에 보여주는 방법을 간단히 살펴 보겠습니다.
from PyQt5 import QtWidgets
from PyQt5 import QtGui
app = QtWidgets.QApplication([])
label = QtWidgets.QLabel()
pixmap = QtGui.QPixmap('tkv.jpg')
label.setPixmap(pixmap)
label.resize(pixmap.width(), pixmap.height())
label.show()
app.exec_()
QtGui.QPixmap() 객체를 생성할 때 이미지 파일의 경로를 전달해서 생성하고 그 객체를
QtWidgets.QLabel 객체에는 setPixmap() 함수가 있는데 여기에 전달하면 끝입니다.
여기서 QPixmap 객체를 생성할 때 파일의 경로를 전달했는데, 이 부분을 OpenCV에서 읽은 ndarray를 통해 읽게 해주면 되는게 핵심입니다.
그러자면 다시 새로운 객체가 필요한데요.
QtGui.QImage 객체 입니다. 이 객체를 생성할때 생성자에 ndarray.data 를 전달하고 다시 QImage를 QPixmap으로 변환하는 겁니다.
아래 코드는 위와 같은 이미지를 OpenCV로 읽어서 PyQt로 보여주는 예제 입니다.
import cv2
from PyQt5 import QtWidgets
from PyQt5 import QtGui
app = QtWidgets.QApplication([])
label = QtWidgets.QLabel()
img = cv2.imread('tkv.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h,w,c = img.shape
qImg = QtGui.QImage(img.data, w, h, w*c, QtGui.QImage.Format_RGB888)
pixmap = QtGui.QPixmap.fromImage(qImg)
label.setPixmap(pixmap)
label.resize(pixmap.width(), pixmap.height())
label.show()
app.exec_()
자 그럼 카메라로 캡쳐한 화면을 실시간을 처리하게 해보면 모든 문제는 해결되는 셈이네요.
카메라로 캡쳐한 프레임을 실시간으로 보여주려면 단순히 한번의 흐름으로 끝나지는 않을겁니다.
그래서 while 같은 반복문을 써야 하는데, QApplication 자체도 대기 상태이고 그 안에서 무한 반복 문이 나오면 그것도 대기상태가 되기 때문에
화면이 그려질 방법이 없을 테니, 결국 Thread가 필요하다는 결론입니다.
Qt에서 제공하는 QThread를 쓰든, 이벤트를 날리고 받든, 어쨌든 쓰레드가 필요합니다.
저는 가장 단순하게 Python 기본 모듈인 threading 모듈을 가지고 구현해 보겠습니다.
코드는 객체지향을 완전히 제거해서 흐름에만 집중 할 수 있게 했습니다.
화면에 카메라를 켜고 끄는 버튼을 만들어서 조정하게 했구요. 종료 버튼을 누르면 쓰레드도 자동 종료되게 했습니다.
import cv2
import threading
import sys
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5 import QtCore
running = False
def run():
global running
cap = cv2.VideoCapture(-1)
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
label.resize(width, height)
while running:
ret, img = cap.read()
if ret:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h,w,c = img.shape
qImg = QtGui.QImage(img.data, w, h, w*c, QtGui.QImage.Format_RGB888)
pixmap = QtGui.QPixmap.fromImage(qImg)
label.setPixmap(pixmap)
else:
QtWidgets.QMessageBox.about(win, "Error", "Cannot read frame.")
print("cannot read frame.")
break
cap.release()
print("Thread end.")
def stop():
global running
running = False
print("stoped..")
def start():
global running
running = True
th = threading.Thread(target=run)
th.start()
print("started..")
def onExit():
print("exit")
stop()
app = QtWidgets.QApplication([])
win = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout()
label = QtWidgets.QLabel()
btn_start = QtWidgets.QPushButton("Camera On")
btn_stop = QtWidgets.QPushButton("Camera Off")
vbox.addWidget(label)
vbox.addWidget(btn_start)
vbox.addWidget(btn_stop)
win.setLayout(vbox)
win.show()
btn_start.clicked.connect(start)
btn_stop.clicked.connect(stop)
app.aboutToQuit.connect(onExit)
sys.exit(app.exec_())
처음 실행하면 아래와 같은 모습입니다.
카메라를 켜면 아래와 같은 모습입니다.
제가 구현한 방법 말고도 PyQt에서 제공하는 pyqtSignal과 slot을 이용하고 paintEvent을 재정의하는 방법도 있습니다.
하지만 결국 가장 중요한 부분은 QImage를 생성해서 Widget으로 하여금 보이게 한다는 것입니다.
이상입니다.