简介:面向乳腺癌病灶自动检测的YOLO格式数据集,专为医学影像AI与目标检测任务设计,帮助算法工程师、医学科研人员快速训练乳腺癌自动检测模型,解决病灶定位与辅助诊断需求。压缩包共2000个文件,主要由1316个txt标注文件与682张jpg医学影像构成,同时包含1个yaml训练配置和1个docx说明文档,整体约57.65MB。标注文件与图像严格对应,可直接适配YOLOv5/v7/v8等主流框架。数据集包含1316张专业医学影像,训练集1053张、验证集263张,全部聚焦Breast Cancer类别,由医学团队精准标注,覆盖不同密度、形态的恶性病变组织,可用于医学影像AI诊断系统开发、医疗设备实时检测模块集成、癌症早期检测算法研究以及医学影像课程教学等场景。目前已有235人浏览学习,适合需要真实医学标注数据开展模型训练与学术研究的开发者使用。
1. 乳腺癌医学影像检测数据集.zip:先别急着解压,先想清楚它能不能进训练管线
拿到一份「乳腺癌医学影像检测数据集.zip」,最容易犯的错是双击解压后直接开训练。这份压缩包装的通常是乳腺X线、超声或病理切片影像,以及配套的病灶框、mask 或 CSV 标签文件,本质上是目标检测工程的起点,但也只是起点。下载之后的第一关,不是急着看标注合不合理,而是确认 zip 本身完整、解压目录规整、标注格式能被框架读进去。这篇笔记就按这个顺序,把这个数据集从压缩包变成可训练数据集的完整路径拆开,顺带把 zip 解压、伪加密、格式转换和 YOLOv8 训练里的常见问题一次说清。
2. 解压不是双击:先校验哈希、再识破伪加密,最后选对解压命令
2.1 为什么我先算 SHA-256 而不是直接解压
医学影像数据集通常体积大、文件多,从数据集下载站、网盘或教研协作渠道传到本地,中途发生截断或位翻转的概率比你想象的高。zip 文件本身带 CRC32 校验,但很多解压工具对 CRC 错误只是弹个警告然后继续解,被解出来的文件已经是坏的,你还在拿坏数据训练,回头查起来玄学问题一大堆。
我拿到 zip 的第一件事永远是算哈希。如果发布方给了 SHA-256 值,直接对比;没给的话,至少把本地哈希记下来,复现问题时能确认不是解压环节出错。
sha256sum breast_cancer_dataset.zipWindows 上可以用certutil -hashfile breast_cancer_dataset.zip SHA256,顺手也能在 PowerShell 里用Get-FileHash拿到同样结果。Linux 下如果只想快速确认文件没被截断,unzip -t或者7z t也可以,但哈希的优先级更高,因为它不依赖 zip 内部结构。
import hashlib h = hashlib.sha256() with open("breast_cancer_dataset.zip", "rb") as f: for chunk in iter(lambda: f.read(65536), b""): h.update(chunk) print(h.hexdigest())这段 Python 等价于命令行 sha256sum,按 64KB 分块读入,避免一次性把几个 GB 的 zip 全加载进内存。分块大小对结果没有影响,只影响内存峰值和进度感知,你可以在循环里加个计数器打印进度。注意哈希校验的是压缩包本身,不是解压后的文件,所以要在解压前执行。
2.2 用 zipfile 探针识别伪加密与 EOCD 损坏
zip 格式里,每个文件头的通用标志位(general purpose bit flag)第 0 位如果置 1,解压工具就认为文件被密码加密。但有些压缩包在打包时这位置位了,实际上并没有真正的加密数据,这就是所谓「zip 伪加密」。这种现象在数据集分发场景里并不罕见,常常是打包工具兼容性问题造成的。
伪加密的特征是:解压软件要求输入密码,但你输入空密码或者随便什么都能解出来一部分。判断它不难,写个探针读文件头就行。
import struct path = "breast_cancer_dataset.zip" with open(path, "rb") as f: data = f.read() # zip 解压目录记录 EOCD 签名在文件尾部 eocd_pos = data.rfind(b"\x50\x4b\x05\x06") if eocd_pos == -1: raise SystemExit("找不到 EOCD,包可能被截断,或根本不是有效 zip") # 从 EOCD 里读中央目录条数 total = struct.unpack_from("<H", data, eocd_pos + 10)[0] print(f"中央目录记录数: {total}") # 逐条读取本地文件头的加密标志位 pos = eocd_pos for _ in range(total): local_pos = data.rfind(b"\x50\x4b\x03\x04", 0, pos) if local_pos == -1: print("本地文件头缺失") break flag = struct.unpack_from("<H", data, local_pos + 6)[0] if flag & 0x0001: print(f"偏移 {local_pos}: 加密标志位置位, flag = {flag:#06x}") pos = local_pos - 1这段脚本从尾部 EOCD 开始往回找每个本地文件头,检查偏移量 +6 处的标志位。0x0001 就是加密位。如果所有文件的加密位都是 0,那 zip 根本不需要密码,问题出在下载工具或解压工具上,换一个工具或挂载方式就好。如果只有部分文件置位,那大概率是伪加密或打包时误设置,需要修复。
伪加密的修复办法不是破解密码,而是把标志位改回去重新打包。
out = bytearray(data) # 把上面定位到的 local_pos 对应的标志位第 0 位清零 if flag & 0x0001: flag_clean = flag & ~0x0001 out[local_pos + 6:local_pos + 8] = struct.pack("<H", flag_clean) print(f"已清除偏移 {local_pos} 的加密标志位") with open("breast_cancer_dataset_fixed.zip", "wb") as f: f.write(out)注意:这只对伪加密有效,真正的密码加密数据本身是加密的,靠改标志位只会解出乱码,这条路不要走。
2.3 Linux 和 Windows 下解压命令的选择与路径习惯
确认 zip 没有结构问题之后,才轮到解压。Linux 上最常见的解压命令组合是unzip和7z,前者够用,后者在处理损坏包或非标准压缩方法时更稳。
unzip breast_cancer_dataset.zip -d breast_dataset-d指定输出目录,避免把几百个文件直接撒在当前位置。如果unzip报missing zip entry或invalid zip archive: could not find EOCD,说明 EOCD 区域损坏,先用第 2.2 节的探针确认结构。
7z x breast_cancer_dataset.zip -obreast_dataset7-Zip 命令行在 Linux、Windows、macOS 下都有对应版本,-o参数指定输出目录,注意-o后面不能有空格。遇到 CRC 错误时,7z x的容错能力通常比unzip好一些,能跳过坏块解出大部分文件。
Windows 上右键解压虽然方便,但有两个坑:一是中文路径,尤其是压缩包内部目录名带空格或中文,解压后 OpenCV 读图时经常找不到文件;二是右键解压默认不校验完整性。我更推荐用 PowerShell 调 .NET 的 ZipFile 类,或者直接 Python 解压:
import zipfile zip_path = "breast_cancer_dataset.zip" out_dir = "breast_dataset" with zipfile.ZipFile(zip_path) as zf: # 解压前检查成员名,防 zip slip for name in zf.namelist(): if name.startswith("/") or ".." in name: raise SystemExit(f"危险路径: {name}") zf.extractall(out_dir)extractall之前手动遍历成员名检查路径穿越,这是很多教程会跳过但实际该做的一步。恶意 zip 可以在成员名里写../逃逸到目标目录之外,医学影像数据集虽然少见这种攻击,但养成习惯没坏处。Python 解压的缺点是大文件解压速度一般,优点是跨平台一致、可编程、便于在解压后直接接统计逻辑。
3. 从 zip 到可用数据:读懂目录结构与标注组织的三个步骤
3.1 医学影像数据集的三种典型目录形态
解压完不要急着训练,先把目录当成文档读一遍。乳腺癌影像数据集的目录形态,常见的有三种:
| 形态 | 影像格式 | 标注格式 | 对应场景 |
|---|---|---|---|
| 扁平文件组 | PNG / JPG / BMP | 同名 TXT / XML / JSON | 经过预处理的公开数据集,直接能喂检测框架 |
| DICOM 分卷 + CSV | DICOM | CSV 记录坐标、类别、患者 ID | 医院或竞赛原始数据,需要二次解析 |
| 主目录分 train / val / test | PNG / JPEG | COCO JSON 或 YOLO TXT | 已经有人帮你划好数据集的成品包 |
如果展开后看到的是 DICOM 文件,先别慌,它和自然图像的处理方式不同。DICOM 不只是一个图像文件,它还内嵌了患者信息、成像参数、窗宽窗位等元数据,不能直接用cv2.imread读。反过来,如果是 PNG/JPG 加同名 TXT,那通常已经是 YOLO 风格或至少接近 YOLO 风格,转格式成本很低。
3.2 用一个 Python 脚本摸清楚压缩包内部全貌
我拿到解压结果后的第一个动作是统计扩展名分布和文件大小,几行代码就能避免后面踩「数据集里混进了 300 个空文件」这种坑。
from pathlib import Path import collections root = Path("breast_dataset") if not root.exists(): raise SystemExit("目录不存在,先确认解压路径") exts = collections.Counter() total_size = 0 file_count = 0 for p in root.rglob("*"): if p.is_file(): exts[p.suffix.lower()] += 1 file_count += 1 total_size += p.stat().st_size print("文件总数:", file_count) print("总大小: %.2f MB" % (total_size / 1024 / 1024)) print("扩展名分布:", dict(exts))rglob("*")递归列出所有子目录下的文件。suffix.lower()做小写归一,避免.PNG和.png被统计成两类。这个脚本的输出能回答三个问题:数据里有没有 DICOM 需要转码、有没有.DS_Store之类的垃圾文件、标注文件数量和图像文件数量是不是一个量级。
如果图像数量是 1000,标注文件只有 200,那基本可以确认标注文件丢失或命名不一致,别指望训练时能自动补齐。
3.3 把标注文件和类别定义对上号
统计完扩展名,下一步是检查标注配对。最常见的是同名配对,即img_001.png对应img_001.txt或img_001.json。
from pathlib import Path root = Path("breast_dataset") images = list(root.rglob("*.png")) + list(root.rglob("*.jpg")) missing = [] for img in images: # 优先找同名 .txt,找不到再找 .json txt = img.with_suffix(".txt") if not txt.exists(): missing.append(img) print(f"图像总数: {len(images)},缺同名 TXT: {len(missing)}") for m in missing[:20]: print(m)这个脚本只能检查同名文件是否缺失,没法判断内容是否匹配。更稳的做法是随机抽几张图像,把标注框画出来人工确认,这部分留到第 6 章专门讲。
另外要确认类别定义。公开的乳腺癌影像数据集中,类别可能是0: benign、1: malignant,也可能是0: normal、1: benign、2: malignant。如果压缩包里有classes.txt或labels.txt,直接打开看;如果没有,但标注文件是 JSON/CSV 结构,用第 3.2 节的脚本把标注内容抽样打印出来,找找类别字段。这一步不花时间,但漏了它就容易出现「训练完模型预测的良性恶性正好反了」的尴尬。
4. 把数据集喂给 YOLOv8:格式转换、目录划分与训练参数设置
4.1 DICOM 转 PNG:先做窗宽窗位映射,别直接保存像素数组
DICOM 转 PNG 看起来简单,但很多人直接取pixel_array就保存,结果图像一片黑或一片白。原因是 DICOM 的像素值不是 0-255 的 RGB,而是原始计量值,需要按窗宽窗位映射到可视范围,这就是所谓「窗宽窗位」处理。
import pydicom import numpy as np from PIL import Image ds = pydicom.dcmread("sample.dcm") arr = ds.pixel_array.astype(np.float32) # 从 DICOM 元数据读取窗宽窗位,部分数据集可能缺失 wl = getattr(ds, "WindowCenter", None) ww = getattr(ds, "WindowWidth", None) if wl is None or ww is None: wl, ww = np.percentile(arr, 50), np.percentile(arr, 95) - np.percentile(arr, 5) print("DICOM 缺少窗宽窗位,用百分位估算") # pydicom 返回的可能是 MultiValue,取第一个 if isinstance(wl, (list, tuple)): wl = wl[0] if isinstance(ww, (list, tuple)): ww = ww[0] low = float(wl) - float(ww) / 2.0 high = float(wl) + float(ww) / 2.0 arr = (arr - low) / (high - low) arr = np.clip(arr, 0, 1) Image.fromarray((arr * 255).astype(np.uint8)).save("sample.png")这里处理了两类脏数据:一是 DICOM 元数据里的WindowCenter和WindowWidth可能是多值列表,需要取第一个;二是部分文件的窗宽窗位字段缺失,用像素分布的百分位估算一个范围。乳腺 X 线图像经常是 12 位或 16 位深,直接按 8 位保存会把低对比度的病灶细节压没,这一步不能省。
DICOM 文件名通常是1.2.826.0.1.3680043.10000.1.000000.dcm这种 UID 风格,转 PNG 后文件名最好保留原 UID 前缀,方便后续和 CSV 标注里的image_id对齐。
4.2 坐标归一化:从绝对坐标到 YOLO 格式
YOLO 系列的标签格式是每行一个目标,格式为class_id cx cy w h,其中cx、cy是中心点相对图像宽高的比例,w、h是宽高占比。如果数据集原始标注是 COCO 风格的[x1, y1, w, h]或 VOC 风格的[x1, y1, x2, y2],都要转换。
import numpy as np def voc_to_yolo(x1, y1, x2, y2, img_w, img_h): """ 将 VOC 格式的绝对坐标 (左上角, 右下角) 转为 YOLO 归一化坐标 """ dw = 1.0 / img_w dh = 1.0 / img_h cx = (x1 + x2) / 2.0 * dw cy = (y1 + y2) / 2.0 * dh w = (x2 - x1) * dw h = (y2 - y1) * dh # 防止标注越界 cx = min(max(cx, 0.0), 1.0) cy = min(max(cy, 0.0), 1.0) w = min(w, 1.0) h = min(h, 1.0) return cx, cy, w, h我这里顺手做了坐标裁剪,中心点被限制在 0 到 1 之间。注意min(max(cx, 0.0), 1.0)这种写法只能让中心点不越界,如果边界框本身超出图像外,宽高算出来仍然会超过 1,遇到这种情况应该跳过该样本并打印警告,而不是强制裁剪后交给网络训练。
4.3 划分 train / val 并配置 dataset.yaml
划分时有个容易忽略的细节:医学影像数据集经常有多张图像来自同一位患者,如果随机划分,同一个患者的一小部分图像会同时出现在 train 和 val 里,导致验证指标虚高。理想做法是按患者 ID 分组后划分,但很多数据集没有提供患者 ID 字段,只能退而求其次用文件名前缀分组。
import random from pathlib import Path random.seed(42) images = list(Path("images").glob("*.png")) random.shuffle(images) val_ratio = 0.2 n_val = int(len(images) * val_ratio) val_images = images[:n_val] train_images = images[n_val:] for split, imgs in [("train", train_images), ("val", val_images)]: img_dir = Path(f"breast_yolo/images/{split}") lbl_dir = Path(f"breast_yolo/labels/{split}") img_dir.mkdir(parents=True, exist_ok=True) lbl_dir.mkdir(parents=True, exist_ok=True) for img in imgs: # 用硬链接而不是复制,省磁盘 (img_dir / img.name).symlink_to(img.resolve()) label_src = Path("labels") / (img.stem + ".txt") if label_src.exists(): (lbl_dir / (img.stem + ".txt")).symlink_to(label_src.resolve()) else: print(f"警告: {img} 缺少对应标签")我用的是符号链接,代替文件复制。数据集几 GB、几十 GB 的时候,复制一份意味着磁盘翻倍,符号链接只占一个 inode,训练代码读取时无感知。如果你的数据集要跨服务器传输,那就不能传链接,得用cp或rsync实际拷贝。
接下来写dataset.yaml,这是 YOLOv8 的数据集描述文件:
path: ./breast_yolo train: images/train val: images/val names: 0: benign 1: malignantpath是相对 yaml 文件的根目录,train和val是相对path的路径。names是一个有序字典,类别顺序必须和标签文件里的class_id完全一致,否则训练不报错,但语义全反。
4.4 训练命令里值得动手改的四个选项
处理好数据集到dataset.yaml之后,就可以用 YOLOv8 跑起来了。训练命令本身不长:
yolo detect train data=breast_yolo.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 amp=True换成更稳的yolov8s.pt或yolov8m.pt只改model即可。第一次跑别追求精度,先用yolov8n把流程跑通,重点观察四个参数:
imgsz决定输入分辨率。乳腺癌病灶有些很小,乳腺 X 线里几个像素的钙化点也算目标,imgsz=640是速度和精度的平衡点;如果 GPU 显存够,imgsz=1024对小病灶更友好。
epochs在医学小数据集上不必迷信大数值。几百张图训练 300 epoch 很可能做过拟合,先设 100,看到 val loss 回升就提前停。
batch受显存限制,16 不行就 8,显存不够优先降 batch 而不是降 imgsz,因为小目标对分辨率更敏感。
amp混合精度训练在 A100、4090 等新卡上能省显存,但如果训练 loss 早期出现 NaN,第一件事就是关掉 amp 试一次。
5. 训练前的避坑清单:五个让数据集白下载的高频翻车点
5.1 解压报 CRC 错误,另一个软件却能正常打开
现象:用unzip -t检查时报CRC failed,但用 7-Zip 手动解压能解出大部分文件,且图像看起来正常。
原因:zip 中央目录记录的 CRC32 与实际文件数据不一致,常见于跨平台传输时二进制模式被篡改,或者上传下载过程中文件被拼接了额外字节。另一个常见场景是部分下载工具创建了「不完整文件」,只是把扩展名改成了 zip。
解决:先用7z t定位是哪几个文件 CRC 出错,单独重新下载;小文件出错可以用zip -FF breast.zip --out fixed.zip修复,它能重新扫描压缩数据重建中央目录。修复后再转成 YOLO 格式训练前,务必对用过该包的所有中间产物重新生成一遍,不要复用之前转换输出的 PNG。
5.2 DICOM 图像全黑或全白
现象:用 pydicom 打开后保存的 PNG 一片黑,或者整张图白到看不见结构。
原因:DICOM 像素值是带量纲的数据,不是 8 位灰度。最常见的情况是没做窗宽窗位映射,直接把 16 位整数数组缩放到 0-255,导致大部分像素被压缩到很小范围。另一个原因是数据本身是 MONOCHROME1(白底黑字)格式,像素值方向相反。
解决:按第 4.1 节的逻辑做窗宽窗位映射,保存前检查ds.PhotometricInterpretation,如果值是MONOCHROME1,对像素数组取反再保存。这类细节只看预览图很难发现,建议把转出来的 PNG 直接放进标注可视化脚本里画框验证。
5.3 标注框越界导致训练 Loss 异常
现象:训练开始后 loss 快速下降但 mAP 长期为 0,或者 loss 出现 NaN。
原因:标注文件里的边界框值大于 1 或小于 0,常见于坐标归一化时用错了分母。有的数据集标注的坐标是相对坐标但是绝对像素值,有的把x1, y1, x2, y2顺序混成了x1, x2, y1, y2,直接套标准转换公式得到负宽度、负高度。
解决:在训练前统一做一次越界检查。扫描所有 txt 标签,打印出任何中心点不在 0-1 或宽高不在 0-1 的行,逐个核对原始标注。不要写脚本直接裁剪,宁可手动修正几十条,也别让脏标注参与训练;在医学影像场景里,一条越界标注可能意味着某个病灶的边界信息本来就是错的。
5.4 路径带中文或空格,OpenCV 无声失败
现象:代码里cv2.imread("D:/乳腺癌数据/图像 001.png")返回None,但文件确实存在。
原因:OpenCV 在 Windows 上读取含中文或空格路径时依赖系统编码,Python 字符串传入后经常无法正确转换,imread失败后不抛异常,只返回空对象。后续代码访问img.shape就报 AttributeError。
解决:两个方向。一是数据集目录一律用英文字母、下划线、数字,解压时就创建breast_data这类目录;二是在读图环节改成cv2.imdecode绕开系统路径编码问题:
import cv2 import numpy as np def imread_unicode(path): data = np.fromfile(path, dtype=np.uint8) img = cv2.imdecode(data, cv2.IMREAD_COLOR) if img is None: raise ValueError(f"无法读取图像: {path}") return imgnp.fromfile按二进制读入,cv2.imdecode从内存解码,完全绕开文件名编码。保存图像时用cv2.imencode配合tofile同理。这算是我个人的血泪经验,部署到 Windows 服务器时早晚碰上。
5.5 类别 ID 错位:良性恶性标反了
现象:训练完成后 val 精度看着不错,但抽查预测结果,模型把每个恶性肿瘤区域都标成了良性。
原因:数据集里的classes.txt顺序和标签文件里的class_id不一致。比如压缩包里classes.txt写的是malignant\nbenign,而某个标注文件里0实际代表良性。YOLO 训练不检查类别名,只按整数匹配dataset.yaml里的names列表,所以names顺序写错时训练照常进行,损失照常下降,但语义整个错位。
解决:训练前把标注文件里的所有class_id统计出来,和classes.txt逐条对照:
cut -d' ' -f1 labels/train/*.txt | sort | uniq -ccut -d' ' -f1提取每行第一个字段,sort | uniq -c统计每个类别出现次数。输出应当是连续且与classes.txt对应的整数。只要看到类别 ID 中出现的最大值大于classes.txt行数减一,或者出现负数,就不要开始训练。
6. 用最小脚本做训练前体检:一张图一张框地确认标注
6.1 全量画框回放:把标注变成能看的图
格式转换、目录划分做完之后,我习惯跑一次全量可视化,把每张图的标注框画出来,输出到另一个目录。这个方法比看任何统计数字都直观,标注错位、框太小、类别颜色不对,一眼就能看出来。
import cv2 from pathlib import Path img_dir = Path("breast_yolo/images/train") lbl_dir = Path("breast_yolo/labels/train") out_dir = Path("breast_yolo/verify") out_dir.mkdir(exist_ok=True) # 类别对应颜色,绿=良性,红=恶性 colors = {0: (0, 255, 0), 1: (0, 0, 255)} for txt in sorted(lbl_dir.glob("*.txt")): img_path = img_dir / (txt.stem + ".jpg") if not img_path.exists(): img_path = img_dir / (txt.stem + ".png") img = cv2.imread(str(img_path)) if img is None: print(f"跳过无法读取的图像: {img_path}") continue h, w = img.shape[:2] for line in txt.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: print(f"格式异常: {txt} -> {line}") continue cid, cx, cy, bw, bh = map(float, parts) cid = int(cid) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) color = colors.get(cid, (255, 255, 0)) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, str(cid), (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(str(out_dir / (txt.stem + ".jpg")), img)这个脚本里txt.stem是标签文件名去掉后缀的部分,图像文件从同名 JPG 查起,不存在再试 PNG。cv2.putText在框的左上角标注类别 ID,方便肉眼核对。脚本输出目录里出现的图像,每一张都应该满足:框在图像内、框的大小符合病灶尺度、颜色与预期类别一致。
6.2 从体检报告到训练前的放行条件
画框回放适合抽查,但全量检查几百上千张图太累。我建议再加一个量化体检脚本,输出几项硬指标:
| 体检项 | 检查方法 | 异常阈值 | 处理动作 |
|---|---|---|---|
| 标签行数 | 统计每行字段数 | 不等于 5 | 打印文件路径,人工核对 |
| 坐标范围 | 检查 cx/cy/bw/bh 是否 0-1 | 任一越界 | 从源标注重新转换 |
| 边界框面积 | 计算 bw*bh 相对整图占比 | 小于 0.0001 | 视为噪声或丢失目标,复核直方图 |
| 类别 ID | 统计出现过的 ID 集合 | 非连续或超范围 | 重新映射类别定义 |
| 类别不均衡 | 统计各类别目标数 | 比例大于 20:1 | 考虑过采样或修改损失权重 |
import numpy as np from pathlib import Path lbl_dir = Path("breast_yolo/labels/train") all_ratios = [] all_counts = [] class_counter = {} for txt in lbl_dir.glob("*.txt"): lines = txt.read_text().strip().splitlines() all_counts.append(len(lines)) for line in lines: parts = line.split() if len(parts) != 5: print(f"异常行: {txt}: {line}") continue cid, cx, cy, bw, bh = map(float, parts) class_counter[int(cid)] = class_counter.get(int(cid), 0) + 1 if not (0 <= cx <= 1) or not (0 <= cy <= 1): print(f"中心点越界: {txt}: {line}") if bw <= 0 or bh <= 0: print(f"非正宽高: {txt}: {line}") all_ratios.append(bw * bh) print("类别分布:", dict(sorted(class_counter.items()))) print("每图目标数 中位数:", np.median(all_counts)) print("框面积占比 中位数:", np.median(all_ratios)) print("框面积占比 1% 分位:", np.percentile(all_ratios, 1))这个脚本不会告诉你数据集「能不能用」,但能告诉你「该不该现在就用」。框面积占比低于 0.01% 的目标在imgsz=640下只占十几个像素,训练几乎学不到特征,这时要把imgsz调大或在预处理阶段裁剪 ROI。
我自己做医学影像检测项目时,已经把画框回放和量化体检固定成训练前唯一必跑的两个脚本,跑完这两步再决定改参数还是换模型。很多训练翻车不是模型的问题,是数据进管线之前没人看。希望帮到你。
本文还有配套的精品资源,点击获取