圆孔毛刺检测系统:PyQt6界面开发实战

算法跑通了,得有个界面让用户用。

工具栏、表格、图像预览、状态栏、快捷键——一个不少。

一、主窗口架构

1.1 基础结构

python

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('圆孔毛刺检测与外部定位系统')
        self.setGeometry(100, 100, 1200, 800)
        
        # 核心组件
        self.detector = Detector()
        self.external_controller = ExternalController()
        self.image_paths = []
        self.results = {}
        self.current_image_index = -1
        
        self.init_ui()

1.2 组件初始化

组件作用初始化时机
Detector检测算法实例构造函数
ExternalController外部控制实例构造函数
image_paths图像路径列表加载目录后
results检测结果字典检测后填充

二、工具栏布局

2.1 完整工具栏

python

def init_toolbar(self, parent_layout):
    toolbar = QHBoxLayout()
    toolbar.setContentsMargins(0, 0, 0, 0)
    
    # 选择目录
    self.btn_select_dir = QPushButton('选择目录')
    self.btn_select_dir.clicked.connect(self.select_directory)
    toolbar.addWidget(self.btn_select_dir)
    
    # 开始检测
    self.btn_batch_detect = QPushButton('开始检测')
    self.btn_batch_detect.clicked.connect(self.start_batch_detection)
    toolbar.addWidget(self.btn_batch_detect)
    
    # 取消检测
    self.btn_cancel = QPushButton('取消检测')
    self.btn_cancel.clicked.connect(self.cancel_detection)
    self.btn_cancel.setEnabled(False)
    toolbar.addWidget(self.btn_cancel)
    
    # 重新检测
    self.btn_redetect = QPushButton('重新检测')
    self.btn_redetect.clicked.connect(self.start_redetection)
    toolbar.addWidget(self.btn_redetect)
    
    # 分隔符
    toolbar.addWidget(QLabel('|'))
    
    # 外部配置
    self.btn_config_controls = QPushButton('外部配置')
    self.btn_config_controls.clicked.connect(self.open_control_config)
    toolbar.addWidget(self.btn_config_controls)
    
    # X坐标输入
    toolbar.addWidget(QLabel('X:'))
    self.edit_x = QLineEdit()
    self.edit_x.setMaximumWidth(80)
    toolbar.addWidget(self.edit_x)
    
    # Y坐标输入
    toolbar.addWidget(QLabel('Y:'))
    self.edit_y = QLineEdit()
    self.edit_y.setMaximumWidth(80)
    toolbar.addWidget(self.edit_y)
    
    # 发送定位
    self.btn_send_xyz = QPushButton('发送定位')
    self.btn_send_xyz.clicked.connect(self.send_xy_and_locate)
    toolbar.addWidget(self.btn_send_xyz)
    
    parent_layout.addLayout(toolbar)

2.2 按钮状态管理

python

def start_batch_detection(self):
    # 检测开始:禁用操作按钮
    self.btn_select_dir.setEnabled(False)
    self.btn_batch_detect.setEnabled(False)
    self.btn_redetect.setEnabled(False)
    self.btn_cancel.setEnabled(True)

def on_batch_finished(self):
    # 检测完成:恢复按钮
    self.btn_select_dir.setEnabled(True)
    self.btn_batch_detect.setEnabled(True)
    self.btn_redetect.setEnabled(True)
    self.btn_cancel.setEnabled(False)

三、表格控件(QTableWidget)

3.1 初始化

python

def init_table(self, parent_layout):
    self.table = QTableWidget()
    self.table.setColumnCount(4)
    self.table.setHorizontalHeaderLabels(['文件名', '检测状态', '评级', '合格/不合格'])
    self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
    self.table.cellDoubleClicked.connect(self.on_table_double_click)
    self.table.currentCellChanged.connect(self.on_table_selection_changed)
    self.table.setMinimumWidth(400)
    self.table.setMaximumWidth(500)
    
    parent_layout.addWidget(self.table)

3.2 填充数据

python

def load_images(self, dir_path):
    self.image_paths = []
    valid_extensions = ('.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.tif')
    
    for filename in os.listdir(dir_path):
        if filename.lower().endswith(valid_extensions):
            self.image_paths.append(os.path.join(dir_path, filename))
    
    # 按数字排序(文件名中有数字时)
    import re
    self.image_paths.sort(key=lambda x: int(''.join(re.findall(r'\d+', os.path.basename(x)))))
    
    # 填充表格
    self.table.setRowCount(len(self.image_paths))
    for i, path in enumerate(self.image_paths):
        self.table.setItem(i, 0, QTableWidgetItem(os.path.basename(path)))
        self.table.setItem(i, 1, QTableWidgetItem('未检测'))
        self.table.setItem(i, 2, QTableWidgetItem('-'))
        self.table.setItem(i, 3, QTableWidgetItem('-'))

