这次我们来看一个医学影像深度学习实战项目:基于 YOLOv8 / YOLOv5 + PySide6 的骨科骨折诊断检测系统。它解决的问题很明确,就是把目标检测模型和桌面 GUI 串起来,让医生或研究人员能通过鼠标点击完成骨折区域的自动定位、置信度筛选和批量影像检测,而不是每次都在命令行里跑推理。
从技术栈来看,这套系统用到的是深度学习里成熟度很高的 YOLO 系列模型,以及 Python 生态里最常用的桌面框架 PySide6。YOLOv8 和 YOLOv5 都支持自定义数据集训练,也支持导出 ONNX、TensorRT 等部署格式;PySide6 则负责把模型推理封装成可视化的桌面窗口。这两者结合,非常适合做医疗辅助检测、工业缺陷检测、安防目标识别等本地化应用。
这篇文章会重点讲清楚四件事:这套系统的整体设计思路、本地部署环境怎么搭、PySide6 界面如何集成 YOLO 推理、以及批量检测和接口扩展怎么做。如果你正在准备深度学习相关的课设、毕设,或者想了解 YOLOv8 如何落地到桌面应用,这篇可以直接收藏。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | 深度学习目标检测 + PySide6 桌面应用 |
| 检测模型 | YOLOv8 / YOLOv5,可切换权重 |
| 检测目标 | 骨科 X 光影像中的骨折区域定位 |
| 开发语言 | Python |
| 界面框架 | PySide6,基于 Qt6 |
| 推理框架 | Ultralytics YOLO(内置 ONNX 导出支持) |
| 支持平台 | Windows / Linux,macOS 需按 PySide6 与 CUDA 环境评估 |
| 是否支持 CPU | 支持,但速度明显低于 GPU |
| 启动方式 | 命令启动 PySide6 主窗口,或封装一键启动脚本 |
| 是否支持 API | 可封装 FastAPI / Flask 服务,模型推理层独立 |
| 是否支持批量任务 | 支持,按目录批量读取影像并输出标注结果 |
| 推荐硬件 | 建议 NVIDIA 显卡,显存 4GB 以上;无 GPU 可用 CPU 小图测试 |
| 适合场景 | 医疗影像辅助检测、目标检测入门课设、PySide6 桌面应用开发 |
需要说明的是,显存占用和推理速度取决于模型大小、输入分辨率、批量大小以及是否开启 TensorRT、ONNX 等加速。YOLOv8n / YOLOv8s 这类轻量权重在普通消费级显卡上压力不大,实际占用需以本机测试为准。
2. 适用场景与使用边界
2.1 适合谁
这个系统适合三类人。
第一类是正在做深度学习课程设计或毕业设计的学生。骨折检测是一个很典型的目标检测落地场景,数据可以来自公开数据集或医院授权的脱敏影像,模型结构用 YOLOv8,界面用 PySide6,整个项目技术栈清晰,答辩时能讲清楚数据标注、模型训练、推理部署和界面交互。
第二类是希望把 YOLO 模型封装成桌面工具的开发者。很多人训练完模型后只会用命令行预测,输出结果也不直观。通过 PySide6 封装后,模型推理变成打开窗口、选择图片、点击检测、查看结果,这对非技术用户非常友好。
第三类是医疗信息化相关方向的研究人员。系统可以作为医学影像辅助检测的原型验证工具,用于骨折筛查、教学演示或影像科二次复查的参考。
2.2 能解决什么问题
核心解决的是“从模型权重到可用工具”的最后一公里问题。模型训练完只是得到一个权重文件,普通用户无法直接使用。PySide6 提供一个图形界面,把权重加载、图片选择、目标检测、结果展示、置信度筛选、批量处理都集中到一个窗口里。
对检测任务本身来说,YOLOv8 在骨折这类目标尺寸明显、背景相对简单的影像上通常能获得不错的效果。只要标注数据质量到位,模型可以快速定位骨折区域并给出类别和置信度。
2.3 不适合什么场景
需要明确一点:这个系统适合科研、教学和辅助检测,但不适合直接作为临床诊断工具。骨折诊断涉及复杂的影像学评估、病史信息、多角度影像对比,任何自动化系统都只能提供辅助参考,最终诊断必须由专业医生完成。
另外,如果你的目标是在低算力嵌入式设备上实时检测,建议优先考虑 YOLOv8n 导出 ONNX 或 TensorRT 的方案,而不是直接跑 PySide6 桌面应用。
2.4 数据与合规边界
医疗影像数据属于敏感数据。使用公开数据集时,要确认数据集的授权协议;使用医院或机构提供的影像数据时,必须完成脱敏处理,并获得数据使用授权。系统在开发测试阶段应使用脱敏后的样本,不要在未授权环境下处理真实患者数据。
3. 本地部署环境准备
3.1 推荐环境
| 项目 | 推荐配置 |
|---|---|
| 操作系统 | Windows 10 / 11 较省心 |
| Python | 3.9 - 3.11,不要直接用最新版,部分依赖可能滞后 |
| 显卡 | NVIDIA 显卡,显存 4GB 以上 |
| CUDA | CUDA 11.8 或按 PyTorch 官方要求安装 |
| 界面库 | PySide6 |
| 检测框架 | ultralytics |
| 其他依赖 | opencv-python、numpy、pillow |
如果电脑没有 NVIDIA 显卡,也可以跑 CPU 推理。用小分辨率图片、轻量级 YOLOv8n 权重,CPU 推理一张图需要数秒到十几秒,适合功能验证,不适合大批量处理。
3.2 Python 虚拟环境
建议先创建独立的 Python 虚拟环境,避免把全局环境搞乱。
# 创建虚拟环境 python -m venv yolo_pyside_env # Windows 激活 yolo_pyside_env\Scripts\activate # Linux / macOS 激活 source yolo_pyside_env/bin/activate # 升级 pip python -m pip install -U pip3.3 安装 PySide6 与 ultralytics
pip install PySide6 pip install ultralytics pip install opencv-python numpy pillow安装完成后可以验证版本:
python -c "import PySide6; print(PySide6.__version__)" python -c "from ultralytics import YOLO; print('ultralytics OK')"如果import PySide6报错,大概率是 Python 版本兼容问题,换 Python 3.10 或 3.11 重试。如果import ultralytics报错,优先检查 PyTorch 装的是什么版本。
3.4 模型权重准备
模型权重的来源有两种:
第一种是下载官方预训练权重,用于功能测试:
yolo predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg第二种是使用自己的骨折数据集训练后的权重,通常命名为best.pt。训练完成后,这个文件会保存在runs/detect/train/weights/目录下。
在开发阶段建议准备两个权重:一个用于验证代码流程,另一个用于实际检测效果测试。不要把预训练权重直接当骨折检测模型用。
4. 训练一个骨折检测模型
虽然文章重点是 PySide6 界面和模型部署,但为了整个项目闭环,这里给出 YOLOv8 训练自定义数据集的流程。
4.1 数据集结构
YOLO 格式的数据集目录通常长这样:
datasets/ ├── bone_fracture.yaml ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/bone_fracture.yaml内容如下:
train: datasets/images/train val: datasets/images/val nc: 1 names: ["fracture"]如果你的数据集包含多种骨折类型,比如“闭合性骨折”“开放性骨折”“粉碎性骨折”,就把nc和names对应改掉。
4.2 训练命令
from ultralytics import YOLO # 加载预训练权重 model = YOLO("yolov8n.pt") # 训练 model.train( data="bone_fracture.yaml", epochs=100, imgsz=640, batch=16, device=0, name="fracture_detection" )训练过程中,ultralytics 会打印每个 epoch 的 loss、精度、召回率等信息,也会自动保存best.pt和last.pt。
4.3 训练轮数与精度
训练轮数不是越大越好。骨折检测场景下,建议先用 50 到 100 轮跑一版,观察验证集精度和 loss 曲线是否收敛。如果数据量很小,只有几百张图片,盲目加大训练轮数很容易过拟合。
如果发现训练集 loss 持续下降但验证集精度不涨,说明模型过拟合,可以增加数据增强、降低模型复杂度或减少训练轮数。ultralytics 自带马赛克增强、随机翻转等策略,对小数据集有一定帮助,但数据量太少时效果有限。
训练完成后,可以用model.val()验证模型效果,也可以导出训练过程中生成的results.csv,自己画 loss 和 mAP 曲线图。
5. PySide6 集成 YOLO 推理
5.1 界面总体设计
PySide6 界面通常包含以下区域:
- 顶部工具栏:加载图片、加载文件夹、保存结果。
- 左侧主显示区:显示原始图片和检测标注结果。
- 右侧参数面板:置信度阈值、IOU 阈值、模型选择、设备选择。
- 底部日志区:显示检测耗时、目标数量、当前状态。
这样一个布局既能满足单张图片检测,也能扩展批量检测。
5.2 主窗口代码框架
下面给出一个最小可运行的主窗口示例。省略部分细节,核心看整体结构。
import sys from pathlib import Path import cv2 from PySide6.QtWidgets import ( QApplication, QMainWindow, QLabel, QPushButton, QFileDialog, QVBoxLayout, QHBoxLayout, QWidget, QLineEdit, QComboBox, QTextEdit ) from PySide6.QtGui import QPixmap, QImage from ultralytics import YOLO class BoneFractureApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("骨科骨折诊断检测系统") self.setMinimumSize(1100, 700) self.model = None self.current_image_path = None self._init_ui() def _init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # 参数区 param_layout = QHBoxLayout() self.model_combo = QComboBox() self.model_combo.addItem("yolov8n.pt") self.model_combo.addItem("best.pt") self.conf_input = QLineEdit() self.conf_input.setPlaceholderText("置信度阈值,默认0.5") self.conf_input.setText("0.5") self.load_btn = QPushButton("加载模型") self.load_btn.clicked.connect(self.load_model) self.open_btn = QPushButton("选择图片") self.open_btn.clicked.connect(self.open_image) self.detect_btn = QPushButton("开始检测") self.detect_btn.clicked.connect(self.detect_image) param_layout.addWidget(QLabel("模型:")) param_layout.addWidget(self.model_combo) param_layout.addWidget(QLabel("置信度:")) param_layout.addWidget(self.conf_input) param_layout.addWidget(self.load_btn) param_layout.addWidget(self.open_btn) param_layout.addWidget(self.detect_btn) # 图片显示区 self.image_label = QLabel("请选择图片") self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.image_label.setMinimumSize(800, 500) # 日志区 self.log_text = QTextEdit() self.log_text.setReadOnly(True) self.log_text.setMaximumHeight(120) layout.addLayout(param_layout) layout.addWidget(self.image_label) layout.addWidget(self.log_text) def log(self, msg: str): self.log_text.append(msg) def load_model(self): model_name = self.model_combo.currentText() try: self.model = YOLO(model_name) self.log(f"模型加载成功: {model_name}") except Exception as e: self.log(f"模型加载失败: {e}") def open_image(self): file_path, _ = QFileDialog.getOpenFileName( self, "选择图片", "", "Image Files (*.jpg *.jpeg *.png *.bmp)" ) if file_path: self.current_image_path = file_path pixmap = QPixmap(file_path) self.image_label.setPixmap( pixmap.scaled( self.image_label.size(), Qt.AspectRatioMode.KeepAspectRatio ) ) self.log(f"已加载图片: {file_path}") def detect_image(self): if self.model is None: self.log("请先加载模型") return if not self.current_image_path: self.log("请先选择图片") return try: conf = float(self.conf_input.text().strip() or "0.5") except ValueError: conf = 0.5 self.conf_input.setText("0.5") self.log("置信度输入不合法,已重置为0.5") results = self.model.predict( source=self.current_image_path, conf=conf, save=False ) # 获取标注后的图像 annotated_frame = results[0].plot() rgb_image = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape bytes_per_line = ch * w qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format.Format_RGB888) pixmap = QPixmap.fromImage(qt_image) self.image_label.setPixmap( pixmap.scaled( self.image_label.size(), Qt.AspectRatioMode.KeepAspectRatio ) ) boxes = results[0].boxes self.log(f"检测完成,目标数量: {len(boxes)}") if __name__ == "__main__": app = QApplication(sys.argv) window = BoneFractureApp() window.show() sys.exit(app.exec())运行方式:
python main.py5.3 置信度输入的处理
PySide6 的QLineEdit本身不限制输入格式,用户可能输入字母、负数或空字符串。代码里一定要做防御性处理。上面的示例用try/except把非法输入重置为默认值,同时写日志提示用户。这是界面开发里很容易被忽略的细节。
另外,QLineEdit获取输入内容用.text(),不是.toPlainText()。很多初学者会把QTextEdit的方法用在QLineEdit上,导致AttributeError。
5.4 界面更新方式
YOLO 推理是同步阻塞的。如果图片较大或使用 CPU 推理,点击“开始检测”后界面会卡住,这是正常现象。如果希望界面不卡顿,可以用QThread把推理逻辑放到子线程,推理完成后通过信号更新界面。对于模型较大的场景,这个问题会很明显,后续可以单独优化。
6. 功能测试与效果验证
6.1 单张图片检测测试
测试目的:验证模型能否在单张骨折 X 光影像上正确输出检测框。
操作步骤:
- 启动主程序。
- 在模型下拉框中选择
best.pt。 - 点击“加载模型”,观察日志区输出。
- 点击“选择图片”,选择一张验证集图片。
- 设置置信度为 0.5。
- 点击“开始检测”。
判断成功的标准:
- 图片显示区域出现带标注框的结果图。
- 日志区显示目标数量。
- 检测框位置和骨折区域基本一致。
- 程序没有崩溃退出。
常见失败原因:
- 模型未加载或加载失败。
- 图片路径包含中文或特殊字符导致 OpenCV 读取失败。
- 输入图片尺寸过大,预处理时间较长。
- 置信度过高导致漏检,置信度过低导致误检。
6.2 批量影像检测测试
骨折检测往往不只处理一张图片。批量检测是这套系统的重要能力。
这里给出一个独立的批量检测脚本,可以在界面上加一个“选择文件夹”按钮触发,也可以直接用命令行运行。
from pathlib import Path from ultralytics import YOLO model = YOLO("best.pt") input_dir = Path("./data/test_images") output_dir = Path("./data/test_outputs") # 支持的图片格式 suffixes = {".jpg", ".jpeg", ".png", ".bmp"} for img_path in input_dir.iterdir(): if img_path.suffix.lower() not in suffixes: continue results = model.predict( source=str(img_path), conf=0.5, save=True, project=str(output_dir), name="batch_result", exist_ok=True ) print(f"已处理: {img_path.name}")批量处理的保存规则是:结果会写在output_dir/batch_result/下,原始文件名保持不变。这样便于后续核对。
6.3 多模型切换测试
在model_combo中加入yolov5s.pt或你训练好的 YOLOv5 权重,可以对比不同模型的检测效果。测试时重点观察:
- 同一张图片的检测框数量是否一致。
- 置信度分布有没有明显差异。
- 小目标骨折区域在 YOLOv5 和 YOLOv8 下的表现。
- 单张图片推理耗时差异。
7. 接口 API 与批量任务扩展
PySide6 是桌面界面层,但模型推理可以独立封装成服务。这样既能在桌面端使用,也能给其他系统调用。
7.1 FastAPI 封装推理接口
from fastapi import FastAPI, UploadFile, File, Form from ultralytics import YOLO import cv2 import numpy as np app = FastAPI() model = YOLO("best.pt") @app.post("/predict") async def predict( file: UploadFile = File(...), conf: float = Form(0.5) ): image_bytes = await file.read() nparr = np.frombuffer(image_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) if img is None: return {"code": 1, "message": "图片解码失败"} results = model.predict(img, conf=conf) boxes = results[0].boxes detections = [] for box in boxes: xyxy = box.xyxy[0].tolist() cls_id = int(box.cls[0]) conf_value = float(box.conf[0]) detections.append({ "bbox": xyxy, "class_id": cls_id, "class_name": model.names[cls_id], "confidence": round(conf_value, 4) }) return {"code": 0, "count": len(detections), "detections": detections}启动服务:
pip install fastapi uvicorn uvicorn api_server:app --host 0.0.0.0 --port 80007.2 调用接口测试
curl -X POST "http://127.0.0.1:8000/predict" \ -F "file=@test_fracture.png" \ -F "conf=0.5"Python 调用示例:
import requests url = "http://127.0.0.1:8000/predict" files = {"file": open("test_fracture.png", "rb")} data = {"conf": 0.5} response = requests.post(url, files=files, data=data, timeout=30) print(response.json())接口服务的价值在于:桌面端、Web 端、手机端都可以复用同一个模型推理能力。实际使用时要考虑并发请求、超时时间、服务鉴权和访问限制。不要把带有接口服务的机器直接暴露在公网,建议只绑定127.0.0.1,或通过内网网关控制访问范围。
7.3 批量任务队列
如果一次性处理上千张图片,建议不要在主线程里循环调用模型,而是设计一个简单的任务队列:
- 启动时加载一次模型。
- 接口接收批量任务列表。
- 后台线程逐张读取图片、推理、保存结果。
- 将每张图的处理状态写入日志或数据库。
- 失败任务记录原因并支持重试。
from queue import Queue from threading import Thread class BatchTask: def __init__(self, image_paths, output_dir, conf=0.5): self.queue = Queue() self.output_dir = output_dir self.conf = conf self.failed = [] for path in image_paths: self.queue.put(path) def worker(self, model): while not self.queue.empty(): img_path = self.queue.get() try: model.predict( source=img_path, conf=self.conf, save=True, project=str(self.output_dir), name="batch_result", exist_ok=True ) except Exception as e: self.failed.append((img_path, str(e))) finally: self.queue.task_done() def run_batch(model, image_paths, output_dir): task = BatchTask(image_paths, output_dir) workers = [Thread(target=task.worker, args=(model,)) for _ in range(2)] for t in workers: t.start() for t in workers: t.join() return task.failed注意,多线程推理时如果显存不足,线程数要调低。批量任务一定要有日志,处理完成后统计成功和失败数量。
8. 资源占用与性能观察
8.1 显存占用观察
模型加载后,界面底部日志区可以打印当前进程占用情况。观察显存最直接的方式是 NVIDIA 显卡自带的命令:
nvidia-smi推理过程中持续观察nvidia-smi里的显存占用曲线。显存占用受以下因素影响:
- 模型大小:YOLOv8n 占用最小,YOLOv8x 会明显增加。
- 输入分辨率:从 640 提升到 1280,显存占用显著上升。
- 批量大小:单张推理和 batch=8 推理的显存占用差距很大。
- 是否开启 TensorRT:TensorRT 会做层融合和显存优化,通常能降低峰值显存。
在开发阶段建议先用 YOLOv8n + 640 分辨率 + batch=1 跑通,再根据实际显存余量逐步调大参数。
8.2 CPU 与 GPU 推理差异
GPU 推理的优势在大图和批量任务上非常明显。CPU 推理的优势是兼容性高,不需要配置 CUDA,适合没有 NVIDIA 显卡的机器。
实际开发中可以考虑在界面里增加一个“设备”下拉框,让用户选择cuda或cpu:
device = "cuda" if self.device_combo.currentText() == "GPU" else "cpu" results = self.model.predict(source=img_path, conf=conf, device=device)8.3 降低显存占用的方案
如果运行时报CUDA out of memory,可以依次尝试:
- 把输入分辨率从 640 降到 320。
- 把置信度参数固定,不做多模型并行加载。
- 把批量大小降到 1。
- 使用 YOLOv8n 替换 YOLOv8s。
- 关闭界面中其他占用显存的功能,比如同时显示多张结果图。
- 用 ONNX Runtime 或 TensorRT 推理,降低显存峰值。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动后 PySide6 窗口未出现 | 虚拟环境未激活或依赖缺失 | 检查终端报错日志 | 重新激活环境,pip show PySide6验证 |
| 模型加载失败 | 权重文件路径错误或文件损坏 | 打印完整异常信息 | 重新下载权重,确认路径包含.pt后缀 |
| 选择图片后界面不显示 | 图片路径包含中文或格式不支持 | 查看日志输出 | 改用英文路径,或预处理图片格式 |
| 检测结果框过多 | 置信度阈值设置过低 | 调整conf参数 | 提高到 0.5 或 0.6 |
| 漏检严重 | 置信度阈值过高、模型欠拟合或数据分布差异大 | 对比验证集图片 | 调整阈值,重新训练模型,检查数据集标注 |
CUDA out of memory | 显存不足 | 查看nvidia-smi | 降低分辨率、换轻量模型、减小 batch |
| CPU 推理速度很慢 | 图片分辨率过高 | 查看预处理耗时 | 缩放到 640 再推理 |
QLineEdit获取输入报错 | 误用了toPlainText() | 看代码调用链 | 使用.text()获取字符串 |
| 界面点击检测后卡住 | 推理在主线程同步执行 | 查看 CPU/GPU 使用率 | 使用 QThread 异步推理 |
| API 接口调用超时 | 并发请求过多或图片过大 | 查看服务端日志 | 增加超时时间,限制并发,优化图片大小 |
| 训练精度不稳定 | 数据集样本少、标注不一致 | 查看训练集和验证集分布 | 增加数据、统一标注标准、使用 K 折验证 |
| 批量任务中断 | 某张图片解码失败 | 检查失败日志 | 跳过异常文件,记录失败列表并重试 |
10. 最佳实践与使用建议
第一,先把最小流程跑通。不要一上来就追求完整界面,先用YOLO("best.pt").predict()验证模型本身没问题,再套 PySide6 外壳。模型有问题时,界面做得再完整也没有意义。
第二,项目目录要清晰分开。
project/ ├── models/ │ └── best.pt ├── data/ │ ├── images/ │ └── labels/ ├── ui/ │ └── main_window.py ├── inference/ │ └── detector.py ├── api/ │ └── server.py └── main.py模型文件、测试图片、输出结果分开管理,批量任务和接口服务独立成模块,方便后续扩展。
第三,批量任务一定要加日志和失败重试。实际处理上千张图片时,总有少数图片因为格式、解码或内存问题失败。把失败文件记录下来,下一轮只处理失败项,效率比整批重跑高很多。
第四,接口服务要限制访问范围。开发调试阶段绑定127.0.0.1,不要直接开在公网。如果需要在局域网内访问,设置 token 或简单的访问白名单。
第五,涉及医学影像时必须有合规意识。数据集要确认来源合法并完成脱敏,系统只能作为辅助判断的科研工具,不能直接对外输出“诊断结论”。如果项目要商用,必须咨询专业医疗合规人员。
第六,训练阶段建议小数据集跑通再逐步扩大。骨折检测数据量少是常见问题,先用几百张图片验证训练流程,不要一上来就构建上万张的数据集。
第七,权重文件要保存好训练参数。best.pt是模型文件,如果以后要重新训练或继续训练,建议把data.yaml、训练脚本和results.csv一并归档。
11. 总结与下一步
这套基于 YOLOv8 / YOLOv5 + PySide6 的骨科骨折诊断检测系统,最值得尝试的点是“模型 + 桌面界面 + 批量任务”的完整链路。它能帮你把一个训练好的.pt权重变成真正可操作的本地工具,同时也为你后续扩展到 Web 服务、接口调用和更多检测场景打下基础。
建议最先验证三个功能:单张图片检测是否准确、批量检测是否能稳定产出结果、PySide6 界面在切换模型和调整置信度阈值时是否流畅。
最容易踩的坑是三个:一是数据集样本少导致训练精度虚高,实际验证时漏检严重;二是 PySide6 主线程直接做推理导致界面卡死;三是接口服务没有做访问限制就直接暴露到网络。这三个问题在项目答辩或实际交付前务必先解决。
后续可以继续扩展的方向包括:导出 ONNX 模型用 ONNX Runtime 推理以减小 GUI 包体积、接入 DICOM 影像读取、增加检测结果报告导出、加入多模型集成投票机制,以及把推理服务迁移到 GPU 服务器上做集中式批量处理。整套系统建议收藏备用,尤其是准备做深度学习目标检测桌面应用的时候,可以直接拿这套架构做底子。