简介:这份毕设参考资源面向计算机视觉方向的本科与研究生毕业生,提供一套基于YOLOv8与PyQt5的常见皮肤病辅助检测系统完整实现,可用于毕业设计选题、课程项目或算法落地练习。压缩包共85个文件,约34.32MB,包含20个py源码文件、26个pyc编译文件、15张png界面素材、3个pt模型权重以及ui、qrc、css、ttf等界面与字体资源,覆盖模型推理、界面布局、样式美化与配置管理等模块。项目以YOLOv8完成皮肤病图像中病变区域的识别与分类,借助PyQt5实现自适应不同屏幕尺寸的交互界面,支持摄像头实时采集与本地图片导入,并配有训练脚本、检测工具与运行说明文档。目前已有183人学习下载,读者可据此理清从数据准备、模型训练到界面集成的完整链路,快速搭建可演示的辅助检测原型,并参考其目录组织与排错思路完成自己的毕设开发。
1. 从一份毕设压缩包说起:yolov8+pyqt5 皮肤病辅助检测系统到底在做什么
很多同学拿到「基于yolov8+pyqt5自适应界面设计的常见皮肤病辅助检测系统」这个题目时,第一反应是去搜 yolov8下载、pyqt5安装,然后卡在环境上三天。我当年帮人看这类毕设,最常见的翻车现场不是模型训不出来,而是训练脚本跑通了,界面却连图片都传不进去。这套系统的本质其实很朴素:用 YOLOv8 做皮肤病灶区域的检测与分类,用 PyQt5 做一个能自适应窗口大小的桌面端界面,把「选图—推理—显示结果」这条链路串起来。它适合两类人:一类是需要一个能跑通、能答辩、能写论文的完整毕设;另一类是想练手「模型落地到桌面应用」这条工程链路的开发者。核心难点从来不在 YOLOv8 本身,而在于数据集标注质量、界面与推理线程的配合、以及自适应布局这三个容易被忽略的地方。下面我按自己实际搭过一遍的顺序,把选型、训练、界面、踩坑讲清楚。
2. 数据集与模型选型:为什么是 YOLOv8 而不是分类网络
2.1 皮肤病检测任务的本质是「定位+分类」而不是纯分类
很多人第一反应是用 ResNet 做图像分类,输入一张皮肤图,输出是哪种病。这个思路在毕设里能跑,但答辩时老师一句「病灶在图片哪个位置」就把你问住了。皮肤病的实际场景里,一张图可能有多个病灶,或者病灶只占画面一小块,纯分类网络会把整张图的背景噪声也学进去。YOLOv8 是单阶段目标检测,一次前向就输出边界框和类别,天然适合「一张图里找病灶并判断类型」这个需求。常见皮肤病数据集一般包含带状疱疹、湿疹、银屑病、痤疮、荨麻疹等几类,每类几十到几百张不等。我一般建议至少每类 150 张以上,否则 YOLOv8 的 mAP 会很难看。
选 YOLOv8 而不是 YOLOv5 的理由也很实际:Ultralytics 这套库把训练、验证、导出、推理全封装成一行命令,对毕设这种「时间紧、要出图、要写论文」的场景非常友好。yolov8n 权重只有 6MB 左右,gtx1660ti 这种老卡也能跑,甚至 ubuntu20.04 搭建 yolov8 环境 cpu 版本也能勉强推理。如果你只是做毕设演示,yolov8n 或 yolov8s 足够;想冲论文指标再上 yolov8m。
2.2 用 labelme 标注后转 YOLO 格式的完整流程
热词里 labelme 无法安装 pyqt5 是个高频问题,因为 labelme 本身依赖 PyQt5,版本冲突很常见。我的做法是给 labelme 单独建一个虚拟环境,别和主项目混在一起。
# 给标注工具单独建环境,避免和主项目 PyQt5 版本打架 conda create -n labelme_env python=3.9 -y conda activate labelme_env pip install labelme -i https://pypi.tuna.tsinghua.edu.cn/simple # 启动标注 labelme标注时选「创建矩形」,框住病灶,标签名用英文(如 eczema、psoriasis),别用中文,否则后面转换脚本容易出编码问题。标完每张图会生成一个同名 .json 文件。接下来要转成 YOLO 需要的 txt 格式,每行是类别id 中心x 中心y 宽 高,且都是归一化到 0-1 的值。
import json import os from pathlib import Path # 类别映射,顺序要和 data.yaml 里的 names 完全一致 class_map = {"eczema": 0, "psoriasis": 1, "acne": 2, "urticaria": 3} json_dir = Path("labels_json") out_dir = Path("labels_txt") out_dir.mkdir(exist_ok=True) for jf in json_dir.glob("*.json"): data = json.loads(jf.read_text(encoding="utf-8")) img_w = data["imageWidth"] img_h = data["imageHeight"] lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_map: continue pts = shape["points"] xs = [p[0] for p in pts] ys = [p[1] for p in pts] # 转成中心点+宽高的归一化格式 cx = (min(xs) + max(xs)) / 2 / img_w cy = (min(ys) + max(ys)) / 2 / img_h w = (max(xs) - min(xs)) / img_w h = (max(ys) - min(ys)) / img_h lines.append(f"{class_map[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") (out_dir / (jf.stem + ".txt")).write_text("\n".join(lines), encoding="utf-8")这段脚本的关键参数是class_map,它必须和后面 data.yaml 里的names顺序严格对应,错一位整个训练标签就全乱了。cx/cy/w/h都除以了图像宽高做归一化,这是 YOLO 格式的硬性要求,不归一化训练时 loss 会直接爆炸。转换完记得抽查几个 txt,用cat看一眼数值是不是都在 0-1 之间。
2.3 data.yaml 与目录结构:一个字母错就白训
YOLOv8 要求的数据集目录结构是固定的,我见过太多人因为路径写错导致训练时提示「no labels found」。
# data.yaml path: /home/user/skin_dataset # 数据集根目录,绝对路径最稳 train: images/train val: images/val nc: 4 # 类别数,必须和 class_map 数量一致 names: ["eczema", "psoriasis", "acne", "urticaria"]目录长这样:skin_dataset/images/train、skin_dataset/images/val、skin_dataset/labels/train、skin_dataset/labels/val。注意 images 和 labels 是平级的两棵子树,YOLO 会自动去 labels 下找同名 txt。常见错误是把 txt 和图片放同一个文件夹,或者 train/val 划分比例失衡。我一般按 8:2 划分,验证集至少留 30 张,否则 val 的 mAP 波动大得没法写进论文。
3. 训练、验证与损失曲线:把 yolov8 训到能答辩
3.1 环境搭建与预训练权重下载
yolov8环境配置其实不复杂,但版本要对。我的组合是 Python 3.9 + torch 2.0 + ultralytics 8.x。如果你只有 CPU,ubuntu20.04 搭建 yolov8 环境 cpu 版本也能跑,只是训练慢,建议直接用预训练权重做推理演示。
conda create -n skin_yolo python=3.9 -y conda activate skin_yolo # 装 pytorch,有显卡的按自己 cuda 版本去官网选命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple # 验证安装 yolo checksyolo checks会打印出你的 torch 版本、cuda 是否可用、以及 ultralytics 版本。如果 cuda 显示 False 但你明明有显卡,八成是 torch 装成了 cpu 版,重装即可。yolov8预训练权重下载不用手动找,第一次训练时 ultralytics 会自动从官方源拉 yolov8n.pt,网络不好就手动下载后放到项目根目录,训练时用model=yolov8n.pt指定本地路径。
3.2 训练命令与关键参数含义
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=30 \ project=runs/skin \ name=exp1逐个说参数:epochs=150是训练轮数,皮肤病数据集小,100-200 之间比较合适,太多会过拟合。imgsz=640是输入分辨率,显存不够就降到 416 或 320,但小病灶会漏检。batch=16看显存,gtx1660ti 6G 跑 640 大概能到 16,爆显存就减半。lr0=0.01是初始学习率,YOLOv8 默认带余弦退火,一般不用大改。patience=30是早停,30 轮没提升就停,省时间。训练完权重在runs/skin/exp1/weights/best.pt。
3.3 画损失函数曲线:论文里必须有的一张图
yolov8画损失函数曲线图不用自己写,训练完results.csv就在 exp1 目录下,里面记录了每轮的 box_loss、cls_loss、dfl_loss 和 mAP。
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/skin/exp1/results.csv") df.columns = [c.strip() for c in df.columns] # 列名可能带空格,先清理 fig, ax1 = plt.subplots(figsize=(10, 6)) ax1.plot(df["epoch"], df["train/box_loss"], label="box_loss") ax1.plot(df["epoch"], df["train/cls_loss"], label="cls_loss") ax1.set_xlabel("epoch") ax1.set_ylabel("loss") ax1.legend(loc="upper right") ax2 = ax1.twinx() ax2.plot(df["epoch"], df["metrics/mAP50(B)"], color="red", label="mAP50") ax2.set_ylabel("mAP50") ax2.legend(loc="lower right") plt.title("YOLOv8 training curve") plt.savefig("loss_curve.png", dpi=200)这段代码把 loss 和 mAP 画在同一张图上,双 y 轴。判断训练好坏的土办法:box_loss 和 cls_loss 应该整体下降并趋于平缓,如果 cls_loss 一直震荡不降,多半是标签类别不平衡或者标注框画得太随意。mAP50 能到 0.7 以上,毕设答辩就稳了。
4. PyQt5 自适应界面:把模型塞进桌面应用
4.1 自适应布局的核心是 QLayout 而不是固定坐标
pyqt5界面设计新手最容易犯的错是用setGeometry写死每个控件的位置,结果换个分辨率界面就散架。自适应界面的正确做法是用布局管理器:主窗口用 QVBoxLayout 竖着分三块(顶部工具栏、中间图像区、底部结果区),中间图像区用 QHBoxLayout 横着放原图和结果图。这样窗口拉伸时控件自动跟着变。
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog) from PyQt5.QtGui import QPixmap from PyQt5.QtCore import Qt class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("皮肤病辅助检测系统") self.resize(1000, 700) central = QWidget() self.setCentralWidget(central) root = QVBoxLayout(central) # 顶部按钮栏 top = QHBoxLayout() self.btn_open = QPushButton("选择图片") self.btn_detect = QPushButton("开始检测") top.addWidget(self.btn_open) top.addWidget(self.btn_detect) top.addStretch() # 弹簧,把按钮推到左边 root.addLayout(top) # 中间图像区,左右两张图 mid = QHBoxLayout() self.lbl_src = QLabel("原图") self.lbl_res = QLabel("检测结果") for lbl in (self.lbl_src, self.lbl_res): lbl.setAlignment(Qt.AlignCenter) lbl.setMinimumSize(300, 300) lbl.setStyleSheet("border: 1px solid #ccc;") mid.addWidget(lbl) root.addLayout(mid, stretch=1) # stretch=1 让图像区占满剩余空间 # 底部结果文字 self.lbl_info = QLabel("等待检测") root.addWidget(self.lbl_info) self.btn_open.clicked.connect(self.open_image) def open_image(self): path, _ = QFileDialog.getOpenFileName(self, "选图", "", "Images (*.png *.jpg *.jpeg)") if path: self.current_path = path pix = QPixmap(path).scaled(self.lbl_src.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.lbl_src.setPixmap(pix) if __name__ == "__main__": app = QApplication([]) w = MainWindow() w.show() app.exec_()关键点是root.addLayout(mid, stretch=1),这个 stretch 让图像区吃掉所有多余空间,窗口拉大时图片区跟着变大,这就是「自适应」的本质。Qt.KeepAspectRatio保证图片缩放不变形。top.addStretch()是弹簧,把按钮挤到左边,不加的话按钮会平均分布很难看。
4.2 推理线程与界面刷新:别让界面卡死
直接在按钮回调里跑model.predict()会让界面卡住几秒,用户体验极差,答辩时老师会以为你程序崩了。正确做法是把推理放到 QThread 里,通过信号把结果传回主线程。
from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class DetectThread(QThread): finished = pyqtSignal(object, list) # 传回绘制后的图 + 检测信息 def __init__(self, model, img_path): super().__init__() self.model = model self.img_path = img_path def run(self): results = self.model.predict(self.img_path, conf=0.25, imgsz=640) r = results[0] # plot() 返回带框的 numpy 图,BGR 格式 annotated = r.plot() infos = [] for box in r.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) infos.append(f"{self.model.names[cls_id]}: {conf:.2f}") self.finished.emit(annotated, infos)conf=0.25是置信度阈值,低于这个的框不显示,皮肤病场景建议 0.25-0.4,太低会框出一堆误检。r.plot()是 ultralytics 自带的绘图函数,直接返回画好框的图,省得自己写 cv2.rectangle。信号finished用object类型传 numpy 数组,主线程收到后转成 QImage 再显示。这样推理在后台跑,界面不卡,进度条也能正常转。
4.3 结果图从 numpy 到 QLabel 的转换
numpy 数组不能直接塞给 QLabel,中间要过一道 QImage。
import cv2 from PyQt5.QtGui import QImage, QPixmap def show_result(self, annotated, infos): # ultralytics 返回的是 BGR,转成 RGB rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) pix = QPixmap.fromImage(qimg).scaled(self.lbl_res.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.lbl_res.setPixmap(pix) self.lbl_info.setText(" | ".join(infos) if infos else "未检测到病灶")ch * w是每行字节数,这个参数写错图会花屏,是血泪经验。Format_RGB888对应三通道 RGB,别用 Format_BGR888,Qt 没这个格式。检测信息拼成字符串显示在底部,答辩时一眼能看到类别和置信度。
5. 避坑与排查:那些让我熬夜的翻车现场
5.1 训练时提示 no labels found
现象:yolo detect train跑起来后警告一堆图片找不到标签,mAP 全程为 0。原因:data.yaml 里的 path 和实际目录对不上,或者 labels 文件夹名拼错(写成 label 少个 s)。解决:用ls skin_dataset/labels/train | head确认 txt 存在,再检查 data.yaml 的 path 是不是绝对路径。相对路径在不同工作目录下启动会失效,一律用绝对路径最稳。
5.2 pyqt5 界面显示中文乱码或方框
现象:按钮上的中文变成方块。原因:系统缺中文字体,或者代码文件编码不是 utf-8。解决:文件头加# -*- coding: utf-8 -*-,并在 QApplication 创建前设置字体app.setFont(QFont("Microsoft YaHei", 10)),Linux 下换成WenQuanYi Micro Hei。别用默认字体,跨平台必翻车。
5.3 labelme 装不上或和主项目 PyQt5 冲突
现象:pip install labelme后主项目的 PyQt5 报版本错误,或者 labelme 启动闪退。原因:labelme 依赖的 PyQt5 版本和你主项目不一致,装在一起互相覆盖。解决:给 labelme 单独建 conda 环境,标注和训练用两个环境,互不干扰。这是最省心的做法,别想着一个环境走天下。
5.4 推理时显存不足 CUDA out of memory
现象:训练能跑,推理时爆显存。原因:PyQt5 主进程已经占了一部分显存,模型加载又占一块。解决:推理时把imgsz降到 416,或者用model.predict(..., device='cpu')强制走 CPU。毕设演示对速度要求不高,CPU 推理一张图一两秒也能接受,稳定性优先。
5.5 自适应界面拉伸后图片变形
现象:窗口拉大,图片被拉扁。原因:QLabel 的 scaled 没加Qt.KeepAspectRatio,或者 QLabel 本身没设 minimumSize 导致布局算不出比例。解决:scaled 一定带 KeepAspectRatio,QLabel 设 minimumSize 给布局一个下界,再配合 stretch 分配空间。三个一起用才稳。
6. 进阶技巧:把检测结果导出成可写进论文的结构化数据
毕设答辩时老师常问「你这系统除了显示框,还能输出什么」。我一般会加一个导出功能,把每张图的检测结果存成 CSV,包含文件名、类别、置信度、边界框坐标。这样论文里的「实验结果分析」章节就有真实数据支撑,而不是只贴几张截图。
import csv from pathlib import Path def export_results(model, img_dir, out_csv="detect_results.csv"): img_dir = Path(img_dir) rows = [] for img in img_dir.glob("*.jpg"): r = model.predict(str(img), conf=0.25, verbose=False)[0] for box in r.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) x1, y1, x2, y2 = [round(float(v), 2) for v in box.xyxy[0]] rows.append([img.name, model.names[cls_id], round(conf, 4), x1, y1, x2, y2]) with open(out_csv, "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["image", "class", "confidence", "x1", "y1", "x2", "y2"]) writer.writerows(rows) print(f"导出 {len(rows)} 条检测记录到 {out_csv}")encoding="utf-8-sig"是为了 Excel 打开不乱码,这个细节很多人不知道。verbose=False关掉 predict 的进度打印,批量跑的时候日志干净。导出后你可以用 pandas 统计每类的平均置信度、检出数量,直接做成论文里的表格。
验证系统是否真的可用,我的习惯是拿 10 张训练集里没出现过的图跑一遍,人工核对框的位置和类别对不对。如果 10 张里错 3 张以上,说明数据集标注质量有问题,回去重新标,别急着调模型参数。模型改进(比如加协调注意力机制)是锦上添花,数据质量才是地基。我踩过最大的坑就是一开始贪快,标注框画得比病灶大一圈,结果模型学出来的框全是虚的,mAP 死活上不去,回头重标花了两倍时间。所以宁可标注慢一点,也别糊弄。希望帮到你。
本文还有配套的精品资源,点击获取