Pyqt Showing Video Stream from Opencv

PyQt showing video stream from opencv

The problem is that the function that obtains the image is executed only once and not updating the label.

The correct way is to place it inside a loop, but it will result in blocking the main window. This blocking of main window can be solved by using the QThread class and send through a signal QImage to update the label. For example:

import cv2
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication
from PyQt5.QtCore import QThread, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QImage, QPixmap

class Thread(QThread):
changePixmap = pyqtSignal(QImage)

def run(self):
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if ret:
# https://stackoverflow.com/a/55468544/6622587
rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgbImage.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(rgbImage.data, w, h, bytesPerLine, QImage.Format_RGB888)
p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
self.changePixmap.emit(p)

class App(QWidget):
def __init__(self):
super().__init__()
[...]
self.initUI()

@pyqtSlot(QImage)
def setImage(self, image):
self.label.setPixmap(QPixmap.fromImage(image))

def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.resize(1800, 1200)
# create a label
self.label = QLabel(self)
self.label.move(280, 120)
self.label.resize(640, 480)
th = Thread(self)
th.changePixmap.connect(self.setImage)
th.start()
self.show()

I am trying to display video stream from opencv to pyqt5 interface but my code is not working

Try this concept
create a python file main_window.py
Paste this in it

import sys

# import some PyQt5 modules
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QWidget
from PyQt5.QtGui import QImage
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import QTimer

# import Opencv module
import cv2

from ui_main_window import *

class MainWindow(QWidget):
# class constructor
def __init__(self):
# call QWidget constructor
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)

# create a timer
self.timer = QTimer()
# set timer timeout callback function
self.timer.timeout.connect(self.viewCam)
# set control_bt callback clicked function
self.ui.control_bt.clicked.connect(self.controlTimer)

# view camera
def viewCam(self):
# read image in BGR format
ret, image = self.cap.read()
# convert image to RGB format
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# get image infos
height, width, channel = image.shape
step = channel * width
# create QImage from image
qImg = QImage(image.data, width, height, step, QImage.Format_RGB888)
# show image in img_label
self.ui.image_label.setPixmap(QPixmap.fromImage(qImg))

# start/stop timer
def controlTimer(self):
# if timer is stopped
if not self.timer.isActive():
# create video capture
self.cap = cv2.VideoCapture(0)
# start timer
self.timer.start(20)
# update control_bt text
self.ui.control_bt.setText("Stop")
# if timer is started
else:
# stop timer
self.timer.stop()
# release video capture
self.cap.release()
# update control_bt text
self.ui.control_bt.setText("Start")

if __name__ == '__main__':
app = QApplication(sys.argv)

# create and show mainWindow
mainWindow = MainWindow()
mainWindow.show()

sys.exit(app.exec_())

Create a new file and name as ui_main_window.py and paste code below

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(525, 386)
self.horizontalLayout = QtWidgets.QHBoxLayout(Form)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.image_label = QtWidgets.QLabel(Form)
self.image_label.setObjectName("image_label")
self.verticalLayout.addWidget(self.image_label)
self.control_bt = QtWidgets.QPushButton(Form)
self.control_bt.setObjectName("control_bt")
self.verticalLayout.addWidget(self.control_bt)
self.horizontalLayout.addLayout(self.verticalLayout)

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

def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Cam view"))
self.image_label.setText(_translate("Form", "TextLabel"))
self.control_bt.setText(_translate("Form", "Start"))

OpenCV Video Feed Automatic Resizing with PyQt5

You should get the resize event of self.camera and set it's width to self.display_width and it's height to self.display_height

and this statement p = convert_to_Qt_format.scaled(self.display_width, self.display_height, Qt.KeepAspectRatio) will do the rest of the work



Update

Set the Minimum size to avoid recursive loop which will keep expanding window

To avoid shaking you can use a fixed size container or remove Qt.KeepAspectRatio which helps shaking the window

  • HERE IS THE UPDATED CODE
from PyQt5 import QtCore, QtGui, QtWidgets
import cv2
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from numpy import ndarray

class Camera(QWidget):
def __init__(self, port):
super().__init__()

self.display_width = 640
self.display_height = 480

self.camera = QLabel()
# self.camera.setGeometry(0,0,640,480)
self.camera.resize(self.display_width, self.display_height)

