欢迎光临
我们一直在努力

BSE电镜多相含量分析软件:QGraphicsView图像显示组件

上一期把GUI框架搭好了,剩下的核心就是显示区——QGraphicsView。

图像加载、缩放适配、AOI绘制、坐标转换——全都靠它。

一、为什么选QGraphicsView

方案优点缺点适用场景
QLabel 简单 无交互、无缩放 静态图标
QWidget+绘画 灵活 需自己实现缩放/平移 简单自定义
QGraphicsView 内置缩放/平移、场景图、事件完备 学习曲线略陡 图像分析工具首选

二、ImageCanvas类结构

python

class ImageCanvas(QGraphicsView):
aoi_updated = Signal(dict)

def __init__(self, parent=None):
super().__init__(parent)
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.setAlignment(Qt.AlignCenter)
self.setDragMode(QGraphicsView.NoDrag)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setBackgroundBrush(QColor(30, 30, 30))

# 图像数据
self.img_path = None
self.original_img = None
self.displayed_img = None
self.rgb_img = None

# AOI状态
self.aoi_mode = 'rectangle'
self.aoi_shape = None
self.aoi_drawing = False
self.aoi_points = []

# AOI图形项
self.aoi_rect_item = None
self.aoi_ellipse_item = None
self.aoi_polygon_item = None

# 标定状态
self.calibration_mode = False
self.calibration_points = []

核心属性:

属性类型说明
original_img ndarray 原始灰度图,用于计算
rgb_img ndarray RGB格式原图,用于显示叠加
displayed_img ndarray 当前显示的图像数组
aoi_mode str AOI模式:rectangle/circle/polygon
aoi_shape dict 当前AOI形状数据
calibration_mode bool 是否处于标定模式

三、图像加载与格式转换

安全读取(支持中文路径)

python

def safe_imread(img_path, flags=cv2.IMREAD_UNCHANGED):
try:
img = cv2.imread(img_path, flags)
if img is not None:
return img
except:
pass

try:
with open(img_path, 'rb') as f:
data = f.read()
img_array = np.frombuffer(data, np.uint8)
img = cv2.imdecode(img_array, flags)
return img
except Exception as e:
print(f"Error reading image: {e}")
return None

格式统一(16-bit → 8-bit → RGB)

python

def load_image(self, img_path):
self.img_path = img_path
img = self.safe_imread(img_path)
if img is None:
return False

self.original_img = img

# 16-bit转8-bit
if img.dtype == np.uint16:
img = (img / 256).astype(np.uint8)

# 转为RGB
if len(img.shape) == 3:
if img.shape[-1] == 4:
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)
else:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
else:
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)

self.rgb_img = img
self.displayed_img = img.copy()
self.update_display()
return True

NumPy → QImage(关键!)

python

def update_display(self):
if self.displayed_img is None:
return

h, w = self.displayed_img.shape[:2]
img_copy = np.ascontiguousarray(self.displayed_img)
qimg = QImage(
img_copy.data, w, h, 3 * w, QImage.Format_RGB888
).copy()

self.scene.clear()
pixmap = QPixmap.fromImage(qimg)
self.scene.addPixmap(pixmap)
self.scene.setSceneRect(0, 0, w, h)
self.fitInView(0, 0, w, h, Qt.KeepAspectRatio)

三个关键点:

操作原因
np.ascontiguousarray() 确保内存连续,QImage要求
QImage(…).copy() 深拷贝,防止numpy数组被回收后显示异常
QImage.Format_RGB888 RGB三通道格式

四、窗口自适应缩放

python

def resizeEvent(self, event):
super().resizeEvent(event)
if self.displayed_img is not None:
h, w = self.displayed_img.shape[:2]
self.fitInView(0, 0, w, h, Qt.KeepAspectRatio)

fitInView参数:

参数作用
Qt.KeepAspectRatio 保持宽高比,等比例缩放
Qt.IgnoreAspectRatio 忽略比例,拉伸填充
Qt.KeepAspectRatioByExpanding 保持比例,完全覆盖(可能裁剪)

本项目用 KeepAspectRatio。

Tab切换时重新适配

python

def on_notebook_tab_changed(self, index):
if index == 0:
canvas = self.original_canvas
elif index == 1:
canvas = self.result_canvas
else:
phase_name = f'phase{index – 1}'
canvas = self.phase_canvases.get(phase_name)

