小样本单类印章检测实战:210张图从数据校验到YOLOv8训练调优
2026/9/23 1:10:32 网站建设 项目流程

简介:这份印章检测数据集面向计算机视觉入门与目标检测实践者,尤其适合需要快速验证印章识别方案的学生、算法工程师及科研人员。数据集同时提供Pascal VOC与YOLO两种标注格式,包含jpg原图、对应xml文件与yolo格式txt文件,可直接接入主流检测框架训练,省去格式转换环节。压缩包共632个文件,以210张jpg图片、210个xml标注和212个txt标签为主,整体约36.47MB,体积轻量便于本地调试与迁移。标注类别仅seal一类,共488个矩形框,使用labelImg完成画框,标注准确合理,适合作为单类别检测的基线数据。目前已有918人学习下载,可作为印章检测模型训练、数据增强实验或教学演示的实用素材,帮助读者快速搭建训练流程并验证检测效果。

1. 210 张印章图、488 个框:小样本单类检测数据集怎么用才不翻车

拿到一个只有 210 张图、1 个类别、488 个标注框的印章检测数据集,第一反应往往是「这么点数据能训出什么」。但印章检测本身就是个典型的单类、强纹理、背景相对固定的任务,210 张精标的图在迁移学习加持下完全够跑出一个能用的基线。这个数据集同时给了 Pascal VOC 的 xml 和 YOLO 的 txt 两套标注,图片命名是firc_yz_176.jpg这种带序号的风格,标注工具是 labelImg,类别只有一个seal,平均每张图 2.3 个框。它适合两类人:一是想跑通 YOLO 训练全流程但不想花时间标数据的新手,二是需要快速验证印章检测方案可行性的工程同学。真正决定成败的不是图片数量,而是你怎么切分、怎么配增强、怎么读标注。

2. VOC 与 YOLO 双格式标注的差异与转换校验

2.1 两套标注到底差在哪

VOC 格式的 xml 存的是绝对像素坐标,结构是<bndbox>下的xmin/ymin/xmax/ymax,还带filenamesizeobject等节点。YOLO 的 txt 存的是归一化后的中心点加宽高,一行一个目标,格式是class_id cx cy w h,五个值全部除以图片宽高落在 0 到 1 之间。这个数据集两套都给全了,省掉了自己写转换脚本的麻烦,但正因为是两套独立文件,必须先校验它们是否一一对应,否则训练时会出现「图有标注但框错位」这种最难查的问题。

常见做法是先做文件名集合比对,再做框数量比对。下面这段脚本一次性把两个问题都查了:

import os import xml.etree.ElementTree as ET img_dir = "images" # jpg 目录 xml_dir = "annotations" # VOC xml 目录 txt_dir = "labels" # YOLO txt 目录 imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")} xmls = {os.path.splitext(f)[0] for f in os.listdir(xml_dir) if f.endswith(".xml")} txts = {os.path.splitext(f)[0] for f in os.listdir(txt_dir) if f.endswith(".txt")} # 1. 三份文件名是否完全一致 print("仅图片有:", imgs - xmls, imgs - txts) print("仅xml有:", xmls - imgs) print("仅txt有:", txts - imgs) # 2. 逐张比对框数量 mismatch = [] for name in sorted(imgs & xmls & txts): tree = ET.parse(os.path.join(xml_dir, name + ".xml")) n_xml = len(tree.findall("object")) with open(os.path.join(txt_dir, name + ".txt")) as f: n_txt = len([l for l in f if l.strip()]) if n_xml != n_txt: mismatch.append((name, n_xml, n_txt)) print("框数不一致:", mismatch)

逻辑上先做集合差集,能立刻暴露漏标、错命名、扩展名大小写这类问题;再做逐张框数比对,能抓到转换脚本丢框的情况。参数上img_dirxml_dirtxt_dir按你解压后的实际目录改,如果数据集把 jpg 和 xml 混在一个文件夹里,把三个路径指向同一目录即可,脚本靠扩展名区分。

