
简介面向机械视觉、目标检测方向的开发者与工业质检从业者这份螺丝螺母目标检测数据集提供423张已标注的零件图像可直接用于训练机械零件识别模型也可作为工业场景检测方案的验证数据。压缩包共428个文件以423张jpg原图为主体附带txt标注文件用于目标框标签读取同时提供1个label_list标签对照说明和1个Python数据增强脚本可灵活扩充训练样本。整个资源包约82.69MB结构简单易用适合快速搭建螺丝螺母检测Demo或补充生产环境下的零部件识别样本。当前已有606人学习下载尤其适合目标检测入门者、机械自动化项目开发者以及需要扩充工业数据集的研究人员。通过数据增强脚本可针对旋转、亮度、裁剪等场景批量生成变体图像帮助提升模型在不同光照与位姿下的泛化能力是一份兼顾数据与工具的开箱型资源。1. 机械零件识别检测为什么先拿螺丝螺母数据集练手在工厂产线视觉检测里螺丝和螺母从来不是“简单目标”它们数量密集、尺寸小、表面反光、背景杂乱是典型的小目标检测场景。这个机械零件识别检测数据集提供了423张带标注文件的图片数量不大但配合附带的增强脚本正好把“标注文件怎么读、增强怎么做、YOLOv8怎么训练自己的数据集”这条线完整跑通。哪怕你之前没跑过目标检测项目这个规模也不会让你耗在几万张图的清洗上半天就能出第一个可用版本。它适合第一次自训检测模型的从业者和学生尤其适合想快速判断“机械零件识别检测”值不值得投入的人——先用这套小数据集验证流程再决定是否扩展产线数据。2. 先别急着训练拆开zip看懂目录和标注格式再动手很多人拿到压缩包的第一动作是解压后直接跑训练这通常会在数据读取阶段翻车。螺丝螺母数据集的常见组织方式分成两类一类是YOLO惯用的images/和labels/两个平级目录另一类是VOC风格的JPEGImages/和Annotations/。前者每张图对应一个同名txt后者对应一个xml。先把zip解压到全英文路径比如C:/datasets/nut_screw或/home/user/datasets/nut_screw这个习惯能省掉后面大量读图报错的麻烦。2.1 解压后先做三件事目录对应、图片损坏、坐标越界解压完不要急着划分数据集。我一般会先写一个检查脚本把三件最容易隐藏的问题暴露出来图片能不能被OpenCV完整读取、标注txt是否与图片一一对应、标注坐标是否全部合法。这三个问题如果不提前清理后面跑增强脚本时会被放大十倍。import cv2 from pathlib import Path img_dir Path(datasets/nut_screw/images) lbl_dir Path(datasets/nut_screw/labels) for img_path in sorted(img_dir.iterdir()): if img_path.suffix.lower() not in (.jpg, .png, .jpeg, .bmp): continue img cv2.imread(str(img_path)) if img is None: print(f图片读取失败: {img_path}) continue lbl_path lbl_dir / (img_path.stem .txt) if not lbl_path.exists(): print(f缺失标注文件: {lbl_path}) continue for line in lbl_path.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: print(f标注行格式错误 {lbl_path}: {line}) continue cid, cx, cy, w, h map(float, parts) if not (0 cx 1 and 0 cy 1 and w 0 and h 0): print(f坐标越界 {lbl_path}: {line}) print(检查完成)这段脚本的逻辑很简单逐张读图、逐行解析标注。cv2.imread返回None一般说明图片损坏、路径编码有问题或者文件被压缩软件提前截断缺失标注文件说明数据整理时丢了配对文件坐标越界则意味着标注工具导出参数有误。任何一个问题都要在进入训练前处理掉否则脏数据会被增强脚本复制成几十份。2.2 YOLO标注文件里的一行五列数字到底在说什么打开任意一个txt每一行都是五个数字像这样0 0.523 0.411 0.064 0.037第一个数字是类别ID后面的四个数字分别是归一化后的框中心x、中心y、框宽、框高。注意这四个值全部是相对图片宽高的比例取值在0到1之间。0.064乘上原图宽度才是真实的框宽如果原图宽度是640像素这个框大约41像素宽属于不折不扣的小目标。有些数据集打包时用的是VOC xml格式那就得先做一次转换解析xml里的xmin/ymin/xmax/ymax绝对值坐标再除以图片宽高得到归一化值。常见做法是用现成脚本批量转但转完必须抽样检查因为我见过不少转换脚本把ymin和ymax的公式写反导致框全部上下颠倒。接下来统计一下整个数据集的目标尺寸分布这个结果直接决定后面的训练参数。import numpy as np from pathlib import Path sizes [] for lbl in Path(datasets/nut_screw/labels).glob(*.txt): text lbl.read_text().strip() if not text: continue for line in text.splitlines(): parts line.split() if len(parts) ! 5: continue _, _, _, w, h map(float, parts) sizes.append((w, h)) arr np.array(sizes) print(平均框宽: {:.3f} ({:.0f}px 640).format(arr[:, 0].mean(), arr[:, 0].mean() * 640)) print(平均框高: {:.3f} ({:.0f}px 640).format(arr[:, 1].mean(), arr[:, 1].mean() * 640))绝大多数螺丝螺母数据集的平均框尺寸集中在0.03到0.08之间换算到640分辨率就是20到50像素。这个数字是后面选imgsz的关键依据平均框不到40像素640的训练尺寸大概率会漏检需要把输入分辨率提到1280来补偿。2.3 423张怎么分配才不翻车训练、验证、测试的划分逻辑423张的合理划分一般是8:1:1也就是约338张训练、42张验证、43张测试。不要手动拖文件写个带固定随机种子的脚本更可靠。这里最容易踩的坑是“同源连拍”工业采集经常对同一个螺丝阵列连拍多张这几张图高度相似如果被随意打散到训练集和验证集验证集mAP会虚高到失真因为模型等于在考试里遇见了原题。import random, shutil from pathlib import Path src_imgs sorted(Path(datasets/nut_screw/images).glob(*.jpg)) random.seed(42) random.shuffle(src_imgs) n len(src_imgs) train_imgs src_imgs[:int(n * 0.8)] val_imgs src_imgs[int(n * 0.8):int(n * 0.9)] test_imgs src_imgs[int(n * 0.9):] for split, imgs in [(train, train_imgs), (val, val_imgs), (test, test_imgs)]: out_img Path(datasets/nut_screw/split) / split / images out_lbl Path(datasets/nut_screw/split) / split / labels out_img.mkdir(parentsTrue, exist_okTrue) out_lbl.mkdir(parentsTrue, exist_okTrue) for img in imgs: shutil.copy(img, out_img / img.name) lbl Path(datasets/nut_screw/labels) / (img.stem .txt) if lbl.exists(): shutil.copy(lbl, out_lbl / lbl.name)复制到新目录而不是移动原文件是为了保留一份原始数据做后悔药。后面增强脚本跑坏了、划分逻辑想重调都还有退路。强调一点随机数种子必须固定否则每次跑出来的划分结果都不一样复现性无从谈起。连拍图的分组逻辑要根据文件名规则或采集时间来定把同一连拍序列放进同一个split而不是逐张随机。3. 数据增强脚本把423张撑成可训练的几千张同时别让标注框飞了数据集规模小增强是绕不开的。但增强的核心不是“把图变多”而是让图片和标注一起同步变换让模型看过更多样化的形态。纯手动旋转图片、手工改坐标的做法在小数据集上几乎必错。我一般直接用Albumentations它内置了bbox同步变换逻辑能省掉最容易被写错的那部分。3.1 增强脚本为什么必须图片和标注一起变很多第一次做增强的人只对图片做旋转跑训练时发现loss曲线能正常下降验证集mAP却惨不忍睹。原因就是图片转了90度标注框还停在原地模型看到的特征和标签对不上学到了完全冲突的信息。用Albumentations这类库最核心的价值在于BboxParams会把旋转、缩放、翻转同步作用到框上。下面这段代码可以直接抄实现每张图增强出2张新图的流程。import cv2 import albumentations as A from pathlib import Path label_map {screw: 0, nut: 1} # 按标注文件里的类别顺序写 def read_label(lbl_path): boxes, cls_ids [], [] text lbl_path.read_text().strip() if not text: return boxes, cls_ids for line in text.splitlines(): parts line.split() if len(parts) ! 5: continue cid, cx, cy, w, h map(float, parts) boxes.append([cx, cy, w, h]) cls_ids.append(int(cid)) return boxes, cls_ids def write_label(lbl_path, boxes, cls_ids): with lbl_path.open(w) as f: for box, cid in zip(boxes, cls_ids): x, y, w, h box f.write(f{cid} {x:.5f} {y:.5f} {w:.5f} {h:.5f}\n) transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.5), A.GaussNoise(var_limit(10.0, 40.0), p0.3), A.ShiftScaleRotate( shift_limit0.05, scale_limit0.05, rotate_limit15, border_modecv2.BORDER_REPLICATE, p0.5 ), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_ids])) img_dir Path(datasets/nut_screw/train/images) lbl_dir Path(datasets/nut_screw/train/labels) out_img_dir Path(datasets/nut_screw/aug/train/images) out_lbl_dir Path(datasets/nut_screw/aug/train/labels) out_img_dir.mkdir(parentsTrue, exist_okTrue) out_lbl_dir.mkdir(parentsTrue, exist_okTrue) for img_path in sorted(img_dir.iterdir()): img cv2.imread(str(img_path)) if img is None: continue boxes, cls_ids read_label(lbl_dir / (img_path.stem .txt)) for i in range(2): # 每张原图增强出2张 aug transform(imageimg, bboxesboxes, class_idscls_ids) out_img_path out_img_dir / f{img_path.stem}_aug{i}_{img_path.name} out_lbl_path out_lbl_dir / f{img_path.stem}_aug{i}.txt cv2.imwrite(str(out_img_path), aug[image]) write_label(out_lbl_path, aug[bboxes], aug[class_ids])这段代码有两个关键点。第一BboxParams(formatyolo)必须与标注格式一致如果误写成pascal_vocAlbumentations会把坐标当绝对值解析所有框的位置全部错乱。第二ShiftScaleRotate里的rotate_limit我只给了15度没有给常见的30度或45度——螺丝是轴向零件转过太大角度后形态特征被扭曲模型学到的是模糊的旋转噪声而不是零件本身。scale_limit也收紧到0.05因为对平均只有30到40像素宽的小目标来说缩放超过0.1就会把目标压到几个像素特征直接丢失。3.2 针对螺丝螺母小目标的增强参数表逐项怎么选把这套参数展开成一张对照表方便你根据自己手里的数据改增强操作参数建议建议度原因HorizontalFlipp0.5建议水平翻转不改变螺丝螺母的类别语义VerticalFlipp0.5视情况类别里有左右旋差异或方向性特征时关掉RandomBrightnessContrastbrightness_limit0.2, contrast_limit0.2建议工业现场光照不均匀模拟过曝和阴影GaussNoisevar_limit(10.0, 40.0)建议模拟不同ISO下的传感器噪声ShiftScaleRotaterotate_limit15, scale_limit0.05建议但收紧小目标经不起大角度旋转和明显缩放RandomRain / RandomSnow无不建议工业场景不需要天气退化加了只会引入无关纹理HSV微调hue_limit10, sat_limit15视情况金属表面颜色差异小hue调过头会改变材质观感一个常见误解是增强越多模型越强。对几百张的小数据集来说增强到2到3倍确实能压过拟合但无节制增强会让模型学到“模糊”而不是“零件”。我的习惯是先增强成2倍左右跑一轮看验证集效果mAP偏低时再提高倍率而不是一上来就翻10倍。3.3 增强后必须做一次画框抽查不然就进了黑匣子增强脚本跑完直接进训练是新手最容易犯的错。增强过程的bug不会报错只会静默地生成坐标偏移的标注训练完效果不对的时候根本不知道问题出在增强还是模型。import cv2 from pathlib import Path def draw_yolo(img_path, lbl_path, save_path): img cv2.imread(str(img_path)) h, w img.shape[:2] if lbl_path.exists(): for line in lbl_path.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: continue cid, cx, cy, bw, bh map(float, parts) x1, y1 int((cx - bw / 2) * w), int((cy - bh / 2) * h) x2, y2 int((cx bw / 2) * w), int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cid)), (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(str(save_path), img) aug_img_dir Path(datasets/nut_screw/aug/train/images) aug_lbl_dir Path(datasets/nut_screw/aug/train/labels) check_dir Path(datasets/nut_screw/check) check_dir.mkdir(exist_okTrue) for i, img_path in enumerate(sorted(aug_img_dir.iterdir())): if img_path.suffix.lower() not in (.jpg, .png): continue lbl_path aug_lbl_dir / (img_path.stem .txt) draw_yolo(img_path, lbl_path, check_dir / img_path.name) if i 100: break print(抽查图已保存到 check 目录)抽查前100张增强结果重点看两类问题一是框的中心是否还在目标正中二是增强后的图片内容本身是否可辨识。我见过增强后50%以上的框都歪出目标的情况排查后定位到ShiftScaleRotate的rotate_limit30在小目标上产生了过大的旋转偏移收紧到15度后恢复正常。抽查100张不用花几分钟却能避免整个训练白跑。4. 用YOLOv8训练自己的数据集从数据集yaml到验证指标数据集准备完成接下来是训练环节。很多人的项目翻车不是模型不行而是数据yaml配置错了。YOLOv8和YOLOv11在数据组织方式上完全一致配置文件写法没有区别只是模型文件和预训练权重不同。4.1 数据集yaml类别名顺序一旦写错训练能跑但mAP全废进入训练前最重要的一步是写数据集yaml。不要直接改Ultralytics的COCO示例配置那会带出一堆无关类别干扰训练。标准写法如下# dataset.yaml path: datasets/nut_screw train: aug/train/images val: aug/val/images test: aug/test/images nc: 2 names: 0: screw 1: nutpath字段指向数据集根目录train和val可以写到images目录也可以只写到aug/train让框架自动找images和labels子目录。但我建议写到images这一级路径越明确越不容易把检查目录或其它杂项文件夹拉进训练。nc是类别数names的顺序必须严格对应标注txt里第一个数字的ID。最常见的错误是标注文件里0是screw、1是nutyaml里却写反或者names写成了[nut, screw]。这个错误有个迷惑性训练loss会正常下降模型也能收敛但验证阶段所有预测的类别ID和真实标签错位mAP永远算出来是0。这种问题一旦发生排查半天都找不到原因因为训练日志里的一切都非常正常。4.2 训练命令与关键参数工业小目标的imgsz、batch、epochs怎么定先装环境。用ultralytics的YOLOv8或YOLOv11安装命令一行就够了pip install ultralytics装完后确认显卡驱动和CUDA可用直接跑训练cd datasets/nut_screw yolo detect train \ datadataset.yaml \ modelyolov8n.pt \ imgsz640 \ epochs100 \ batch16 \ device0 \ patience30 \ projectruns/train \ namenutscrew_v8n参数逐个说清楚。modelyolov8n.pt是nano版本423张增强到900张左右的小数据集nano收敛最快、最不容易过拟合换成small或medium可以提升精度但训练时间成倍增加对工业小目标场景收益有限。imgsz640是起点前面统计的平均框尺寸如果小于40像素640下模型很难学足特征先用640跑基线再看测试集里的漏检情况决定是否提到1280——显存占用会翻倍12G显卡跑nano没问题small可能会爆显存改 batch8 再试。epochs100配合patience30的意思是验证集loss连续30轮不下降就提前停不用傻等100轮跑完。batch16在12G显存上跑nano很稳定8G显存建议降到8。4.3 训练日志里最该看的三条曲线和两个指标训练结束runs/train/nutscrew_v8n目录下会自动生成results.png和weights/best.pt。曲线判断可以对照这张表曲线 / 指标健康表现风险表现box_loss / cls_loss / dfl_loss稳步下降后趋平持续震荡不收敛val_box_loss随训练loss同步下降后期掉头向上过拟合信号mAP50快速上升后趋平高位震荡mAP50-95持续缓升和mAP50差距过大对螺丝螺母这类目标mAP50很容易刷到0.9以上因为目标轮廓清晰、类别区分度高。真正能反映框回归精度的指标是mAP50-95它要求预测框和真实框在IOU 0.5到0.95的多个档位上都足够准。第二个必看指标是Recall也就是漏检率。工业零件检测里漏掉一个螺丝的代价远高于多框一个背景所以如果Recall低于0.9不要急着用置信度阈值凑精度应该先回到增强脚本和数据集去补难例或者换更大的模型。提示val_box_loss拐头向上但训练loss还在降是过拟合的明确信号。小数据集上过拟合是常态不用慌用best.pt而不是last.pt做推理就行。5. 螺丝螺母数据集的避坑手册五次踩坑换来的血泪经验做工业小目标数据集问题几乎不会出现在模型结构上而是出现在数据、标注、增强这些环节里。下面五条是这类数据集最常见的坑每条都是我经历过的真实翻车现场。5.1 标注框太小增强缩放一压小目标直接变成几个像素现象训练完发现模型对大目标识别很好对远处的螺丝完全无感知测试图里小尺寸目标全部漏检。原因原始标注框平均只有30到40像素宽增强时scale_limit0.1会随机把框缩到更小叠加训练时的输入resize小目标可能只剩几个像素特征已经完全被压没。模型没见过有效的目标特征自然学不到。解决把增强里的scale_limit收紧到0.05以下或者干脆关掉随机缩放只保留平移和旋转。训练时把imgsz从640提到1280相当于把小目标放大后再喂给模型。另一个思路是用SAHI做切片推理把大图切成小块分别检测但这属于推理阶段的补救训练阶段的增强参数才是根本。5.2 类别名称和训练yaml不一致训练能跑但指标全是0现象训练loss正常下降验证集mAP、precision、recall全部为0无论怎么调整置信度阈值都无济于事。原因标注txt里类别ID是0和1yaml里names顺序却写反或者names里的类别名和标注文件的实际语义对不上。验证阶段模型输出的类别ID和GT永远错位模型不可能给出正确配对。解决训练前跑一个统计脚本核对类别ID的顺序。from collections import Counter from pathlib import Path ids Counter() for lbl in Path(datasets/nut_screw/aug/train/labels).glob(*.txt): text lbl.read_text().strip() if not text: continue for line in text.splitlines(): parts line.split() if len(parts) 5: ids[int(parts[0])] 1 print(ids)输出的字典直接对照yaml里的names顺序确保id0对应names[0]id1对应names[1]。这个脚本我现在已经固定成训练前必跑的一步几秒钟省掉半天排查时间。5.3 镜像翻转让左右旋螺丝的“身份”混淆现象数据集把左旋和右旋螺丝当作两个类别增强开了HorizontalFlip和VerticalFlip训练后模型对这两种螺丝的区分能力崩掉准确率直接降一半。原因左右旋螺丝在视觉上互为镜像。水平翻转以后左旋看起来就是右旋标注框位置虽然没有变但语义标签已经错了。模型忠实学习图片与标签的对应关系于是同时学到互相矛盾的特征类别区分能力归零。解决只要类别里包含方向性形态差异就必须关掉翻转类增强。保留旋转、亮度、噪声等不影响方向特征的增强。方向性样本的真实形态差异只能用实际拍摄数据补充翻转凑数靠不住。5.4 验证集混入同源连拍图mAP虚高到不真实现象训练时mAP很高验证集mAP也高但把模型部署到现场拍的新图上mAP瞬间大跌。原因工业采集时经常对同一阵列连拍多张这几张图高度相似。随机划分时它们被拆散到训练集和验证集验证结果约等于“开卷考试”。部署时遇到新采图光照和角度一变化模型就露馅。解决划分前先按文件名或采集时间排序把同一时间片、同一角度下的连拍图作为一个整体放进同一个split。宁可验证集小一点也要保证验证集图片与训练集图片没有“血缘关系”。5.5 zip解压后的中文路径让cv2.imread读到None现象解压后跑脚本cv2.imread返回None但文件确实存在资源管理器也能正常打开图片。原因文件名或路径包含中文字符OpenCV的imread在部分Windows版本上对非ASCII路径处理有兼容问题直接返回空对象用PIL读取却正常。这类问题不容易察觉因为脚本如果没有判空逻辑会在下一个环节报出一堆莫名其妙的错误。解决解压时就放到全英文路径比如C:/datasets/nut_screw不要放在桌面或中文目录下。解压后跑一遍2.1节的检查脚本确认所有图片都能被cv2.imread正确读取。如果文件已经在中文路径里把整个文件夹复制到英文路径再继续不要逐个改文件名。注意空txt文件是合法的表示该图没有目标。增强脚本里要跳过空标注的图片否则部分transform会对空列表报错中断整个增强流程。6. 最后一步把预测结果批量导成带框图片做人工复核训练完成、mAP数字满意之后我会再补一个步骤用best.pt对测试集做批量推理把所有预测结果画框导出人工扫一遍。这一步的目的是把模型从“黑匣子”变成“可审查的对象”因为很多问题的微缺陷数字指标上看不出来看一眼可视化结果就明白了。from ultralytics import YOLO model YOLO(runs/train/nutscrew_v8n/weights/best.pt) results model.predict( sourcedatasets/nut_screw/test/images, conf0.25, saveTrue, projectruns/verify, nametest_vis, )导出后逐张浏览测试集预测图重点找三类问题漏检有螺丝螺母但没有任何框、误检背景纹理被框出来、框偏框没有包住完整目标。这个步骤比看mAP更能反映模型在真实场景的可用程度。我习惯看完第一遍后把conf0.25调到0.5再看一遍。低阈值能拖出更多重叠框高阈值会暴露漏检两遍对比就能定位问题出在“置信度调校”还是“特征学习不足”。我的经验是如果漏检集中在特定光照或特定角度回到增强脚本里补对应样本如果误检集中在纹理背景检查标注数据里是否缺了“无目标的负样本”图片适当补充背景图参与训练。这一步做完这个数据集训练出的模型才真正有资格进入现场试用而不是只在验证集上好看。希望这个数据集能成为你走进机械零件识别检测项目的第一步。423张不多但你踩过的每一个坑都会在下一个更大的数据集上替你省下时间。希望帮到你。本文还有配套的精品资源点击获取