算法跑通了,得有个界面让用户用。
工具栏、表格、图像预览、状态栏、快捷键——一个不少。
一、主窗口架构
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秒 │ └─────────────────────────────────────────────────────────────┘
八、踩坑记录
-
表格列宽自适应:设置
setMinimumWidth(400)和setMaximumWidth(500)固定宽度,避免表格被挤压 -
滚动区域:
setWidgetResizable(False)防止图像变形 -
双击事件:
cellDoubleClicked和currentCellChanged分别处理双击和单击选择 -
按钮状态:批量检测期间禁用所有操作按钮,防止重复点击
-
QScrollArea内显示大图:缩放时保持比例,不要拉伸
下篇预告
下一篇写圆孔毛剌检测:数据存储与结果管理。
如果对界面设计有不同思路,评论区聊。
233

被折叠的 条评论
为什么被折叠?