四、图像预览区

4.1 双视图布局(原图 + 标注图)

python

def init_image_viewer(self, parent_layout):
    viewer_group = QVBoxLayout()
    
    # 原图预览
    self.scroll_area_original = QScrollArea()
    self.scroll_area_original.setWidgetResizable(False)
    self.original_image_label = QLabel()
    self.original_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
    self.scroll_area_original.setWidget(self.original_image_label)
    viewer_group.addWidget(self.scroll_area_original, 1)
    
    # 标注图预览
    self.scroll_area_annotated = QScrollArea()
    self.scroll_area_annotated.setWidgetResizable(False)
    self.annotated_image_label = QLabel()
    self.annotated_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
    self.scroll_area_annotated.setWidget(self.annotated_image_label)
    viewer_group.addWidget(self.scroll_area_annotated, 1)
    
    parent_layout.addLayout(viewer_group, 3)

4.2 显示图像

python

def display_image(self, row):
    if row < 0 or row >= len(self.image_paths):
        self.clear_image()
        return
    
    image_path = self.image_paths[row]
    original_img = self._read_image_with_unicode_path(image_path)
    
    # 如果有检测结果,获取标注图
    if row in self.results and self.results[row]['success']:
        result = self.results[row]
        annotated_img = result.get('annotated_img')
    else:
        annotated_img = None
    
    # 显示原图
    self._display_single_image(original_img, self.original_image_label, self.scroll_area_original)
    # 显示标注图(无结果则显示原图)
    self._display_single_image(
        annotated_img if annotated_img is not None else original_img,
        self.annotated_image_label,
        self.scroll_area_annotated
    )

五、状态栏与进度条

5.1 初始化

python

def init_status_bar(self):
    status_bar = self.statusBar()
    
    # 统计信息
    self.label_stats = QLabel('总文件: 0 | 合格: 0 | 不合格: 0 | 总耗时: 0.00秒')
    status_bar.addWidget(self.label_stats)
    
    # 进度条
    self.progress_bar = QProgressBar()
    self.progress_bar.setMaximumWidth(300)
    self.progress_bar.hide()
    status_bar.addWidget(self.progress_bar)

5.2 统计更新

python

def update_stats(self):
    total = len(self.image_paths)
    pass_count = 0
    fail_count = 0
    
    for row in range(total):
        result = self.table.item(row, 3)
        if result and result.text() == '合格':
            pass_count += 1
        elif result and result.text() == '不合格':
            fail_count += 1
    
    self.label_stats.setText(
        f'总文件: {total} | 合格: {pass_count} | 不合格: {fail_count} | 总耗时: {self.total_detection_time:.2f}秒'
    )

六、快捷键支持

python

def __init__(self):
    # F10 → 发送定位
    self.f10_shortcut = QShortcut(QKeySequence(Qt.Key.Key_F10), self)
    self.f10_shortcut.activated.connect(self.send_xy_and_locate)

七、布局总览

text

┌─────────────────────────────────────────────────────────────┐
│  工具栏:选择目录 | 开始检测 | 取消 | 重新检测 | 外部配置       │
│         X:[____] Y:[____] [发送定位]                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌───────────────┐  ┌───────────────────────────────────┐   │
│  │  表格          │  │  原图预览                         │   │
│  │  文件名│状态   │  │  (QLabel in QScrollArea)          │   │
│  │  001.jpg│已检测│  │                                   │  │
│  │  002.jpg│未检测│  ├───────────────────────────────────┤  │
│  │  003.jpg│已检测│  │  标注图预览                        │  │
│  │  ...          │  │  (QLabel in QScrollArea)           │  │
│  └───────────────┘  └───────────────────────────────────┘   │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│  状态栏:总文件: 10 | 合格: 8 | 不合格: 2 | 耗时: 3.2秒        │
└─────────────────────────────────────────────────────────────┘

八、踩坑记录

  1. 表格列宽自适应:设置setMinimumWidth(400)setMaximumWidth(500)固定宽度,避免表格被挤压

  2. 滚动区域setWidgetResizable(False)防止图像变形

  3. 双击事件cellDoubleClickedcurrentCellChanged分别处理双击和单击选择

  4. 按钮状态:批量检测期间禁用所有操作按钮,防止重复点击

  5. QScrollArea内显示大图:缩放时保持比例,不要拉伸

下篇预告

下一篇写圆孔毛剌检测:数据存储与结果管理。

如果对界面设计有不同思路,评论区聊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在世修行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值