if canvas and canvas.displayed_img is not None:
h, w = canvas.displayed_img.shape[:2]
canvas.fitInView(0, 0, w, h, Qt.KeepAspectRatio)

五、AOI绘制交互

三种AOI模式
模式绘制方式数据结构
rectangle 拖拽 {type:'rectangle', x, y, w, h}
circle 拖拽(圆心+半径) {type:'circle', cx, cy, r}
polygon 点击顶点,双击闭合 {type:'polygon', points:[(x,y),…]}
核心鼠标事件

python

def mousePressEvent(self, event):
if self.calibration_mode:
self._calibration_mouse_press(event)
return

pos = self.mapToScene(event.pos()) # 视图坐标 → 场景坐标
x, y = pos.x(), pos.y()

if self.aoi_mode == 'rectangle':
if event.button() == Qt.LeftButton:
self.aoi_drawing = True
self.aoi_points = [(x, y)]
self._clear_aoi_items()
pen = QPen(Qt.red, 2)
self.aoi_rect_item = self.scene.addRect(x, y, 0, 0, pen)

def mouseMoveEvent(self, event):
if not self.aoi_drawing:
return

pos = self.mapToScene(event.pos())
x, y = pos.x(), pos.y()

if self.aoi_mode == 'rectangle' and self.aoi_rect_item:
x0, y0 = self.aoi_points[0]
self.aoi_rect_item.setRect(
min(x0, x), min(y0, y),
abs(x – x0), abs(y – y0)
)

def mouseReleaseEvent(self, event):
if self.aoi_drawing:
self.aoi_drawing = False
if self.aoi_mode == 'rectangle':
rect = self.aoi_rect_item.rect()
self.aoi_shape = {
'type': 'rectangle',
'x': rect.x(), 'y': rect.y(),
'w': rect.width(), 'h': rect.height()
}
self.aoi_updated.emit(self.aoi_shape)

def mouseDoubleClickEvent(self, event):
if self.aoi_mode == 'polygon' and len(self.aoi_points) >= 3:
self.aoi_shape = {
'type': 'polygon',
'points': self.aoi_points.copy()
}
self.aoi_updated.emit(self.aoi_shape)

关键:mapToScene(event.pos()) 是坐标转换的核心。

六、AOI掩码生成

python

def get_aoi_mask(self, img_shape):
if not self.aoi_shape:
return None

h, w = img_shape[:2]
mask = np.zeros((h, w), dtype=np.uint8)

if self.aoi_shape['type'] == 'rectangle':
x = int(self.aoi_shape['x'])
y = int(self.aoi_shape['y'])
w = int(self.aoi_shape['w'])
h = int(self.aoi_shape['h'])
mask[y:y+h, x:x+w] = 255

elif self.aoi_shape['type'] == 'circle':
cx = int(self.aoi_shape['cx'])
cy = int(self.aoi_shape['cy'])
r = int(self.aoi_shape['r'])
y, x = np.ogrid[:h, :w]
mask[(x – cx) ** 2 + (y – cy) ** 2 <= r ** 2] = 255

elif self.aoi_shape['type'] == 'polygon':
pts = np.array([
[int(p[0]), int(p[1])] for p in self.aoi_shape['points']
], np.int32)
cv2.fillPoly(mask, [pts], 255)

return mask

七、结果叠加显示

python

PHASE_COLORS = {
'phase1': (255, 0, 0), # 红
'phase2': (0, 0, 255), # 蓝
'phase3': (0, 255, 0), # 绿
'phase4': (255, 255, 0), # 黄
'phase5': (255, 0, 255), # 品红
}

def display_overlay(self, masks):
if self.rgb_img is None:
return

result = self.rgb_img.copy()

for phase_name, mask in masks.items():
color = PHASE_COLORS.get(phase_name, (255, 255, 255))
result[mask > 0] = color

self.displayed_img = result
self.update_display()

八、踩坑记录

  • QImage显示为黑色:np.ascontiguousarray() + QImage(…).copy() 深拷贝

  • AOI绘制位置偏移:用 mapToScene() 转换坐标

  • Tab切换后图像未缩放:在Tab切换信号中重新适配

  • 下篇预告

    下一篇写图像预处理模块:16-bit转8-bit、高斯滤波、对比度增强。

    赞(0)
    未经允许不得转载:171主机测评 » BSE电镜多相含量分析软件:QGraphicsView图像显示组件
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址