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())

截图:

相关推荐
_一条咸鱼_几秒前
Python 语法入门之流程控制 if 判断(七)
人工智能·python·面试
_一条咸鱼_1 分钟前
Python 流程控制之 while 循环(八)
人工智能·python·面试
HtwHUAT25 分钟前
实验四 Java图形界面与事件处理
开发语言·前端·python
鄃鳕26 分钟前
QSS【QT】
开发语言·qt
Tech Synapse27 分钟前
基于Surprise和Flask构建个性化电影推荐系统:从算法到全栈实现
python·算法·flask·协同过滤算法
麦麦大数据29 分钟前
vue+flask+CNN电影推荐系统
pytorch·python·cnn·flask·scikit-learn·电影推荐
汤姆_51129 分钟前
【c语言】深度理解指针4——sizeof和strlen
c语言·开发语言
腾飞开源33 分钟前
02_Flask是什么?
python·flask·python web开发·flask快速入门教程·人人都能学·小白看得懂学得会·跟我学编程
終不似少年遊*34 分钟前
国产之光DeepSeek架构理解与应用分析04
人工智能·python·深度学习·算法·大模型·ds
碎梦归途38 分钟前
23种设计模式-结构型模式之外观模式(Java版本)
java·开发语言·jvm·设计模式·intellij-idea·外观模式