OpenCV Video Feed 使用 PyQt5 自动调整大小
OpenCV Video Feed Automatic Resizing with PyQt5
这是我第一次 post 在 Stack Overflow 上,所以我希望我做的一切都是正确的。
我一直在尝试在使用 PyQt5 制作的 GUI 上使用 OpenCV 获取视频源。我最终让它工作,但它始终是 640 x 480(我设置的大小)。但是,我希望它自动 scale/resize (具有相同的纵横比)以适应其育儿小部件。例如,当我扩展 window 时,视频源也应该缩放。有谁知道这样做的方法吗?
这是我的代码示例,供参考:
相机小工具
class Camera(QWidget):
def __init__(self, port):
super().__init__()
self.display_width = 640
self.display_height = 480
self.camera = QLabel(self)
self.camera.resize(self.display_width, self.display_height)
self.label = QLabel(f'Port {port}')
self.label.setStyleSheet("""
QLabel {
color: white;
font: bold 30px
}
""")
# Layout
self.layout = QVBoxLayout()
self.layout.addWidget(self.label)
self.layout.addWidget(self.camera)
self.setLayout(self.layout)
self.thread = VideoThread(port)
self.thread.change_pixmap_signal.connect(self.update_image)
self.thread.start()
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()
您应该获取 self.camera
的调整大小事件并将其宽度设置为 self.display_width
并将其高度设置为 self.display_height
这条语句 p = convert_to_Qt_format.scaled(self.display_width, self.display_height, Qt.KeepAspectRatio)
将完成剩下的工作
更新
设置最小大小以避免递归循环不断扩大window
为了避免晃动,您可以使用fixed size container
或删除Qt.KeepAspectRatio
,这有助于晃动window
- 这是更新后的代码
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()
这是我第一次 post 在 Stack Overflow 上,所以我希望我做的一切都是正确的。
我一直在尝试在使用 PyQt5 制作的 GUI 上使用 OpenCV 获取视频源。我最终让它工作,但它始终是 640 x 480(我设置的大小)。但是,我希望它自动 scale/resize (具有相同的纵横比)以适应其育儿小部件。例如,当我扩展 window 时,视频源也应该缩放。有谁知道这样做的方法吗?
这是我的代码示例,供参考:
相机小工具
class Camera(QWidget):
def __init__(self, port):
super().__init__()
self.display_width = 640
self.display_height = 480
self.camera = QLabel(self)
self.camera.resize(self.display_width, self.display_height)
self.label = QLabel(f'Port {port}')
self.label.setStyleSheet("""
QLabel {
color: white;
font: bold 30px
}
""")
# Layout
self.layout = QVBoxLayout()
self.layout.addWidget(self.label)
self.layout.addWidget(self.camera)
self.setLayout(self.layout)
self.thread = VideoThread(port)
self.thread.change_pixmap_signal.connect(self.update_image)
self.thread.start()
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()
您应该获取 self.camera
的调整大小事件并将其宽度设置为 self.display_width
并将其高度设置为 self.display_height
这条语句 p = convert_to_Qt_format.scaled(self.display_width, self.display_height, Qt.KeepAspectRatio)
将完成剩下的工作
更新
设置最小大小以避免递归循环不断扩大window
为了避免晃动,您可以使用fixed size container
或删除Qt.KeepAspectRatio
,这有助于晃动window
- 这是更新后的代码
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()