2.2 归一化坐标的边界坑

YOLO 的归一化值理论上必须在 0 到 1 之间,但 labelImg 导出时如果框贴到图片边缘,偶尔会出现xmax等于图片宽度导致cx + w/2略微超过 1 的情况。训练框架大多会 clip,但个别版本会直接报错或静默丢弃该框。校验时顺手加一道范围检查:

bad = [] for name in txts: with open(os.path.join(txt_dir, name + ".txt")) as f: for i, line in enumerate(f): vals = list(map(float, line.split()[1:])) if any(v < 0 or v > 1 for v in vals): bad.append((name, i, vals)) print("越界框:", bad[:10], "共", len(bad))

发现越界不要慌,绝大多数是浮点误差级别的超出,手动 clip 到[0,1]即可;如果超出幅度很大,说明转换时用错了图片尺寸,得回去核对 xml 里的size节点和实际 jpg 分辨率是否一致。

2.3 类别映射表要固定

这个数据集只有seal一类,YOLO 的class_id就是 0。但如果你后续要合并其他印章数据集,务必先定好统一的类别顺序表,写进data.yamlnames里,别让不同来源的数据各用各的 id。单类任务里这个坑不明显,一旦扩类就是灾难。

格式坐标类型存储单位类别表示典型文件
VOC左上右下绝对像素字符串名称firc_yz_176.xml
YOLO中心宽高归一化 0-1整数 idfirc_yz_176.txt

3. 用 YOLOv8 跑通印章检测训练全流程

3.1 目录结构与 data.yaml

YOLOv8 对目录结构有约定,最省事的组织方式如下:

seal_dataset/ ├── images/ │ ├── train/ # 约 168 张 │ └── val/ # 约 42 张 ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

按 8:2 切分,210 张里训练 168、验证 42。切分脚本要保证图片和同名 txt 一起移动,别只挪图片:

import os, random, shutil random.seed(42) src_img, src_lbl = "images", "labels" for split in ["train", "val"]: os.makedirs(f"seal_dataset/images/{split}", exist_ok=True) os.makedirs(f"seal_dataset/labels/{split}", exist_ok=True) names = [os.path.splitext(f)[0] for f in os.listdir(src_img) if f.endswith(".jpg")] random.shuffle(names) cut = int(len(names) * 0.8) for i, n in enumerate(names): split = "train" if i < cut else "val" shutil.copy(f"{src_img}/{n}.jpg", f"seal_dataset/images/{split}/{n}.jpg") shutil.copy(f"{src_lbl}/{n}.txt", f"seal_dataset/labels/{split}/{n}.txt")

random.seed(42)固定随机种子,保证每次切分结果一致,方便复现实验。cut控制训练集比例,210 张这种小数据集建议验证集别低于 15%,否则指标抖动会很大。

data.yaml内容:

path: ./seal_dataset train: images/train val: images/val nc: 1 names: ["seal"]

nc是类别数,单类写 1;names顺序必须和 txt 里的class_id对应,这里只有 0 号对应seal

3.2 训练命令与关键参数

环境装好后一条命令启动:

yolo detect train \ data=seal_dataset/data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=30 \ project=runs/seal \ name=exp1

model=yolov8n.pt用官方预训练权重做迁移,小数据集千万别从零训,210 张图从零训基本学不出东西。epochs=150配合patience=30,30 轮验证指标不涨就早停,避免过拟合。imgsz=640是 YOLOv8 的默认输入尺寸,印章通常占图比例不小,640 够用;如果印章很小可以提到 960,但显存和速度要重新权衡。batch=16在 8G 显存上跑 640 尺寸比较稳,显存不够就降到 8 并同步把lr0调小到 0.005 左右。

3.3 小样本下的增强策略

210 张图最容易过拟合,YOLOv8 默认开了 mosaic、HSV 抖动、随机翻转。印章检测有个特殊性:印章文字方向有语义,上下翻转(flipud)会把文字倒过来,反而制造噪声。建议关掉垂直翻转,保留水平翻转和轻微旋转:

