PySide6中在QLabel的QImage上绘制ROI矩形的异常问题
QLabel显示图片时,绘制的矩形在窗口背景而非图片上的问题解决
问题描述
基于PySide6创建了AnotherWidget窗口,内部包含QLabel(用于显示QImage)和多个QLineEdit(展示ROI的坐标与尺寸)。目前QLabel能正常显示图片,但绘制的矩形选区出现在窗口背景而非图片上,鼠标事件与绘制事件逻辑看似正常。
原代码片段
AnotherWidget代码
class AnotherWidget(QWidget, Ui_ROIWidget): roi_selected = Signal(int,int,int,int) def __init__(self): super().__init__() self.setupUi(self) self.setWindowTitle("Custom ROI selection") self.begin = QPoint() self.end = QPoint() def paintEvent(self, event): super().paintEvent(event) qp = QPainter(self) br = QBrush(QColor(100, 10, 10, 40)) qp.setBrush(br) qp.drawRect(QRect(self.begin, self.end)) def mousePressEvent(self, event): self.begin = event.pos() self.end = event.pos() self.update() def mouseMoveEvent(self, event): self.end = event.pos() self.update() def mouseReleaseEvent(self, event): self.begin = event.pos() self.end = event.pos() self.rect = QRect(self.start_point, self.end_point).normalized() self.update() x, y, width, height = self.rect.x(), self.rect.y(), self.rect.width(), self.rect.height() self.roi_selected.emit(x, y, width, height) self.update() @Slot() def show_image(self,image): self.label.setPixmap(QPixmap.fromImage(image))
线程与主窗口代码
class CameraProccess(QThread): #Signal type definition Image=Signal(QImage) ImageProcess = Signal(QImage) ImageCaptured = Signal(QImage) ImageROI = Signal(QImage) [...] class mainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.setWindowTitle("Recorder Inspection Monitor V1.0") self.resize(1280,800) self.ROIwindow = AnotherWidget() self.setROIButton.clicked.connect(self.toggle_roiWindow) [...] def toggle_roiWindow(self): global setROI setROI = True if self.ROIwindow.isVisible()==False: self.Worker1_Opencv.ImageROI.connect(self.ROIwindow.show_image) self.ROIwindow.show() else: self.Worker1_Opencv.ImageROI.connect(self.ROIwindow.show_image)
问题原因
- 绘制层级错误:当前在
AnotherWidget(父窗口)的paintEvent中绘制矩形,而QLabel是父窗口的子控件,Qt的绘制顺序是父窗口先绘制,子控件覆盖在父窗口之上,导致矩形被QLabel的图片遮挡,看起来像是在窗口背景上。 - 坐标不匹配:鼠标事件获取的坐标是相对于父窗口的,而非QLabel内部图片的坐标,即使解决层级问题,选中的矩形也无法对应图片的实际像素位置(尤其是图片在QLabel中缩放显示时)。
- 代码bug:
mouseReleaseEvent中使用了未定义的self.start_point和self.end_point,应该使用self.begin和self.end。 - 重复连接信号:每次切换窗口显示状态都重复连接
ImageROI信号,会导致信号触发多次。
解决方案
核心思路是将绘制与鼠标事件逻辑转移到QLabel内部,确保矩形绘制在图片之上,同时处理坐标转换匹配图片实际像素。
步骤1:自定义ROIImageLabel控件
创建继承自QLabel的自定义控件,内置ROI绘制与鼠标事件处理:
from PySide6.QtWidgets import QLabel from PySide6.QtGui import QPainter, QColor, QBrush, QPixmap, QImage from PySide6.QtCore import QPoint, QRect, Signal, Slot class ROIImageLabel(QLabel): roi_selected = Signal(int, int, int, int) def __init__(self, parent=None): super().__init__(parent) self.begin = QPoint() self.end = QPoint() self.setMouseTracking(True) def paintEvent(self, event): # 先绘制QLabel原有的图片内容 super().paintEvent(event) # 绘制ROI矩形 if not self.begin.isNull() and not self.end.isNull(): painter = QPainter(self) brush = QBrush(QColor(100, 10, 10, 40)) painter.setBrush(brush) # 归一化矩形,确保左上角坐标小于右下角 painter.drawRect(QRect(self.begin, self.end).normalized()) def mousePressEvent(self, event): self.begin = event.pos() self.end = event.pos() self.update() def mouseMoveEvent(self, event): self.end = event.pos() self.update() def mouseReleaseEvent(self, event): self.end = event.pos() rect = QRect(self.begin, self.end).normalized() pixmap = self.pixmap() if pixmap: # 获取图片在QLabel中的实际显示区域(处理对齐、缩放) pix_rect = self.pixmapRect() # 计算图片与显示区域的缩放比例 scale_x = pixmap.width() / pix_rect.width() scale_y = pixmap.height() / pix_rect.height() # 将QLabel坐标转换为图片实际像素坐标 img_x = int((rect.x() - pix_rect.x()) * scale_x) img_y = int((rect.y() - pix_rect.y()) * scale_y) img_width = int(rect.width() * scale_x) img_height = int(rect.height() * scale_y) # 确保坐标在图片范围内,避免越界 img_x = max(0, img_x) img_y = max(0, img_y) img_width = min(img_width, pixmap.width() - img_x) img_height = min(img_height, pixmap.height() - img_y) self.roi_selected.emit(img_x, img_y, img_width, img_height) self.update() @Slot(QImage) def set_image(self, image): self.setPixmap(QPixmap.fromImage(image))
步骤2:修改AnotherWidget
将QtDesigner中创建的QLabel提升为ROIImageLabel(QtDesigner操作:选中QLabel → 右键 → 提升为 → 输入类名ROIImageLabel,指定对应的模块路径),然后修改AnotherWidget代码:
class AnotherWidget(QWidget, Ui_ROIWidget): def __init__(self): super().__init__() self.setupUi(self) self.setWindowTitle("Custom ROI selection") # 复用自定义Label的信号 self.roi_selected = self.label.roi_selected @Slot(QImage) def show_image(self, image): self.label.set_image(image)
步骤3:修复主窗口的信号连接
避免重复连接信号,提前在初始化时完成连接:
class mainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.setWindowTitle("Recorder Inspection Monitor V1.0") self.resize(1280,800) self.ROIwindow = AnotherWidget() self.setROIButton.clicked.connect(self.toggle_roiWindow) # 提前连接信号,避免重复连接 self.Worker1_Opencv.ImageROI.connect(self.ROIwindow.show_image) def toggle_roiWindow(self): global setROI setROI = True if not self.ROIwindow.isVisible(): self.ROIwindow.show() else: self.ROIwindow.hide()
关键说明
- 自定义QLabel确保绘制顺序是先图片后矩形,矩形会叠加在图片上方,不会被遮挡。
- 坐标转换处理了图片在QLabel中的缩放、对齐问题,保证ROI坐标对应图片的实际像素。
- 修复了原代码中的变量未定义bug,以及重复连接信号的问题。
内容的提问来源于stack exchange,提问作者zhilaanabdrsyd
相关产品推荐
相关产品推荐

