
简介基于YOLO的帕金森手绘螺旋与波浪数据集面向医疗图像分析、模式识别及运动障碍研究者服务于帕金森病早期检测、健康人与患者区分等目标检测任务。资源包共2000个文件含1999个txt格式的YOLO标注文件和1个yaml配置整体约817.5MB其中txt文件记录各类目标的框坐标与类别yaml定义数据路径和类别信息图像已做标准化、尺寸调整、去噪等预处理并划分训练组与测试组便于模型训练、评估与结果复现。数据集围绕手绘螺旋和波浪两类典型绘图构建注释信息可直接用于YOLO系列模型训练既为医学辅助诊断提供有价值的数据基础也为计算机视觉、机器学习和模式识别算法开发、测试及比较提供了具体场景。已有69人浏览学习适合需要真实医疗绘图数据进行YOLO检测训练、帕金森早期识别研究的开发者与科研人员使用。1. 基于 YOLO 的帕金森手绘螺旋与波浪数据集把量表图像直接变成检测任务第一次看到这个标题我的直觉是手绘螺旋和波浪图又不是自然物体YOLO 真能检出个什么东西等实际跑完一轮训练才发现这套基于 YOLO 的帕金森数据集把健康人和帕金森病患者手绘的螺旋、波浪预处理得相当干净每个图形都配好了边界框和类别注释训练任务从「整张图分类」变成了「定位图形并区分健康/患病」。模型要在一张扫描图上找到手绘区域再给出对应标签。这个转换让问题更贴近真实筛查流程患者随手画在纸上系统自动输出概率而不是先人工裁图再分类。下面按数据构成、标注转换、训练、避坑和验证顺序写按这条流程能在一张普通显卡上把最小可用的检测模型跑通。2. 数据构成与预处理螺旋和波浪里藏着什么怎么喂给检测头2.1 健康人与患者的绘图差异如何变成检测器的学习信号手绘螺旋和波浪不是随便选的图形。神经科用手绘螺旋评估运动控制能力时会观察线条是否平滑、间距是否均匀、是否出现锯齿或停顿。健康人绘制螺旋时笔迹压力稳定螺旋半径从内向外均匀扩大相邻线条间距接近等差帕金森病患者由于静止性震颤和运动迟缓画出来的螺旋线条会出现局部锯齿、半径突变、重叠和断笔。波浪图测试的是连续节律控制健康人的波浪振幅和频率相对稳定患者则容易出现振幅起伏、线条抖动甚至贴到一起。从目标检测角度看这些差异会反映在边界框内部的梯度方向和边缘密度上。健康手绘线条方向变化平缓局部边缘统计特征集中患者手绘线条在局部区域反复改向边缘密度突增方向直方图更散。YOLO 的主干网络在 COCO 等自然图像上学过大量边缘和纹理特征对手绘线条有一定迁移能力但自然图像里几乎没有这类连续笔迹所以不能直接把预训练权重当作最终模型。最稳妥的做法是把预训练权重作为初始参数用这个帕金森数据集微调让检测头重新学「什么样的线条密度和方向变化说明画图者手部控制不稳」这个抽象特征。这里有个必须提前做的决定类别标签怎么定。有的注释集合只提供 healthy / patient 二分类有的按「图形类型 患病状态」拆成四类。我先按二分类跑 baseline因为筛查场景关心的是有没有病四分类更适合科研分析能同时对比螺旋和波浪的敏感性差异。下面这个表可以帮你选标签方案输出含义适用场景样本利用率2 类 healthy / patient只判断是否患病初筛、自动化辅助评分高两种图形共享类别4 类 healthy_spiral / patient_spiral / healthy_wave / patient_wave同时保留图形类型与状态科研分析、分类别评价低类别样本被打散一旦选了四分类后面的预处理、数据增强、类别权重都要跟着改。转标后再回来改类别体系所有标注文件和 data.yaml 重新生成基本上等于重做一遍。2.2 完全预处理顺序去留白、统一通道、保留宽高比、按人划分预处理不是简单把图变小「完全预处理」在我看是四条固定步骤顺序不能乱。第一步是去留白。扫描件四周经常留大片空白如果不裁掉YOLO 的边界框会覆盖整张纸检测头只学会判断「纸上有没有线」而不是判断线的形态。常见做法是用 OpenCV 读取灰度图把像素值小于阈值的部分当背景找到非零像素的最小包围盒再外扩 1% 到 3% 的边距。阈值要看扫描背景亮度一般 10 到 30 比较安全背景越脏阈值越要提高。第二步是统一通道。很多扫描原图是灰度图YOLO 输入要求三通道。直接用cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)把灰度复制成三分量比用 PIL 转 RGB 更稳定因为 OpenCV 读出来是整型不会出现 PIL 在灰度模式下返回二维数组的问题。直方图均衡化可加可不加加了会增强纸张纹理手绘线条对比度本来就高我一般不加。第三步是统一最短边但不要为了省事直接缩成正方形。YOLO 训练时会给输入图做 letterbox保留原宽高比并补灰边如果你提前把所有图缩成正方形标签也基于新尺寸归一化训练时再经过一次 letterbox坐标等于被变形两次。正确的做法是只把最小边缩到 640 或 1280长边保持比例其余交给训练时的 letterbox。标注文件始终基于原图尺寸归一化不要提前改变。第四步是按人拆分训练集和验证集不是按图数量硬拆。同一个患者可能画了三张螺旋、两张波浪这些图像特征高度相似如果同一人的图同时进 train 和 val模型学到的是「这个人」而不是「这个病」。最好根据文件名里的编号去重把人整组放进同一边。下面这段脚本做了裁剪和最小边放大from pathlib import Path import cv2 import numpy as np def crop_scan(src_dir, dst_dir, threshold20, margin_ratio0.02, min_side640): dst_dir.mkdir(parentsTrue, exist_okTrue) for img_path in sorted(Path(src_dir).glob(*.png)): img cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE) if img is None: print(fskip broken: {img_path.name}) continue mask img threshold ys, xs np.where(mask) y0, y1, x0, x1 ys.min(), ys.max(), xs.min(), xs.max() pad_x int((x1 - x0) * margin_ratio) pad_y int((y1 - y0) * margin_ratio) y0 max(0, y0 - pad_y) y1 min(img.shape[0], y1 pad_y) x0 max(0, x0 - pad_x) x1 min(img.shape[1], x1 pad_x) crop img[y0:y1, x0:x1] if min(crop.shape[:2]) min_side: scale min_side / min(crop.shape[:2]) crop cv2.resize(crop, None, fxscale, fyscale, interpolationcv2.INTER_CUBIC) rgb cv2.cvtColor(crop, cv2.COLOR_GRAY2BGR) cv2.imwrite(str(dst_dir / img_path.stem), rgb, [cv2.IMWRITE_PNG_COMPRESSION, 3])逻辑说明先用阈值二值化得到手绘像素再用np.where找出包围盒。margin_ratio0.02表示在包围盒外再扩 2% 的边距这个值比 0.01 更稳比 0.05 更紧凑。min_side640保证小图被放大否则训练时手绘线条在缩略图上会被压缩成一条糊线。注意脚本最后用stem做文件名去掉了.png后缀后面生成标签时按 stem 匹配即可。2.3 样本不平衡先统计再决定增强策略帕金森手绘数据集通常健康人样本比患者样本多因为患者手部僵直、容易中途放弃有效绘图率低。直接训练模型会偏向 healthy患者类别的 recall 可能不到 0.7。先统计类别数量再决定要不要增强grep -r 1 labels/train/ | wc -l grep -r 0 labels/train/ | wc -l这个命令按类别 ID 统计标签行数注意第二行搜索的是完整字段 1 避免把类别 1 的某个坐标值误算进去。如果想更严谨用 Python 逐行解析。如果两者比例超过 2:1常见策略是把患者图旋转几度、轻微缩放后复制一份进训练集但复制图像会产生重复样本导致模型记住特定笔迹。我更倾向用 YOLO 自带的训练期增强在后面第 4 章单独说参数。手绘线条不是照片色域增强几乎没用重点放在几何扰动。3. 把注释转成 YOLO 格式目录结构、类别 ID 与标签检查3.1 标准目录结构与 data.yamlYOLO 物体检测训练要求图像和标签放在并列目录下各分 train 和 val。创建目录mkdir -p datasets/pd_yolo/{images/{train,val},labels/{train,val}}图像文件放images/train对应标签放labels/train两个目录下的文件必须同名前缀一致。标签是纯文本每行格式为class_id x_center y_center width height五个值都用归一化坐标范围 0 到 1。比如一张 1600×1200 的图上某个螺旋框左上角在 (400,300)宽高各 800、600那么中心点归一化是 (800/1600, 600/1200) (0.5, 0.5)宽高归一化是 (800/1600, 600/1200) (0.5, 0.5)。最常见的翻车点是把左上角和宽高的像素值直接写进 txt训练不报错但 loss 长期不收敛。dataset 根目录下放 data.yamlpath: /data/workspace/datasets/pd_yolo train: images/train val: images/val nc: 2 names: 0: healthy 1: patient这里路径不要有中文和空格否则部分版本 ultralytics 在 Windows 上解析会出问题。train和val填相对path的路径不要在开头加斜杠训练命令推荐先 cd 到项目根目录再执行避免 IDE 调试器路径解析不一致。3.2 标签合法性检查宁可慢不可跳标签转换完成后第一件事不是训练而是跑检查脚本。下面这段脚本会扫描所有 txtfrom pathlib import Path import cv2 label_dir Path(datasets/pd_yolo/labels/train) img_dir Path(datasets/pd_yolo/images/train) num_classes 2 for label_path in sorted(label_dir.glob(*.txt)): img_path img_dir / (label_path.stem .png) if not img_path.exists(): print(fmissing image: {label_path.stem}) continue lines label_path.read_text().strip().splitlines() if not lines: print(fempty label: {label_path.name}) continue for line in lines: parts line.split() if len(parts) ! 5: print(fbad format: {label_path.name}: {line}) continue cls, xc, yc, w, h map(float, parts) if cls num_classes: print(fclass id out of range: {label_path.name}: {line}) if not (0 xc 1 and 0 yc 1): print(fcenter out of range: {label_path.name}: {line}) if not (0 w 1 and 0 h 1): print(fwh out of range: {label_path.name}: {line})逻辑说明先检查图像是否存在再查空标签最后逐行检查格式、类别 ID、中心点坐标和宽高。镜像文件名后缀不一致是标注导出时最常见的错误检查脚本能替你兜底。w和h正常情况下都小于 1如果只有宽大于 1多半是转换脚本里没有除以图像宽度。运行后如果出现很多中心点越界优先看是不是 PNG 和 JPG 后缀混用。只扫.png的话JPG 图像会被跳过标签文件却存在训练时同样会丢样本。3.3 从 CVAT 或 JSON 转换时的三个注意点公开手绘数据集如果是新导出的注释很多来自 CVAT 或 LabelMe。从 CVAT 导出的 VOC XML 转 YOLO坐标变换公式是xc (x_min x_max) / 2 / img_width yc (y_min y_max) / 2 / img_height w (x_max - x_min) / img_width h (y_max - y_min) / img_height注意 XML 里左上角和右下角是绝对像素中心坐标要先加后除不要单独除。类别映射不要用导出顺序因为 CVAT 的标签顺序可能和你的 names 列表不一致应该先读 XML 里的 name 字段再做字符串到 ID 的字典映射。还要注意重复导出。同一个图像目录被多次导出时文件名可能带上(1)后缀但图像文件没变标签文件却多出来一份。这种情况只靠重跑检查脚本查文件前缀不够还要检查同一源图是否对应多个 txt。我一般会写一个按 stem 去重的命令保留文件时间戳最新的那一个老旧副本直接移入backup/目录。4. 用 YOLOv8 训练自己的帕金森数据集命令、超参与损失函数观察4.1 安装与预热预测这里用 YOLOv8 作为示例因为小数据上手最快命令行工具完整不用写训练循环。安装pip install ultralytics如果本机 torch 版本较旧安装 ultralytics 时会提示依赖冲突建议新建一个虚拟环境避免把现有项目环境搞坏。装完后先用 CPU 跑一次预测确认环境链路通yolo predict modelyolov8n.pt sourcedatasets/pd_yolo/images/val devicecpu这一步能提前暴露 CUDA、torchvision、opencv 的兼容问题。如果 CPU 预测都报错问题通常不在数据而在安装环境如果 CPU 能跑但 GPU 不能重点查显卡驱动和 CUDA 版本。4.2 最小训练命令与参数解释训练命令yolo train datadatasets/pd_yolo/data.yaml modelyolov8n.pt epochs80 imgsz640 batch16 lr00.01 patience20 device0参数说明imgsz640小模型默认分辨率。帕金森手绘的边界框占图比例高640 能保留清晰笔画如果患者手绘的锯齿在缩略图上看不清再调 1280但要显存更大。batch168GB 显存可以跑再小降到 8不要靠降 imgsz 来保 batch图像质量对医学检测更重要。lr00.01对预训练权重微调稍大。数据量小的场景我一般改成lr00.001前 20 轮损失更稳。patience20连续 20 轮 val 指标不提升就停避免无意义等待。训练时终端会打印box_loss、cls_loss、dfl_loss。对帕金森这个场景cls_loss比box_loss更重要因为螺旋和波浪的定位区域相对固定模型主要靠内容分类来区分 healthy 和 patient。如果 cls_loss 降不下去先回查标签类别有没有混乱再去调数据增强。4.3 数据增强参数对手绘线条要克制YOLOv8 默认增强对自然图像有效对手绘线条不一定。连续螺旋线最怕被 mosaic 拼接打断。mosaic 把四张图拼成一张线条衔接处出现人工断口这与帕金森患者的断笔、抖动在局部特征上非常像模型容易被误导。建议训练时显式关闭或调低yolo train datadatasets/pd_yolo/data.yaml modelyolov8n.pt epochs80 imgsz640 batch16 mosaic0.3 mixup0.0 degrees5各增强项的调节参考如下参数推荐值作用风险mosaic0.3拼图扩充样本打断连续笔迹制造伪断点mixup0.0混合图像稀释患者特征fliplr0.5水平翻转对手绘方向不敏感安全degrees5小角度旋转过大会改变螺旋中心结构scale0.3缩放过大会让笔画粗细比例失真这些参数最终要按 val mAP 调不要全照搬。如果验证集里患者插图很少调增强不如先去补样本。4.4 别只看 mAP要分开看类别 recall帕金森数据集类别不平衡时mAP 会虚高。训练结束后单独跑验证yolo val modelruns/detect/train/weights/best.pt datadatasets/pd_yolo/data.yaml输出里会显示每个类别的 precision 和 recall。如果 healthy 的 recall 0.98patient 只有 0.65理由是患者样本少不是模型不够强。这时先回第 2 章看类别统计不要盲目加大 epoch。用手绘数据训练时过拟合比欠拟合更容易发生因为每张图的笔画都是独步特征。5. 训练和标注避坑最容易翻车的五个细节5.1 边界框太紧模型把线稿端点当特征现象val mAP 有 0.9但拿一张新画螺旋进去模型经常把螺旋中心小圆圈单独框出来类别结果还翻转。原因标注时为了贴合线条边界框沿笔画外缘切得过紧中心小圆曲率最大局部边缘密度最突出被检测头当成另一个目标类型。解决预处理加外扩边距。前面脚本里的margin_ratio调成 0.02 到 0.03重新生成图像和标签。如果已经训练完一轮改完要重新生成标签再训练只改图像不改标签会直接错位。5.2 按图划分训练集和验证集导致患者身份泄漏现象训练损失正常val mAP 很高但换一个陌生患者的新图置信度明显下降有的图被漏检。原因同一患者的多张绘图被分到 train 和 val 两侧模型记住的是这个人的笔迹而不是帕金森病这个类别。解决按受试者编号分组。先用文件名里的编号去重把同一编号的所有图像放在同一边。评价模型时要与人无关按人数划分而不是按图数量硬切。5.3 提前缩成正方形推理时边界框整体偏移现象训练时 mAP 正常但推理阶段喂一张没经过预处理的原始宽高比图像边界框位置明显偏左或偏上置信度也变低。原因预处理脚本把所有图 resize 成正方形标签基于正方形图归一化训练时 YOLO 又一次在正方形图上做 letterbox相当于二次变形。推理时如果只做一次 letterbox坐标对不上。解决预处理只做最小边缩放保留宽高比标签始终基于原图尺寸训练命令里的 imgsz640 由 ultralytics 内部处理 letterbox。这是一条血泪经验检查脚本里加一条if w 1.0 or h 1.0的提示可以尽早发现。5.4 类别不平衡时先改增强再考虑换模型现象patient 类 recall 只有 0.6整体 mAP 却被 healthy 拉起来肉眼看到测试图里患病螺旋被漏框。原因YOLO 检测头在训练时把每个框当独立样本healthy 样本远多于 patient分类损失梯度被多数类压住。解决先统计类别比例。比例超过 2:1 时离线增强患者图旋转 5 到 10 度、轻微缩放后复制进训练集再不行就在损失函数层面给 patient 类提高权重。不要一上来换大模型样本量不足的情况下大模型过拟合更严重。5.5 患者手绘重叠笔画未定义清楚标注规范不一致现象同一张患者螺旋有的标注员用一个完整大框有的把断笔拆成两个小框训练出的模型尺寸敏感对同一类型的图时而输出一个大框、时而输出两个小框。原因患者手部抖动导致笔迹重叠断笔是否是独立目标这个标注规则没定清楚标注员各按各的理解执行。解决在标注规范里写死对象定义。如果目标是「独立笔画段」就允许一张图多框如果目标是「整条螺旋线」就必须一个图一个框。中间状态会让检测头的回归分支学到一个模糊的边界两个标签都训练不出来。规则确认后重新检查所有患者图的框数量分布异常图单独修。6. 进阶验证技巧用置信度和仿射稳定性判断模型学到了什么训练完 best.pt 后先别急着看 mAP做两个小实验更有效。第一个实验是把一张健康螺旋旋转 15 度再做镜像用同一个模型分别预测。健康手绘和患者手绘的差异来自震颤频率和线条平滑度旋转之后这些统计特征应该基本不变所以类别置信度不该大幅翻转。如果旋转 15 度后标签从 healthy 变成 patient大概率是训练时没开旋转增强模型把笔画方向当成了特征。from ultralytics import YOLO from PIL import Image, ImageOps import numpy as np model YOLO(runs/detect/train/weights/best.pt) path datasets/pd_yolo/images/val/h_001.png img Image.open(path).convert(RGB) views { original: img, rot15: img.rotate(15, fillcolor255), mirror: ImageOps.mirror(img), } for name, view in views.items(): result model.predict(np.array(view), imgsz640, conf0.25)[0] if result.boxes is None: print(f{name}: no detection) continue cls int(result.boxes.cls[0]) conf float(result.boxes.conf[0]) print(f{name}: class{cls}, conf{conf:.3f})这段代码的关键是只做几何变换不做颜色和裁剪变化保证模型看到的是同一内容的不同视角。如果三组预测的类别不一致说明增强策略里缺旋转项回到训练命令加degrees10再训练。镜像结果允许置信度略低因为手绘方向翻转会让部分笔画分布改变但类别至少要保持一致。第二个实验是看多次预测的置信度方差。对同一张图连续预测五次如果置信度波动超过 0.05检查输入图像是否被外部的预处理代码缩放过。常见做法是让训练和推理共用同一个 letterbox 逻辑不要在推理链路里单独加像素值归一化。模型训练时手绘线条亮度范围已经固定推理端重复归一化会改变对比度反而制造域偏移。我在一个手绘螺旋实验里遇到过 val mAP 超过 0.95但旋转 15 度后类别从 healthy 翻成 patient 的情况最后定位到训练时默认关闭旋转增强模型抓住了笔画起始方向。后来把 degrees 调到 10 并重训后才稳定。这个教训让我养成一个习惯验证报告里至少放原图、旋转图和镜像图三组预测不只放 val 指标。手绘医疗数据的样本量小任何单组指标的漂亮成绩都可能靠偶然特征撑起来多视角测试能筛掉这类假阳性。希望这些经验和脚本能帮你在螺旋、波浪数据集上少走几步弯路。本文还有配套的精品资源点击获取