PySide(PyQT)使用场景(QGraphicsScene)进行动态标注的一个demo

用以标注图像的一个基本框架demo

python 复制代码
import sys
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QMainWindow, QLabel, QGraphicsPixmapItem
from PySide6.QtGui import QPixmap, QPainter, QTransform
from PySide6.QtCore import Qt, QPointF, Slot, Signal


class ImageViewer(QGraphicsView):
    mouse_pos = Signal(int, int)  # 原图像素坐标信号
    def __init__(self, image_path, parent=None):
        super(ImageViewer, self).__init__(parent)

        # 场景的初始化
        def init_scene():
            # 设置场景
            self.scene = QGraphicsScene(self)
            self.setScene(self.scene)
            # 设置渲染提示
            self.setRenderHint(QPainter.Antialiasing)   # 开启抗锯齿
            self.setRenderHint(QPainter.SmoothPixmapTransform)  # 开启平滑缩放
            # 设置缩放锚点
            self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)  # 转换时以鼠标为中心
            self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)  # 缩放时以鼠标为中心
            # 创建基础的显示内容
            pixmap = QPixmap(image_path)
            self.pixmap_item = QGraphicsPixmapItem(pixmap)
            self.scene.addItem(self.pixmap_item)
            # 标记点的数量
            self.mark_count = 0
            # 遮罩的数量
            self.mask_count = 0
        init_scene()
        self.mouse_pos.connect(self.update_mouse_position)


    def add_mark(self, pixmap, pos=(0, 0), scale=1.0):
        """
        添加标记到场景
        :param pixmap: 图像数据
        :param pos:  坐标
        :param scale: 缩放比例
        :return:  None
        """
        # 加载图像
        pixmap_item = QGraphicsPixmapItem(pixmap)  # 创建图形项
        mark_x = pos[0] - pixmap.width() / 2 * scale
        mark_y = pos[1] - pixmap.height() / 2 * scale
        pixmap_item.setPos(mark_x, mark_y)  # 设置位置
        if scale != 1.0:  # 设置比例
            pixmap_item.setScale(scale)
        self.scene.addItem(pixmap_item)
        self.mark_count += 1

    def add_mask(self):
        """
        添加遮罩
        :return:
        """
        pass

    def remove_mark(self):
        """
        删除一个标记
        :return: None
        """
        if self.mark_count > 0:
            self.scene.removeItem(self.scene.items()[0])
            self.mark_count -= 1

    def remove_mask(self):
        """
        删除一个遮罩
        :return: None
        """
        if self.mask_count > 0:
            self.scene.removeItem(self.scene.items()[0])
            self.mask_count -= 1

    def remove_all_marks(self):
        """
        删除所有标记
        :return:
        """
        while self.mark_count > 0:
            self.scene.removeItem(self.scene.items()[0])
            self.mark_count -= 1


    # # 使用变换矩阵(如果需要)
    def transform(self, t):
        # self.transform = t
        # self.pixmap_item.setTransform(self.transform)
        pass

    # 处理滚轮事件以实现缩放
    def wheelEvent(self, event):
        factor = 1.001 ** event.angleDelta().y()  # 滚轮每滚动一格,缩放比例变化
        self.scale(factor, factor)

    # 处理鼠标单击事件以显示原图像素坐标
    def mousePressEvent(self, event):
        super(ImageViewer, self).mousePressEvent(event)
        scene_pos = self.mapToScene(event.position().toPoint())  # 记录当前鼠标在场景中的位置
        pixmap_rect = self.pixmap_item.boundingRect()   # 记录图像的边界矩形
        # 如果鼠标在图像边界矩形内,则显示像素坐标
        if pixmap_rect.contains(scene_pos):
            pos = self.pixmap_item.mapFromScene(scene_pos)  # 计算图像坐标
            x, y = pos.x(), pos.y()
            print(f"鼠标在图像内,坐标为: ({x}, {y})")

            if event.button() == Qt.LeftButton:  # 左键
                if event.modifiers() == Qt.ShiftModifier:  # Shift 键
                    self.remove_mark()    # 删除标记
                else:
                    self.add_mark(pixmap_mark, (x, y), 0.6)  # 添加标记

            elif event.button() == Qt.RightButton:   # 右键
                if event.modifiers() == Qt.ShiftModifier:   # Shift 键
                    self.remove_all_marks()   # 删除所有标记
                else:
                    self.add_mark(pixmap_unmark, (x, y), 0.6)   # 添加标记

    @Slot()
    def update_mouse_position(self, x, y):
        pass
        # 更新状态栏显示
        # self.status_bar.showMessage(f"原图像素坐标: ({x}, {y})")

class MainWindow(QMainWindow):
    def __init__(self, image_path):
        super(MainWindow, self).__init__()
        self.setGeometry(100, 100, 800, 600)

        # 创建状态栏
        self.status_bar = self.statusBar()

        # 创建 ImageViewer 实例
        self.image_viewer = ImageViewer(image_path, self)
        self.setCentralWidget(self.image_viewer)


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

    # 生成标记图像
    pixmap_mark = QPixmap("mark.png")   # 标记图像(前景)
    pixmap_unmark = QPixmap("un_mark.png")   # 未标记图像(背景)

    image_path = "IMG_PP.jpg"      # 基础图像路径

    # 创建主窗口
    window = MainWindow(image_path)
    window.show()

    sys.exit(app.exec())

截图:

相关推荐
鸡鸭扣19 分钟前
Docker:3、在VSCode上安装并运行python程序或JavaScript程序
运维·vscode·python·docker·容器·js
paterWang1 小时前
基于 Python 和 OpenCV 的酒店客房入侵检测系统设计与实现
开发语言·python·opencv
东方佑1 小时前
使用Python和OpenCV实现图像像素压缩与解压
开发语言·python·opencv
我真不会起名字啊2 小时前
“深入浅出”系列之杂谈篇:(3)Qt5和Qt6该学哪个?
开发语言·qt
神秘_博士2 小时前
自制AirTag,支持安卓/鸿蒙/PC/Home Assistant,无需拥有iPhone
arm开发·python·物联网·flutter·docker·gitee
laimaxgg2 小时前
Qt常用控件之单选按钮QRadioButton
开发语言·c++·qt·ui·qt5
水瓶丫头站住2 小时前
Qt的QStackedWidget样式设置
开发语言·qt
Moutai码农3 小时前
机器学习-生命周期
人工智能·python·机器学习·数据挖掘
小钊(求职中)3 小时前
Java开发实习面试笔试题(含答案)
java·开发语言·spring boot·spring·面试·tomcat·maven
小白教程4 小时前
python学习笔记,python处理 Excel、Word、PPT 以及邮件自动化办公
python·python学习·python安装