使用Qt编写界面显示OpenCV采集的图片时,如果使用QLabel作为图片显示控件,那么该控件的推荐最小尺寸(minimumSizeHint)就是图片的尺寸。这导致图片显示控件无法进一步压缩,在与其它控件一起布局时,缺乏灵活性。缩小窗口显示时,其它控件如表格、文本框都能等比例缩小,图片控件会一直保持原始尺寸。
如下面的代码,对话框中内只显示一张图片。打印出的图片minimumnSizeHint为QSize(556, 222)。对话框无法压缩到比这个尺寸还小(给label设置QSizePolicy::Ignored,可以进一步缩小,但图片无法完整显示)。
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QDialog, QHBoxLayout, QLabel
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = QDialog()
label = QLabel(dialog)
label.setPixmap(QPixmap('picture.png'))
label.setAlignment(Qt.AlignCenter)
layout = QHBoxLayout(dialog)
layout.addWidget(label)
dialog.show()
dialog.resize(480, 320)
print(label.minimumSizeHint())
sys.exit(app.exec_())
运行结果显示对话框不能缩小显示。
解决方法:使用样式表显示图片
使用样式表,将图片文件显示到控件背景上。这样图片就可以自由缩小了。
import sys
from PyQt5.QtWidgets import QApplication, QDialog, QHBoxLayout, QWidget
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = QDialog()
widget = QWidget(dialog)
widget.setStyleSheet('image:url(picture.png)')
layout = QHBoxLayout(dialog)
layout.addWidget(widget)
dialog.show()
dialog.resize(480, 320)
print(widget.minimumSizeHint())
sys.exit(app.exec_())
注意,样式表中要属性要用image。属性使用border-image或background-image会有不同的效果(前者会调整图片铺满整个控件,后者会自动重复多张图片铺满整个控件)。
运行效果如下。
解决方法二:使用paintEvent()自绘背景
使用OpenCV采集的图像数据,有时候不会保存为文件,而是转换为QPixmap进行显示。要达到前面同样的效果,可以使用paintEvent()绘制到背景上。例子中作为对比,还是把图片用QPixmap载入后进行显示。MyWidget继承了QWidget的paintEvent,实现了背景的绘制。
import sys
from PyQt5.Qt import Qt, QPointF
from PyQt5.QtGui import QPixmap, QPainter, QPaintEvent
from PyQt5.QtWidgets import QApplication, QDialog, QHBoxLayout, QWidget
class MyWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
def paintEvent(self, event: QPaintEvent):
super().paintEvent(event)
pixmap = QPixmap('picture.png')
painter = QPainter(self)
scale = min(self.width()/pixmap.width(), self.height()/pixmap.height(), 1.0)
width = scale * pixmap.width()
height = scale * pixmap.height()
painter.drawPixmap(QPointF((self.width()-width)/2, (self.height()-height)/2),
pixmap.scaled(round(width), round(height), Qt.KeepAspectRatio, Qt.SmoothTransformation))
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = QDialog()
widget = MyWidget(dialog)
layout = QHBoxLayout(dialog)
layout.addWidget(widget)
dialog.show()
dialog.resize(480, 320)
sys.exit(app.exec_())
运行效果与方法二基本相同。
解决方法三:使用QGraphicsView的fitInView()
方法二在绘制时,需要计算图片的缩小比例与绘制位置。而这些都可以能过QGraphicsView的fitInView()函数实现。我们可以写一个QGraphicsView派生类MyGraphicsView,并只包含一个QGraphicsPixmapItem项目用于显示图片。给MyGraphicsView添加一个判断是否需要缩小显示的函数view_resize()。它将判断控件大小无法完整显示图片时,使用fitInView()调节图片显示。 在用户改变控件尺寸或导入新图片时,调用view_resize()。
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QDialog, QHBoxLayout, QGraphicsPixmapItem, QGraphicsScene, QGraphicsView
class MyGraphicsView(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.image = QGraphicsPixmapItem() # 创建Graphics图片对象
self.image.setTransformationMode(Qt.SmoothTransformation) # 图片平滑
self.img_w = 0
self.img_h = 0
scene = QGraphicsScene(self) # 创建Graphics场景
scene.addItem(self.image)
self.setScene(scene) # Graphics视图初始化
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 隐藏滚动条
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def resizeEvent(self, e):
self.view_resize()
super().resizeEvent(e)
def view_resize(self):
if self.img_w > self.width() or self.img_h > self.height():
self.fitInView(self.image, Qt.KeepAspectRatio)
def set_pixmap(self, pixmap: QPixmap):
self.image.setPixmap(pixmap)
self.img_w = pixmap.width()
self.img_h = pixmap.height()
self.view_resize()
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = QDialog()
widget = MyGraphicsView(dialog)
widget.set_pixmap(QPixmap('picture.png'))
layout = QHBoxLayout(dialog)
layout.addWidget(widget)
dialog.show()
dialog.resize(480, 320)
sys.exit(app.exec_())
运行效果如下。