# Set Minimum size of camera stream to avoid going in recursive loop
self.camera.setMinimumWidth(640)
self.camera.setMinimumHeight(480)

self.label = QLabel(f'Port {port}')

self.label.setStyleSheet("""
color: black;
font: 30px;
""")

# Layout

self.layout = QVBoxLayout()
self.layout.addWidget(self.label)
self.layout.addWidget(self.camera)

self.setLayout(self.layout)

# Get the resize Event Callback
self.resizeEvent = self.label_resize
self.camera.resizeEvent = self.camera_resize

self.thread = VideoThread(port)
self.thread.change_pixmap_signal.connect(self.update_image)
self.thread.start()

# Resize Event Callback
def label_resize(self, resizeEvent:QResizeEvent):
self.camera.resize(resizeEvent.size())

# Resize Event Callback
def camera_resize(self, resizeEvent:QResizeEvent):
self.display_width, self.display_height = self.camera.width(), self.camera.height()

def close_event(self, event):
self.thread.stop()
event.accept()

@pyqtSlot(ndarray)
def update_image(self, cv_img):
qt_img = self.convert_cv_qt(cv_img)
self.camera.setPixmap(qt_img)

def convert_cv_qt(self, cv_img):
rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_image.shape
bytes_per_line = ch * w
convert_to_Qt_format = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)
p = convert_to_Qt_format.scaled(self.display_width, self.display_height, Qt.KeepAspectRatio)
return QPixmap.fromImage(p)

class VideoThread(QThread):
change_pixmap_signal = pyqtSignal(ndarray)

def __init__(self, port):
super().__init__()
self.running = True
self.port = port

def run(self):
self.capture = cv2.VideoCapture(self.port)

while self.running:
self.ret, self.image = self.capture.read()
if self.ret:
self.change_pixmap_signal.emit(self.image)

self.capture.release()

def stop(self):
self.running = False
self.wait()

How to display a cv2 video inside a python GUI?

Inside the while loop, you need to convert frame into a QPixmap again, similar to what you did above, and then update ui:

cap = cv2.VideoCapture('video.mov')

while (cap.isOpened()):
ret, frame = cap.read()
if not ret:
break

frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
pix = QPixmap.fromImage(img)
pix = pix.scaled(600, 400, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.ui.label_7.setPixmap(pix)

self.ui.frame = pix # or img depending what `ui.frame` needs

if cv2.waitKey(1) & 0xFF == ord('q'):
break

cap.release()

Pyqt crashes when trying to show opencv videostream

Fixed it using QMutex and QWaitCondition to prevent update call while main thread is already updating. Apparently, issue was in that. eyllanesc, I'm new here as you see, should I make an answer in original thread?

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QMessageBox
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot, Qt, QMutex, QWaitCondition
import cv2
import sys
import time

class CamThread(QThread):
changemap = pyqtSignal('QImage')

def __init__(self, mutex, condition):
super().__init__()
self.mutex = mutex
self.condition = condition

def run(self):
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while True:
try:
ret, img_rgb = cap.read()
if ret:
rgb = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2RGB)

#any other image processing here

convert = QImage(rgb.data, rgb.shape[1], rgb.shape[0], QImage.Format_RGB888)
p = convert.scaled(640, 480, Qt.KeepAspectRatio)
self.changemap.emit(p)
self.condition.wait(self.mutex)

except:
print('error')

class App(QWidget):
time = 0

def __init__(self):
super().__init__()
self.title = 'webcam'
self.mutex = QMutex()
self.condition = QWaitCondition()
self.initUI()

@pyqtSlot('QImage')
def setImage(self, image):
self.mutex.lock()
try:
self.label.setPixmap(QPixmap.fromImage(image))
finally:
self.mutex.unlock()
self.condition.wakeAll()

def initUI(self):
self.mutex.lock()
self.setWindowTitle(self.title)
self.setGeometry(100, 100, 640, 480)
self.resize(640, 480)
self.label = QLabel(self)
self.label.resize(640, 480)
self.thr = CamThread(mutex = self.mutex,condition=self.condition)
self.thr.changemap.connect(self.setImage)
self.thr.start()

app = QApplication(sys.argv)
win = App()
win.show()
app.exit(app.exec_())

N.B. You still need to properly stop thread and close camera connection in this example.



Related Topics



Leave a reply



Submit