简介:这份资源是面向高校计算机、人工智能相关专业毕业设计参考的常见皮肤病辅助检测系统完整工程包,适合正在准备毕设或希望入门目标检测与桌面端界面开发的学生。项目以YOLOv8作为核心检测算法,对皮肤病图像中的病变区域进行识别与分类,并借助PyQt5搭建可自适应不同屏幕尺寸的操作界面,涵盖图像采集、本地图片选择、检测结果查看等交互流程,帮助读者理解从模型推理到GUI落地的完整链路。压缩包共85个文件,约34.32MB,包含20个py源码、26个pyc编译文件、15个png与4个jpg界面素材、3个pt权重文件,以及ui、qrc、css、ttf等界面与字体资源,另附程序运行说明文档和训练脚本。目前已有183人学习下载。读者可据此获得可运行的检测工程、界面自适应实现思路、模型权重与训练配置,以及目录模块划分参考,便于快速复现并在此基础上完成二次开发与论文撰写。
1. 从一份毕设压缩包说起:YOLOv8+PyQt5 皮肤病辅助检测到底能跑出什么效果
很多同学拿到「基于 YOLOv8+PyQt5 自适应界面设计的常见皮肤病辅助检测系统」这个题目时,第一反应是去搜 yolov8 下载、pyqt5 安装、yolov8 训练自己的数据集,结果环境还没配完就先被版本冲突劝退。我当年做类似项目时也踩过这个坑:明明 yolov8 在命令行里能推理,一接上 PyQt5 界面就报线程冲突,窗口直接卡死。这套系统的核心其实就两件事——用 YOLOv8 把皮肤病灶框出来并分类,再用 PyQt5 做一个能自适应不同分辨率、能加载图片和视频、能实时显示检测结果的桌面端界面。它适合做毕设的本科生、想快速验证医学图像检测流程的算法初学者,以及需要给非技术用户演示模型能力的开发者。读完你能拿到一条从数据标注、模型训练到界面集成的完整落地路径,而不是零散的教程拼凑。
2. 数据准备与 YOLOv8 训练:从 labelme 标注到损失曲线收敛
2.1 皮肤病数据集怎么标、怎么转成 YOLO 格式
常见皮肤病检测的数据来源一般是公开数据集加自己补拍,比如 DermNet、ISIC 这类。但公开数据集大多给的是分类标签,要做目标检测就得自己标边界框。我一般用 labelme 标,因为它能直接导出 JSON,再写脚本转成 YOLO 需要的 txt 格式。注意 labelme 在 Windows 下装 pyqt5 有时会冲突,如果报labelme 无法安装 pyqt5,先建一个干净虚拟环境,再指定 pyqt5 版本安装。
标注时每张图对应一个 JSON,里面 shapes 字段记录每个病灶的 label 和 points。YOLO 格式要求每行是class_id x_center y_center width height,且全部归一化到 0~1。下面这个转换脚本我用了很多次,直接改路径就能跑:
import json import os from pathlib import Path # 类别映射,按你的实际标签改 class_map = {"acne": 0, "eczema": 1, "psoriasis": 2, "normal": 3} def convert_labelme_json(json_path, output_dir, img_w, img_h): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_map: continue points = shape['points'] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 归一化中心点和宽高 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h width = (x_max - x_min) / img_w height = (y_max - y_min) / img_h lines.append(f"{class_map[label]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") # 输出同名 txt out_path = Path(output_dir) / (Path(json_path).stem + ".txt") with open(out_path, 'w') as f: f.write("\n".join(lines)) # 批量处理示例 json_dir = "labels_json" out_dir = "labels_yolo" os.makedirs(out_dir, exist_ok=True) for jf in Path(json_dir).glob("*.json"): # 这里需要知道原图尺寸,可以从 JSON 的 imageWidth/imageHeight 读 with open(jf, 'r', encoding='utf-8') as f: meta = json.load(f) convert_labelme_json(str(jf), out_dir, meta['imageWidth'], meta['imageHeight'])逻辑说明:脚本先读 labelme 的 JSON,拿到每个形状的类别和四个角点,再算外接矩形并归一化。参数上class_map必须和后面训练时的data.yaml里 names 顺序一致,否则类别会错位。imageWidth和imageHeight直接从 JSON 里取,不要硬编码,否则不同分辨率图片会标偏。
2.2 YOLOv8 训练参数怎么设:从预训练权重到损失曲线
环境搭好后,训练本身不复杂,关键是参数别乱设。我一般用yolov8n.pt或yolov8s.pt做预训练起点,皮肤病数据量通常不大,n 或 s 就够了。下面是我在 GTX 1660Ti 上跑通的训练命令:
yolo detect train \ data=skin_data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=30 \ device=0 \ project=skin_runs \ name=exp1参数含义:data指向你的数据集配置文件,里面写 train/val 路径和类别名;imgsz=640是输入尺寸,显存不够就降到 416 或 320;batch=16在 6G 显存上比较稳,再大容易 OOM;lr0初始学习率,lrf最终学习率因子,默认 0.01 就行;patience=30表示 30 轮没提升就早停,省时间。训练完在skin_runs/exp1下会生成results.csv,用下面这段代码画损失曲线:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("skin_runs/exp1/results.csv") df.columns = df.columns.str.strip() plt.figure(figsize=(10, 5)) plt.plot(df['epoch'], df['train/box_loss'], label='train box loss') plt.plot(df['epoch'], df['val/box_loss'], label='val box loss') plt.plot(df['epoch'], df['train/cls_loss'], label='train cls loss') plt.plot(df['epoch'], df['val/cls_loss'], label='val cls loss') plt.xlabel('epoch') plt.ylabel('loss') plt.legend() plt.savefig('loss_curve.png')如果 val loss 一直震荡不降,先看标注有没有漏标或框错,再考虑加数据增强或换更大模型。皮肤病图像颜色差异大,建议在data.yaml里开hsv_h、hsv_s增强,但别开太大,否则病灶颜色失真反而掉点。
3. PyQt5 自适应界面设计:从布局到检测结果实时渲染
3.1 用 QVBoxLayout+QHBoxLayout 做自适应布局
PyQt5 界面设计最怕写死坐标,换台电脑就错位。我一般用嵌套布局:顶层 QVBoxLayout,上面放图片显示区,下面放按钮和结果表。图片区用 QLabel 加setScaledContents(True),再配合resizeEvent动态缩放。下面是一个最小可用的主窗口骨架:
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTableWidget, QTableWidgetItem, QFileDialog) from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt import cv2 class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("皮肤病辅助检测系统") self.resize(1200, 800) central = QWidget() self.setCentralWidget(central) main_layout = QVBoxLayout(central) # 图片显示区 self.image_label = QLabel("请加载图片") self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(640, 480) self.image_label.setStyleSheet("border: 1px solid gray;") main_layout.addWidget(self.image_label, stretch=3) # 按钮行 btn_layout = QHBoxLayout() self.btn_load = QPushButton("加载图片") self.btn_detect = QPushButton("开始检测") btn_layout.addWidget(self.btn_load) btn_layout.addWidget(self.btn_detect) main_layout.addLayout(btn_layout) # 结果表 self.table = QTableWidget(0, 3) self.table.setHorizontalHeaderLabels(["类别", "置信度", "位置"]) main_layout.addWidget(self.table, stretch=1) self.btn_load.clicked.connect(self.load_image) self.btn_detect.clicked.connect(self.run_detect) self.cv_image = None def load_image(self): path, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.png *.jpg *.jpeg)") if path: self.cv_image = cv2.imread(path) self.show_image(self.cv_image) def show_image(self, cv_img): rgb = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) pix = QPixmap.fromImage(qimg) self.image_label.setPixmap(pix.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) def run_detect(self): # 这里接 YOLOv8 推理,下一节细说 pass逻辑说明:stretch参数控制图片区和表格的高度比例,窗口拉伸时图片区优先变大。show_image里把 OpenCV 的 BGR 转 RGB 再转 QImage,注意ch * w是每行字节数,写错会花屏。resizeEvent可以重写来实时缩放,但简单场景下setScaledContents配合scaled已经够用。
3.2 把 YOLOv8 推理塞进 PyQt5:线程与信号槽
直接在按钮回调里跑model.predict()会卡界面,因为推理是阻塞的。正确做法是开 QThread,推理完通过 pyqtSignal 把结果传回主线程更新 UI。下面是我常用的检测线程类:
from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO import cv2 class DetectThread(QThread): finished = pyqtSignal(object, list) # 传回画框后的图和结果列表 def __init__(self, model_path, image): super().__init__() self.model = YOLO(model_path) self.image = image def run(self): results = self.model.predict(self.image, conf=0.25, iou=0.45, verbose=False) boxes = [] annotated = self.image.copy() for r in results: for box in r.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) xyxy = box.xyxy[0].cpu().numpy().astype(int) boxes.append((cls_id, conf, xyxy.tolist())) cv2.rectangle(annotated, (xyxy[0], xyxy[1]), (xyxy[2], xyxy[3]), (0, 255, 0), 2) cv2.putText(annotated, f"{cls_id} {conf:.2f}", (xyxy[0], xyxy[1]-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) self.finished.emit(annotated, boxes)逻辑说明:conf=0.25是置信度阈值,皮肤病检测可以适当降低到 0.2 提高召回,但误检会变多;iou=0.45是 NMS 的 IoU 阈值,密集病灶可以调到 0.5。finished信号传回标注后的图和原始框列表,主线程收到后调show_image和填表格。注意 QThread 里不要碰 UI 控件,所有更新都通过信号槽。
4. 避坑与排查:版本冲突、显存爆炸和界面卡死
4.1 坑一:PyQt5 与 labelme 版本打架
现象:装完 labelme 后 PyQt5 被降级,自己写的界面报DLL load failed或could not find Qt platform plugin。原因:labelme 依赖特定 PyQt5 版本,和项目环境混在一起。解决:给 labelme 单独建虚拟环境,标注完导出 JSON 就行,训练和界面环境不要装 labelme。
4.2 坑二:训练时显存不够直接崩
现象:CUDA out of memory,尤其用yolov8m或imgsz=1280时。原因:batch 和输入尺寸太大。解决:先把batch降到 8 或 4,再把imgsz降到 416,还不行就换yolov8n。另外workers设太高也会占内存,Windows 下建议设 0 或 2。
4.3 坑三:界面点检测就无响应
现象:点「开始检测」后窗口白屏,几秒后才恢复。原因:推理在主线程跑。解决:按 3.2 节用 QThread,并且确保model = YOLO(...)在子线程里初始化,不要在主线程加载模型再传进去,否则第一次推理还是会卡。
4.4 坑四:检测结果框偏移或类别全错
现象:框的位置明显不对,或者所有病灶都标成同一类。原因:data.yaml里 names 顺序和标注时的 class_map 不一致,或者图片归一化时宽高用反。解决:核对data.yaml的 names 列表,确保和转换脚本里的 class_map 完全一致;归一化时确认img_w是宽度、img_h是高度,别写反。
4.5 坑五:打包成 exe 后找不到模型文件
现象:PyInstaller 打包后运行报FileNotFoundError: yolov8n.pt。原因:模型路径写成了相对路径,打包后工作目录变了。解决:用sys._MEIPASS拼绝对路径,或者把模型文件放到 exe 同目录并用os.path.dirname(os.path.abspath(__file__))定位。
5. 进阶技巧:用自适应阈值和热力图提升辅助检测可信度
模型跑通后,真正让这套系统在毕设答辩里站住脚的,是能不能解释「为什么模型认为这里是病灶」。我一般加两个东西:一是自适应置信度阈值,二是 Grad-CAM 热力图。自适应阈值根据图片整体亮度动态调conf,暗光图片适当降低,避免漏检;热力图则把模型注意力区域叠在原图上,让非技术用户也能看懂。
下面是一个简单的自适应阈值逻辑,放在推理前:
import cv2 import numpy as np def adaptive_conf(image, base_conf=0.25): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) mean_brightness = np.mean(gray) # 亮度低于 80 认为偏暗,降低阈值提高召回 if mean_brightness < 80: return max(0.15, base_conf - 0.1) # 亮度高于 180 认为过曝,提高阈值减少误检 if mean_brightness > 180: return min(0.5, base_conf + 0.1) return base_conf参数说明:base_conf是你验证集上调好的基准值,mean_brightness用灰度均值衡量整体亮度。这个逻辑不是万能的,但比固定阈值稳,尤其皮肤科图片经常有闪光灯过曝或暗光拍摄。
热力图部分可以用pytorch-grad-cam库,但注意 YOLOv8 的输出层结构,要指定 target layer 为 backbone 最后一层。我一般只在答辩演示时开热力图,因为计算慢,实时检测时关掉。最后说个血泪经验:别在毕设里堆太多花哨功能,把「加载图片→检测→显示框和类别→导出结果」这条链路做稳,比加十个用不上的按钮强。希望帮到你。
本文还有配套的精品资源,点击获取