YOLO V3与TensorFlow 1.x安全帽识别:数据集、预训练模型及训练部署全流程解析
简介面向工业安全场景的安全帽佩戴检测需求这份基于TensorFlow 1.x实现的YOLO V3模型训练资源包非常适合有一定深度学习基础、希望快速落地目标检测应用的开发者。压缩包共33个文件以17个Python脚本为核心覆盖数据预处理、模型构建、训练、评估、推理等完整流程同时附带3张网络结构示意图、3张检测效果图、类别标签文件、锚框配置文件及说明文档整体体积仅828KB轻量便携。已有212人学习浏览实用价值初步获得认可。资源内不仅能获取预训练权重转换脚本、K均值锚框聚类工具、单张图片与视频测试脚本还配有数据集下载指引可支撑从零复现安全帽识别模型。借助架构图、骨干网络图和聚类结果图读者还能深入理解YOLO V3的设计原理便于后续迁移到高空坠物、跌倒检测等类似工业视觉任务。1. YOLO V3 TensorFlow 1.x 的安全帽识别旧组合为何还没被丢进垃圾桶TensorFlow 1.x 已经停止维护但网上大批安全帽识别项目仍锁死在 1.15 Python 3.7 的组合。不是这些项目作者不愿意升级而是 YOLO V3 的 TensorFlow 1.x 实现里有大量 session、placeholder、frozen graph 代码迁到 2.x 几乎等于重写。对刚拿到“YOLO V3Tensorflow 1.x 安全帽 识别 提供数据集下载和预训练模型.zip”的人来说这套包的价值在三点数据集是已经清洗过的施工安全场景图片预训练模型能直接跑出框训练脚本把 Darknet 权重到 TensorFlow checkpoint 的路径写得很短。下面按解包、复现、训练、冻结 PB 的完整流程走一遍。2. 数据集下载与预训练模型的解包检查拿到 zip 后我一般第一件事是列出包内目录不是直接喂给训练脚本。这类压缩包通常包含 JPEGImages、Annotations、cfg 和 weights 四个部分目录名偶尔是安全帽、hardhat 或 helmet。先列一遍文件结构确认数据是否完整再谈训练。2.1 zip 包内的典型目录结构unzip YOLO V3Tensorflow 1.x 安全帽 识别 提供数据集下载和预训练模型.zip -d safehat cd safehat find . -maxdepth 2 -type d | sortunzip 在大多数 Linux 发行版和 Git Bash 里都有找不到时用 Python 的 zipfile 模块解压更通用python -m zipfile -e YOLO V3Tensorflow 1.x 安全帽 识别 提供数据集下载和预训练模型.zip safehat解压后重点确认四类文件在不在缺哪类补哪类路径内容训练时是否必需data/safehat/JPEGImages安全帽佩戴、未佩戴的现场照片通常几十到几千张不等是data/safehat/AnnotationsVOC XML 标注框住头部和帽子区域类别名通常是 helmet 或 head是cfg/yolov3.cfg网络结构、anchors、类别数配置是weights/darknet53.conv.74预训练骨架权重用于迁移学习建议有只看到 JPEGImages 而缺少 Annotations说明压缩包只给了图片标注要自己补。常见做法是用 LabelImg 重新画但那样工作量大得多。weights 目录为空则要从 Darknet 官方权重里拿 darknet53.conv.74训练时只用前 74 层卷积参数。如果连 cfg 都没有就要根据代码里的配置模板重新补一份YOLO V3 的 cfg 内容很长手工补容易漏掉 scale 层后面的 yolo 层配置。2.2 标注格式与训练集/验证集划分这类旧项目的数据集标注格式常见有三种VOC XML、COCO JSON、YOLO txt。标题下这个压缩包大概率给的是 VOC XML因为 YOLO V3 TensorFlow 1.x 时代的数据加载器主要写的是 VOC。XML 里每个 object 的 name 是安全帽类别名bndbox 给出 xmin、ymin、xmax、ymax。拿到后先做分布检查到底有几个类别、每类的框数是否均衡。用 Python 统计类别和标注框数量import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter ann_dir Path(data/safehat/Annotations) cls_counter Counter() box_counter 0 for xml_file in ann_dir.glob(*.xml): root ET.parse(xml_file).getroot() for obj in root.findall(object): name obj.find(name).text.strip() cls_counter[name] 1 box_counter 1 print(类别统计:, dict(cls_counter)) print(总框数:, box_counter)逻辑说明遍历所有 XML 文件把每个 object 的 name 取出来计数顺便数总框数。安全帽类目常见的坑有两个。一是类别名不统一同一份数据集里 helmet 和安全帽混用训练时会被当成两个不相干的类别。二是标注框把整个头戴设备都画全了导致小目标漏检。统计时发现类别超过 2 个就要警觉安全帽项目通常只有 helmet 和 head 两类出现第三个类别大多是背景误标或标签写错。训练脚本一般要求数据清单格式是图片路径 对应 XML 路径可以用下面的脚本生成 train.txt 和 val.txt并顺手做一次八二切分python - PY import random from pathlib import Path imgs sorted(Path(data/safehat/JPEGImages).glob(*.jpg)) random.seed(2024) random.shuffle(imgs) split int(len(imgs) * 0.8) with open(data/safehat/train.txt, w) as f: for img in imgs[:split]: f.write(str(img.resolve()) \n) with open(data/safehat/val.txt, w) as f: for img in imgs[split:]: f.write(str(img.resolve()) \n) PY这里固定随机种子保证每次切分结果一致用绝对路径是为了避免后续训练脚本切换工作目录时找不到图片。要注意路径里有中文时TensorFlow 1.x 的文件读取接口有时会解码失败建议把数据集挪到纯英文路径下再做切分。2.3 预训练模型文件确认与哈希校验zip 里的预训练模型可能是三种格式之一Darknet 的 .weights、TensorFlow 的 .ckpt / .index / .meta、或者冻结后的 .pb。用 file 命令看文件头最直接file weights/* weights/*/* 2/dev/nullDarknet .weights 开头是版本号和迭代次数等元信息文件头一般不是 ASCIITensorFlow checkpoint 是二进制但会伴随 .index 和 .meta.pb 本身就是 protobuf可以用 strings 查到可读节点名。判断技巧如果压缩包里同时有 yolov3-tf.ckpt.data-00000-of-00001 和 checkpoint 文件说明作者已经把 Darknet 权重转成 TensorFlow 格式直接加载即可。如果只有 .weights后面转换步骤少不了一块。预训练模型版本要和数据集的类别数匹配。安全帽项目通常把 COCO 的 80 类权重截断成两类来用转换脚本会在最后一个卷积层前砍掉输出维度。下载之后先校验哈希避免传输损坏sha256sum weights/darknet53.conv.74得到的 64 位哈希要和压缩包附带校验文件比对。没有校验文件时至少观察文件大小darknet53.conv.74 常见大小在 150MB 到 160MB 之间差太多就直接换来源。这一步省不得因为旧项目训练时如果载入的是半截权重报错会在几个 epoch 之后才出现排查成本反而更高。3. 用 Anaconda 复刻 TensorFlow 1.x 环境并加载预训练权重YOLO V3 的 TensorFlow 1.x 代码对 CUDA 版本很敏感装错组合最常见的报错是 Could not load dynamic library libcudart.so.10.0。TensorFlow 1.15 需要 CUDA 10.0 与 cuDNN 7.4网上能找到的多数中文教程也把 Python 锁在 3.6/3.7。在开始转换权重之前先把环境固定下来。3.1 anaconda 安装 tensorflow 1.15 的一组固定命令常见做法是用 conda 新建独立环境不要动 base 环境。下面的命令组在 Windows 和 Linux 下均适用conda create -n tf1 python3.7 -y conda activate tf1 pip install tensorflow-gpu1.15.0 numpy1.17.5 pip install opencv-python4.1.2.30 pillow lxml tqdm参数说明numpy 锁在 1.17.5是为了避免 TensorFlow 1.x 和 numpy 1.19 的array_function兼容问题opencv 用 4.1.x 是因为 3.x 的接口在旧检测脚本里读取 uint8 时行为不同4.2 偶尔会导致画框时字体崩掉。如果机器没有 NVIDIA 卡把 tensorflow-gpu 换成 tensorflow1.15.0CPU 版跑推理可以训练 416x416 的大尺寸会非常慢。安装完先验证 GPU 是否可见python -c import tensorflow as tf; print(tf.__version__); print(tf.test.is_gpu_available())打印出 1.15.0 且 gpu 为 True 才继续。输出 False 时不要急着换安装包先跑 nvidia-smi 看驱动版本再检查 cuDNN 是否被放进了 conda env 的 lib 目录。很多“已经安装成功但训练时找不到 GPU”的问题都出在 conda 环境里没有 libcudnn.so 这个细节上。3.2 从 Darknet 权重转换到 TensorFlow checkpoint 的常见做法如果压缩包里只有 darknet53.conv.74 或 yolov3.weights需要用转换脚本把它变成 TensorFlow 可加载的 ckpt。绝大多数开源项目的转换逻辑是逐层读取 Darknet 的二进制权重再按 cfg 里的 layer type 顺序写入 tf 变量。下面是一段简化思路的示意代码用来帮助理解转换流程import tensorflow as tf import numpy as np def load_weights_variable(var, weights_data, start): shape var.get_shape().as_list() size np.prod(shape) values weights_data[start: start size].reshape(shape) start size return start, tf.assign(var, tf.constant(values, dtypetf.float32))逻辑说明Darknet 的 .weights 文件保存的是一维数组转换脚本按 cfg 中各层的卷积核大小裁出对应分片再 reshape 成变量的原始维度。上面的代码没有处理 BN 层的 gamma、beta、mean、var 排布真正转换时要卷积层和 BN 层成组读取。想手工验证转换是否正确可以随机抽一个 conv2d 的 kernel 值和 darknet 权重里对应偏移做逐位对比。实际项目里一般直接执行转换脚本调用方式类似python convert_weights.py \ --cfg configs/yolov3.cfg \ --weights weights/darknet53.conv.74 \ --output checkpoints/yolov3-tf脚本结束后目录下会出现 checkpoint、yolov3-tf.ckpt.index、yolov3-tf.ckpt.data-00000-of-00001 三个文件。看到这组文件说明权重转换成功后面训练脚本的 restore 路径填 checkpoints/yolov3-tf 即可。注意 Train.py 加载的是路径前缀不能带 .ckpt.data 后缀。3.3 网络结构、输入尺寸与三个输出张量TensorFlow 1.x 版的 YOLO V3 最常把输入占位符定义成 [None, 416, 416, 3]输出是三个尺度的特征图13x13、26x26、52x52对应大中小目标。每个网格点会输出 num_anchors/3 乘以5 num_classes个值其中 5 是 4 个框坐标加 1 个置信度num_classes 对安全帽识别是 2。配置里最影响结果的是下面几个参数参数名常用值影响input_size416小于 416 提速但小目标丢失num_classes2对应 helmet 和 headanchors9 组从小到大小锚框保留在 52x52 输出层strides[8, 16, 32]分别对应 52x52、26x26、13x13 的下采样倍率安全帽检测属于典型的小目标密集场景视频监控里人头可能只占几十像素。常见做法是在第一个 52x52 输出层上增大置信度权重或者在数据增强阶段对图片做 416 以内的随机裁剪强制模型学习局部特征。如果直接把 COCO 那套 80 类配置搬过来只改 classes十有八九小目标全丢掉。4. 用 YOLO V3 训练自己的安全帽检测模型的参数清单现在很多教程一上来就是 yolov5 训练自己的数据集但 YOLO V3 的 TensorFlow 1.x 版训练脚本依然围绕 train.py 展开核心是 --dataset、--classes、--pretrained_weights 这几个参数。换汤不换药区别主要在超参数的选值和 cfg 里的隐藏项。4.1 train.py 的常用参数和推荐取值用一段命令作为起点python train.py \ --dataset data/safehat/train.txt \ --val_dataset data/safehat/val.txt \ --classes data/safehat.names \ --pretrained_weights checkpoints/yolov3-tf \ --input_size 416 \ --epochs 100 \ --batch_size 8 \ --learning_rate 1e-4 \ --gpu 0参数说明pretrained_weights 优先指向第 3 章生成的 ckpt 前缀而不是直接填 .weights 文件train.py 里需要的是 TensorFlow checkpoint 路径。batch_size 8 是显存不够时的安全值显存充足时可以上到 16但 cfg 里的 subdivisions 要同步调。learning_rate 1e-4 用于迁移学习。如果从零开始训练通常是 1e-3 起步。安全帽数据量往往只有几百张用 1e-3 很快会过拟合所以我一般建议迁移学习阶段一律从 1e-4 开始。train.py 训练时会读取 cfg 文件里的超参数cfg 中与训练强相关的 5 项需要特别注意cfg 字段推荐值坑batch64实际显存占用必须再除以 subdivisionssubdivisions8真实 batch 大小 batch / subdivisionslearning_rate0.001自定义小数据集上要降到 0.0001steps40000, 60000小数据要按 epoch 换算成 step 数classes2改完 classes 必须同步改最后一层卷积滤波器数表格里的坑在旧项目里出现频率很高。TensorFlow 代码有时直接读 cfg 里的 batch 为 64再除以 subdivisions 得到 mini-batch如果代码走的是 argparse 而不是 cfg则以命令行参数优先。两者混用会导致显存爆掉或者训练 iter 数对不上。4.2 锚框聚类安全帽数据集的 9 组 anchors 要重新算迁移学习阶段可以沿用 COCO 的原始锚框但施工安全场景的目标尺寸分布和 COCO 差异很大帽子在图像中的比例更小且更集中。常见做法是写一个 k-means 脚本对训练集 xml 里的宽高聚类得到 9 组 anchors。import numpy as np from pathlib import Path import xml.etree.ElementTree as ET from sklearn.cluster import KMeans boxes [] for xml_file in Path(data/safehat/Annotations).glob(*.xml): root ET.parse(xml_file).getroot() for obj in root.findall(object): bbox obj.find(bndbox) w float(bbox.find(xmax).text) - float(bbox.find(xmin).text) h float(bbox.find(ymax).text) - float(bbox.find(ymin).text) boxes.append((w, h)) boxes np.array(boxes) kmeans KMeans(n_clusters9, random_state0).fit(boxes) anchors kmeans.cluster_centers_ print(np.round(anchors, 1))逻辑说明sklearn 的 KMeans 用欧氏距离聚类效果勉强能看标准 YOLO 锚框聚类应该用 IoU 距离 d 1 - IOU(box, centroid)这样小目标不会被大目标拉偏。上面的代码适合快速估算真要用于训练需要把欧氏距离换成 IoU 距离并且做 k-means 初始化。算出的 9 组 anchors 按从小到大的顺序分配1-3 组给 52x524-6 组给 26x267-9 组给 13x13。安全帽场景常见输出是前几组宽度在 10-30 像素之间。如果聚类结果里最小锚框大于 50说明数据集里存在大量远距离群体照此时要做随机裁剪增强否则小目标会被当成背景。4.3 Loss 曲线和 mAP 验证训练过程中保存的 checkpoint 默认在 checkpoints/ 下train.py 会记录 loss 到日志目录。用 TensorBoard 看曲线tensorboard --logdir logs浏览器打开 6006 端口只关注三个现象。第一loss 在前 10 个 epoch 有没有从高位降下来第二是否在 epoch 60 附近进入平台期第三val loss 上升而 train loss 继续下降就是过拟合。第一种现象通常是学习率太高第二种要把 steps 提前第三种要增加数据增强或缩小模型输入尺寸。评估脚本一般叫 evaluate.py输出每个类别的 AP 和整体 mAPpython evaluate.py \ --dataset data/safehat/val.txt \ --classes data/safehat.names \ --weights checkpoints/yolov3-tf \ --nms_thresh 0.45 \ --iou_thresh 0.5参数说明nms_thresh 是 NMS 的 IoU 阈值值越大越容易合并重叠框0.45 是 YOLO V3 常见默认iou_thresh 是计算 mAP 时判定预测框是否命中真值的阈值0.5 是标准 PASCAL 指标。对 helmet 和 head 两类且头部遮挡严重的数据集如果 helmet 的 AP 低于 0.7先看两类标注框的重叠是不是太高。施工场景里帽子紧贴头部框的 IoU 经常超过 0.7评估结果容易两头掉。5. 冻结 PB 做推理并把三个高频故障拧掉训练完的 checkpoint 不适合直接丢给线上服务TensorFlow 1.x 的常见做法是先固化成 .pb 文件再通过 tf.compat.v1 加载。安全帽识别这类边缘部署场景PB 体积小、计算图固定推理时不会因代码版本变动而踩坑。5.1 单张图片与视频流推理命令python detect.py \ --image test_images/photo1.jpg \ --weights checkpoints/yolov3-tf \ --classes data/safehat.names \ --score_thresh 0.30 \ --nms_thresh 0.45 \ --output output/photo1.jpgdetect.py 内部流程是加载模型把图片 resize 到 416x416跑 session.run 得到 detection_boxes再把坐标映射回原图。score_thresh 0.30 是安全帽监控常用值因为误检比漏检好处理调到 0.5 会漏掉远距离的小帽子。视频流推理把 --image 换成 --video path.mp4配合 OpenCV 的 VideoCapture 逐帧处理。5.2 checkpoint 冻结为 PB 的最小步骤TensorFlow 1.x 的 freeze_graph 工具能直接复用。命令形如python freeze_graph.py \ --input_checkpoint checkpoints/yolov3-tf \ --output_graph frozen_model.pb \ --output_node_names detectionsoutput_node_names 是检测输出的张量名在 train.py 源码里搜索常被命名为 detections、pred_sbbox 等。如果名字填错会报 KeyError 或输出空张量。冻结后建议用 40-50 张验证图对比 ckpt 和 pb 的 mAP两者应完全一致不一致多半是 BN 层在冻结时没有正确融合。5.3 三个高频故障显存不足时把 batch_size 降为 2或将 cfg 的 subdivisions 调到 16同时设置显存按需增长config tf.ConfigProto() config.gpu_options.allow_growth True sess tf.Session(configconfig)检测框在视频里抖动时把 NMS 阈值压到 0.35或者对连续帧的框坐标做指数滑动平均。如果抖动只在特定位置出现多半是输入图片被 VideoCapture 默认宽高改变了和模型无关。推理速度远低于预期时检查是否漏了输出版本校验更常见的是冻结后的 PB 里 BN 层 epsilon 未融合导致 CPU 推理变慢。安全帽场景需要提帧率时把输入尺寸降到 320mAP 通常只掉 1-2 个点帧率能提升 40% 左右。最后确认 checkpoint 的前缀三个文件都在加载时报 missing file 时直接看路径前缀别只看报错行尾的 .index。本文还有配套的精品资源点击获取