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)
核心卡顿原因
- 频繁调用
saveState()和setPoints():每次添加点都触发ROI全量状态保存与更新,顶点越多开销越大 - 顶点手柄渲染:
PolyLineROI默认显示每个顶点的可拖动手柄,大量顶点会导致渲染压力剧增 - 坐标计算冗余:使用数组转换计算距离,且存在坐标系统不匹配问题
优化方案
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孟
相关产品推荐
相关产品推荐

