You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQtGraph中PolyLineROI绘制大量点时卡顿的解决方法咨询

解决PyQtGraph使用PolyLineROI绘制大量顶点多边形时的卡顿问题

问题描述

使用PyQtGraph的PolyLineROI实现鼠标逐点绘制多边形,但当多边形顶点数量较多时,程序出现严重卡顿,寻求优化方案。

问题代码

import cv2
import numpy as np
import pyqtgraph as pg
from PyQt5.QtCore import QPoint, QPointF, Qt
from PyQt5.QtWidgets import QApplication, QAction
import tifffile

from datashader import Point
from pyqtgraph import functions as fn

class ImageViewBox(pg.ViewBox):
    def __init__(self, file_path, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.file_path=file_path
        self.draw=False
        self.polygons = []  
        self.setMenuEnabled(True) 
        if self.file_path.endswith(".tif"):
            self.image_data = tifffile.imread(self.file_path)
        else:
            self.image_data = cv2.imread(self.file_path, cv2.IMREAD_UNCHANGED)
        self.image_data = self.image_data.astype(np.uint16) 

        pyramid_levels = 3  
        for _ in range(pyramid_levels):
            self.image_data = cv2.pyrDown(self.image_data)

        self.imageitem = pg.ImageItem(self.image_data)
        self.addItem(self.imageitem)

    def mouse_click(self, ev):
        if ev.button()==Qt.LeftButton:
            pos_scene = ev.scenePos()
            start_pos = self.mapSceneToView(pos_scene)
            if not self.imageitem.sceneBoundingRect().contains(pos_scene):
                return

            if not self.draw:
                polygon = pg.PolyLineROI(positions=[],closed=True, pen='g', movable=False)
                self.polygons.append(polygon)
                self.addItem(self.polygons[-1])
                self.draw = True

            if len(self.polygons[-1].getSceneHandlePositions()) > 2:
                last_poygon = self.polygons[-1].saveState()['points'][-1]
                last_point = np.array([last_poygon[0], last_poygon[1]])
                pos_array = np.array([ev.pos().x(), ev.pos().y()])
                distance = np.linalg.norm(pos_array - last_point)
                if distance < 50:
                    self.draw = False
                    return

            self.add_point(start_pos)

        if ev.button() == Qt.MiddleButton:
            if self.draw == True:
                self.draw = False
            elif self.draw == False and len(self.polygons[-1].saveState()['points'] )> 0:
                print(len(self.polygons[-1].saveState()['points'] ))
                self.draw =True

    def add_point(self,point):
        current_polygon_points = self.polygons[-1].saveState()['points']
        current_polygon_points.append(point)
        self.polygons[-1].setPoints(current_polygon_points)

核心卡顿原因

  1. 频繁调用saveState()和setPoints():每次添加点都触发ROI全量状态保存与更新,顶点越多开销越大
  2. 顶点手柄渲染:PolyLineROI默认显示每个顶点的可拖动手柄,大量顶点会导致渲染压力剧增
  3. 坐标计算冗余:使用数组转换计算距离,且存在坐标系统不匹配问题

优化方案

1. 直接操作ROI内部点集,避免冗余状态操作

修改add_point方法,跳过saveState()和setPoints()的全量更新,直接操作ROI内部点列表并触发局部更新:

def add_point(self, point):
    roi = self.polygons[-1]
    roi.points.append(point)
    # 仅更新线条和触发必要信号,而非全量重设点集
    roi.updateLines()
    roi.sigRegionChanged.emit(roi)

2. 禁用ROI顶点手柄

创建PolyLineROI时关闭手柄显示,减少渲染负载:

polygon = pg.PolyLineROI(positions=[], closed=True, pen='g', movable=False, handles=False)

3. 优化鼠标事件中的距离计算

使用QPointF原生方法计算距离,避免数组转换,同时保证坐标系统一致:

roi = self.polygons[-1]
if len(roi.points) > 2:
    last_point = roi.points[-1]
    # 使用QPointF内置方法计算距离,高效且坐标匹配
    distance = start_pos.distanceToPoint(last_point)
    if distance < 50:
        self.draw = False
        return

4. 绘制完成后转换为轻量PolygonItem

如果绘制完成后不需要编辑顶点,将PolyLineROI转换为pg.PolygonItem,后者渲染开销更低:

def finish_polygon(self):
    roi = self.polygons[-1]
    points = roi.points
    polygon_item = pg.PolygonItem(points=points, pen='g')
    self.addItem(polygon_item)
    self.removeItem(roi)
    self.polygons[-1] = polygon_item
    self.draw = False

优化后的完整代码

import cv2
import numpy as np
import pyqtgraph as pg
from PyQt5.QtCore import QPoint, QPointF, Qt
from PyQt5.QtWidgets import QApplication, QAction
import tifffile

class ImageViewBox(pg.ViewBox):
    def __init__(self, file_path, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.file_path=file_path
        self.draw=False
        self.polygons = []  
        self.setMenuEnabled(True) 
        if self.file_path.endswith(".tif"):
            self.image_data = tifffile.imread(self.file_path)
        else:
            self.image_data = cv2.imread(self.file_path, cv2.IMREAD_UNCHANGED)
        self.image_data = self.image_data.astype(np.uint16) 

        pyramid_levels = 3  
        for _ in range(pyramid_levels):
            self.image_data = cv2.pyrDown(self.image_data)

        self.imageitem = pg.ImageItem(self.image_data)
        self.addItem(self.imageitem)

    def mouse_click(self, ev):
        if ev.button()==Qt.LeftButton:
            pos_scene = ev.scenePos()
            start_pos = self.mapSceneToView(pos_scene)
            if not self.imageitem.sceneBoundingRect().contains(pos_scene):
                return

            if not self.draw:
                # 创建ROI时禁用手柄,减少渲染开销
                polygon = pg.PolyLineROI(positions=[], closed=True, pen='g', movable=False, handles=False)
                self.polygons.append(polygon)
                self.addItem(self.polygons[-1])
                self.draw = True

            roi = self.polygons[-1]
            if len(roi.points) > 2:
                last_point = roi.points[-1]
                # 使用QPointF原生方法计算距离,更高效
                distance = start_pos.distanceToPoint(last_point)
                if distance < 50:
                    # 绘制完成后转换为轻量PolygonItem
                    self.finish_polygon()
                    return

            self.add_point(start_pos)

        if ev.button() == Qt.MiddleButton:
            if self.draw == True:
                self.draw = False
            elif self.draw == False and len(self.polygons) > 0 and len(self.polygons[-1].points) > 0:
                print(len(self.polygons[-1].points))
                self.draw = True

    def add_point(self, point):
        roi = self.polygons[-1]
        # 直接操作内部点列表,避免saveState/setPoints的冗余开销
        roi.points.append(point)
        # 仅更新线条和触发必要信号
        roi.updateLines()
        roi.sigRegionChanged.emit(roi)

    def finish_polygon(self):
        # 将ROI转换为轻量的PolygonItem,适合静态显示
        roi = self.polygons[-1]
        points = roi.points
        polygon_item = pg.PolygonItem(points=points, pen='g')
        self.addItem(polygon_item)
        self.removeItem(roi)
        self.polygons[-1] = polygon_item
        self.draw = False

内容的提问来源于stack exchange,提问作者oi哆啦A孟

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 13:55:29