yolo detect train \ data=seal_dataset/data.yaml \ model=yolov8n.pt \ epochs=150 imgsz=640 batch=16 \ fliplr=0.5 flipud=0.0 \ degrees=10.0 translate=0.1 scale=0.3 \ mosaic=1.0 close_mosaic=20

flipud=0.0关掉垂直翻转,degrees=10.0允许小角度旋转模拟拍摄倾斜,close_mosaic=20表示最后 20 轮关闭 mosaic,让模型在接近真实分布的图上收敛。这几个参数是小样本印章任务里我一般会调的,默认值直接跑往往验证集 mAP 上不去。

提示:训练前先用yolo detect train ... epochs=1跑一轮,确认数据能正常加载、没有报「no labels found」,比训到一半才发现路径错要省事得多。

4. 训练结果解读与常见报错排查

4.1 看哪些指标、怎么判断过拟合

训练完在runs/seal/exp1/下有results.csv和一堆曲线图。重点看三个:metrics/mAP50metrics/mAP50-95train/box_lossval/box_loss的走势。单类印章任务,mAP50 能到 0.9 以上算正常,如果只有 0.6 左右,先怀疑标注质量而不是模型。判断过拟合看两条 loss 曲线:训练 loss 持续下降但验证 loss 在某个 epoch 后开始上升,就是过拟合信号,此时要么加增强要么减 epoch。

# 快速看最后 10 轮的指标 tail -n 10 runs/seal/exp1/results.csv

4.2 典型报错对照

报错信息原因处理
No labels foundtxt 路径或命名不匹配检查 labels 目录与 images 同名同层级
class label 1 exceeds nc=1txt 里出现 id 1单类只能是 0,回去查转换脚本
Image Not Founddata.yaml 的 path 写错用绝对路径或确认相对基准目录
验证 mAP 为 0验证集 txt 为空或全越界重跑第 2 章的校验脚本

4.3 推理验证

训完拿几张没参与训练的图测一下,确认框的位置和置信度:

yolo detect predict \ model=runs/seal/exp1/weights/best.pt \ source=test_imgs/ \ conf=0.25 \ save=True

conf=0.25是置信度门限,印章检测误检通常不多,可以适当调高到 0.4 减少虚警;如果漏检多就降到 0.15。save=True会把画框结果存到runs/detect/predict/,肉眼过一遍比看数字直观。

5. 从 210 张到可用模型:扩样与置信度调优技巧

210 张的天花板是明显的,想再往上走有两条路。第一条是扩样:把训练好的模型拿去推理未标注的印章图,挑置信度高的预测框转成 YOLO txt 做伪标签,人工复核后并入训练集,这是小数据集半自动扩样的标准玩法。伪标签脚本核心就一句model.predict后把boxes.xywhn按行写文件,但一定要人工过一遍,印章检测里模型把红色圆形 logo 误判成印章的情况不少。

第二条是置信度门限的精细调优。别用默认 0.25 一把梭,而是画一条 PR 曲线找拐点:

from ultralytics import YOLO model = YOLO("runs/seal/exp1/weights/best.pt") metrics = model.val(data="seal_dataset/data.yaml", conf=0.001, iou=0.6) # metrics.box 里含 precision/recall 曲线数据,导出后找 F1 最大点 p, r = metrics.box.p, metrics.box.r f1 = [2 * pi * ri / (pi + ri + 1e-9) for pi, ri in zip(p, r)] best_conf = metrics.box.ap_class_index # 结合曲线数据定位 print("F1 峰值附近对应门限:", max(f1))

conf=0.001是为了让验证时输出足够多的点来画完整曲线,实际部署再取 F1 峰值对应的门限。印章检测对漏检比对误检敏感的场景(比如票据审核),门限往低取保召回;对误检敏感的场景(比如自动盖章计数),门限往高取保精度。这个取舍没有标准答案,取决于你的下游业务怎么用这些框。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询