简介这是一份基于OpenCV与SSD模型的人脸检测项目压缩包面向计算机视觉初学者与开发者解决图像及视频流中的人脸定位需求。包内共10个文件约6.36MB核心包含两个Python脚本单图检测与视频检测、预训练的SSD caffemodel权重、模型描述文件、XML工程配置以及多张JPG测试样图无需额外准备模型即可直接运行并观察检测效果。资源已有417人学习下载适合希望快速上手深度学习目标检测、了解OpenCV调用SSD流程的读者。借助清晰的函数模块划分与处理流程可以掌握图像预处理、模型加载、置信度过滤、边界框绘制等关键环节同时测试图片和模型权重分离存放便于替换自己的图像进行验证为后续扩展人脸识别、实时监控或嵌入式部署等项目实践打下基础。1. 一个 facedetection.zip 压缩包到底能救你什么一个叫facedetection.zip的压缩包到你手里通常意味着一个人脸检测方案的最小可复用资产里面大概率有训练好的模型权重、推理脚本、依赖清单和一小撮标注数据能让你不用从零训练就在图片或视频里把人脸框出来。对做安防、考勤、客流统计的团队来说它是典型的“拿到就能先验证跑通再谈改造”。但压缩包不是承诺解压后能不能用取决于三个问题模型是什么框架导出的、输入输出是什么格式、代码和当前环境是否兼容。这篇笔记就按拿到zip后的实际操作顺序写解压、识别结构、跑通推理、微调训练、排坑最后用评估指标判断这个方案值不值得继续投入。2. 用 unzip 安全解压 facedetection.zip先看清单而不是直接运行很多人拿到压缩包第一反应是双击解压然后拖进 IDE。这个习惯会给你之后的工作埋两颗雷一是压缩包可能带目录层级直接解压会让一堆文件铺满当前目录后面找模型找不到二是如果包本身损坏或内容被篡改你到训练时才发现白跑了一轮。所以我拿到任何facedetection.zip第一件事永远是建一个干净的目录先校验再解压。这个过程不涉及任何业务逻辑但能帮你省掉后面百分之三十的排错时间。2.1 解压前先做两件事文件大小和 MD5 校验压缩包可能只有几 KB也可能有几 GB这个差异直接决定了它是代码骨架还是带了完整权重。先看大小再算校验值是成本最低的两步。# 1. 看文件大小判断由模型权重主导还是由代码主导 ls -lh facedetection.zip # 2. 计算 MD5并保存下来方便后续核对传输结果 md5sum facedetection.zip | tee facedetection.zip.md5 # 3. 创建专用目录解压到里面避免文件散落到当前目录 mkdir -p facedetection unzip -q facedetection.zip -d facedetection这三条命令里ls -lh和unzip -q都好理解关键是中间的md5sum。很多工程事故不是代码写错而是压缩包在拷贝过程中丢了一个字节导致模型加载到一半报“Unexpected end of file”。你把md5sum算出来的值和来源方给的对不上就别继续浪费时间解压直接重传。如果来源方没给值算完存起来至少自己能追查。Windows 环境可以把md5sum换成certutil -hashfile facedetection.zip MD5逻辑一样。解压到facedetection/目录后我的习惯是立刻看一眼解压出来的顶层文件不要急着跑脚本。还有一个容易忽略的细节如果压缩包是在 Windows 上打的解压后文件权限可能都是 644脚本没有执行权限。这时候先chmod x *.sh再用ls -R确认目录树的完整度。很多人在这一步直接跳过后面bash train.sh时报 permission denied还以为是代码问题。2.2 一个典型人脸检测项目的目录结构与文件作用虽然facedetection.zip内部长得各不相同但人脸检测项目翻来覆去就那几类文件。理解的职责如下表文件/目录常见名称举例作用模型文件models/face.onnx、weights/face.pt、model.pth人脸检测推理的核心可能是权重也可能是完整图推理脚本detect.py、run.py、infer.py读取模型对图片/视频/摄像头做预测数据样例data/sample.jpg、test.mp4让你不依赖真实业务数据就能快速复现结果配置文件config.yaml、face.yaml模型路径、输入尺寸、置信度阈值、类别名依赖清单requirements.txt、environment.yml锁定运行环境的第三方库版本训练脚本train.py、train.sh训练或微调模型标注数据annotations/、labels/、JPEGImages/训练或验证用的人脸框标签说明文档README.md、INSTALL.md作者的使用说明通常包含环境配置和运行命令这里要提醒一句不要迷信README.md。我见过不少压缩包里的 README 是自动生成的写满了“TODO”真正有价值的信息在代码里。所以下一步我会打开detect.py或train.py的头部看它 import 了什么库、读取了哪个路径。这两分钟能帮你少踩很多坑。另外如果发现包里有requirements.txt我会先cat它而不是直接pip install -r。因为有些依赖列表里写着pytorch1.8.1但你机器上的 CUDA 是 11.7装了也不一定能跑。先看再装比装完再卸强。2.3 怎么判断这个压缩包是推理包、训练包还是数据集包三者混淆是常态但根据关键文件可以快速归类。如果解压后有models/face.onnx加一个detect.py这是推理包目标是把现有模型跑起来重点看模型输入输出和依赖。如果出现train.py、datasets/、face.yaml这是训练包说明作者把训练配置也放进来了你可以用它微调。如果全是images/和xml/或json/标注那它只是个数据集包并不带模型你需要另外找训练代码或直接用标注格式去训练。我的判断方法是先看有没有模型文件再看有没有 train.py最后看标注数据的格式。最省事的是推理包因为它不需要你配训练环境最容易被误判的是数据集包有人拿到一堆图片和标注以为缺了模型文件其实是这包本来就没打算给你模型。所以别一上来就骂对方“少发了一个文件”先按这个思路排一遍。这里还有一个实际场景有些包既有.onnx又有.pt还有onnx2trt.py。这种通常是作者的交付模板包含了从 PyTorch 到 ONNX 再到 TensorRT 的完整转换链。这时你不必全部跑通只需要根据部署目标选一条路。如果目标是验证算法效果直接用.pt或.onnx如果目标是上线 GPU 服务器再看转换脚本。不要贪心把链路都跑一遍因为每一步都要和版本对齐时间成本很高。2.4 如果压缩包里的 README 是空白的怎么把结构补全我遇到过一次整个包只有一个detect.py和一个.onnx没有说明。这种情况下我会先用 grep 看代码里引用了哪些入口文件和资源路径。# 在解压目录内搜索代码中出现的模型、输入路径和依赖关键字 grep -R load\|weight\|onnx\|yaml\|video --include*.py .这条命令能把代码里所有涉及模型加载和输入输出的行翻出来。之后看代码开头 import 的库就能逆推出环境依赖。如果代码里写了torch.load(face.pt)那模型就是 PyTorch 格式写了ort.InferenceSession(face.onnx)就是 ONNX 格式。再配合requirements.txt如果有装依赖没有就手动装几个最常见的库比如opencv-python、numpy、torch或onnxruntime。这套反推流程基本能把一个“裸包”盘活。如果连代码都没有只有一个模型文件那就只能靠模型本身去推断输入输出。这时可以用torch.jit加载.pt或者用onnx库查看计算图。常见做法是python -c import onnx; m onnx.load(models/face.onnx); print(m.graph.input[0]); print(m.graph.output[0])这样能看到输入张量的 shape 和输出张量的 shape从而确定输入尺寸。没有 README 并不可怕可怕的是不检查就盲跑跑了一天发现模型路径写的是绝对路径根本不是你机器上的目录。到这一步包的结构和类型已经清楚了。下一章我们进入最关键的事件把推理真正跑通。3. 把 facedetection 的推理跑通从模型文件识别框架到最小脚本推理是判断一个facedetection.zip有没有价值的试金石。很多模型文件在理论上有 99% 的 mAP可一旦放到你的机器上加载失败、输出乱码、框画错都是推理阶段暴露的。所以不要先去纠结训练先把最小推理验证跑通。3.1 先看后缀名判断框架ONNX、TensorRT 还是 PyTorch模型文件的后缀基本决定了你的技术路线。下面是我常用的判断表和初次运行时需要的运行时库文件后缀大概率框架运行时选择一句话注意点.pt/.pthPyTorchtorch要小心它是state_dict还是完整模型加载方式不同.onnxONNXonnxruntime跨平台最稳可转 TensorRT/OpenVINO.engine/.trtTensorRTTensorRT 运行时只适配特定GPU型号换卡要重新导出.xml.binOpenVINOopenvino适合 Intel CPU 部署但需要转换工具链这里最容易翻车的是.engine文件。TensorRT 的推理引擎和 CUDA、显卡架构强绑定你在 A 卡上导出的 engine放到 B 卡上大概率加载失败。所以见到.engine先问清楚来源环境的显卡型号和 TensorRT 版本。如果没有.engine只有.onnx那是最好的一条路因为onnxruntime在 CPU 上也能跑能先把逻辑验证跑通再谈加速。3.2 最小推理脚本加载模型、人脸预处理、后处理过滤假设解压目录里有一个models/face.onnx和一段说明文字我们就用 ONNX Runtime 写一个最小推理脚本。选择 ONNX 是因为它不需要搭建复杂的 PyTorch 环境用 CPU 就能在大多数机器上跑。# detect_min.py # 以 ONNX 模型为例读取一张图片并输出人脸框 import cv2 import numpy as np import onnxruntime as ort # ---------- 1. 加载模型 ---------- session ort.InferenceSession( models/face.onnx, providers[CPUExecutionProvider] # CPU 先调通之后可按需切 GPU ) input_name session.get_inputs()[0].name print(模型输入:, session.get_inputs()[0].shape, session.get_inputs()[0].type) # ---------- 2. 预处理等比缩放 letterbox 填充 ---------- def letterbox(img, new_size640): h, w img.shape[:2] scale min(new_size / h, new_size / w) new_w, new_h int(w * scale), int(h * scale) resized cv2.resize(img, (new_w, new_h)) canvas np.full((new_size, new_size, 3), 114, dtypenp.uint8) canvas[:new_h, :new_w] resized return canvas, scale, (new_w, new_h) img cv2.imread(test.jpg) canvas, scale, (new_w, new_h) letterbox(img, 640) # 转为 CHW 并归一化到 0~1 blob cv2.dnn.blobFromImage(canvas, 1/255.0, (640, 640), swapRBTrue) # ---------- 3. 推理 ---------- outputs session.run(None, {input_name: blob}) pred outputs[0][0] # 通常第一个维度是 batch第二维是候选框 print(原始输出形状:, pred.shape) # ---------- 4. 后处理过滤低置信度 NMS ---------- # 假设输出格式为 [x_center, y_center, w, h, objectness, class_score] conf pred[:, 4] * pred[:, 5] keep conf 0.5 boxes pred[keep] conf conf[keep] # 把中心点坐标转成左上角和右下角 x1 boxes[:, 0] - boxes[:, 2] / 2 y1 boxes[:, 1] - boxes[:, 3] / 2 x2 boxes[:, 0] boxes[:, 2] / 2 y2 boxes[:, 1] boxes[:, 3] / 2 # 还原 letterbox先减 padding再除 scale pad_x (640 - new_w) / 2 pad_y (640 - new_h) / 2 x1 (x1 - pad_x) / scale y1 (y1 - pad_y) / scale x2 (x2 - pad_x) / scale y2 (y2 - pad_y) / scale # NMS 去重 indices cv2.dnn.NMSBoxes( [(float(a), float(b), float(c-a), float(d-b)) for a, b, c, d in zip(x1, y1, x2, y2)], [float(c) for c in conf], score_threshold0.5, nms_threshold0.45 ) for i in indices: i i[0] if isinstance(i[0], list) or isinstance(i[0], np.ndarray) else i cv2.rectangle(img, (int(x1[i]), int(y1[i])), (int(x2[i]), int(y2[i])), (0, 255, 0), 2) cv2.imwrite(result.jpg, img) print(检测完成结果已写入 result.jpg)这段代码有三个关键点你要注意。第一letterbox函数里填充用的是114这是 YOLO 系列常用的填充灰度值不是随便填的。如果你拿的模型来自 MTCNN 或 RetinaFace可能根本不期望 letterbox而是直接 resize 到固定尺寸那时这段预处理就要改。第二pred.shape能帮你确定输出格式如果第二维是25200多半是 YOLO 风格的解码前输出如果是N且每行大于 5就可能是x1, y1, x2, y2, conf, ...的直接回归格式。需要按模型自定义。第三坐标还原那步“减 padding再除 scale”是最容易被漏掉的后面避坑章节我会再展开。3.3 三个必调参数conf_thres、nms_thres、input_size不管模型来源是什么你在推理阶段一定会遇到这三个参数它们直接决定检测效果。置信度阈值conf_thres控制“多像人脸才算检测到”。默认 0.5但距离远、模糊的人脸置信度往往只有 0.3~0.4。我一般先用 0.3 看召回再用 0.5 看精度最后根据业务场景选一个平衡点。NMS 阈值nms_thres控制“两个重叠框合并的松紧度”。设 0.4 会保留较少的框适合密集人群设 0.6 会合并更多相近的框但可能导致多人贴脸时漏检。最微妙的是input_size模型训练时的输入尺寸决定了它的感受野。如果你用 320 寸输入一张包含无数小脸的集体照可能全部漏检换 640 就能好很多但推理速度也会相应下降。这个参数不是越大越好要配合业务里人脸最小的像素数来定。3.4 用摄像头实时跑一遍验证不是只能跑图片图片能跑通只说明模型的一部分。我会把它改成摄像头输入因为人脸检测最终大多会落在视频流上。# video_demo.py # 对摄像头每一帧做人脸检测断掉边界情况验证稳定性 import cv2 from detect_min import letterbox import onnxruntime as ort import numpy as np session ort.InferenceSession(models/face.onnx, providers[CPUExecutionProvider]) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break canvas, scale, (new_w, new_h) letterbox(frame, 640) blob cv2.dnn.blobFromImage(canvas, 1/255.0, (640, 640), swapRBTrue) pred session.run(None, {session.get_inputs()[0].name: blob})[0][0] conf pred[:, 4] * pred[:, 5] keep conf 0.3 # 实时场景适当降低阈值容忍更多误检 # ... 同前面 NMS 和坐标还原逻辑 ... cv2.imshow(face, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()实时跑最怕的不是速度慢而是画面翻转、摄像头采集格式异常这类基础问题。如果画面一直打不开优先检查cap.isOpened()很多环境里 OpenCV 需要sudo或者换采集后端。实时推理时我会把conf_thres降到 0.3因为视频帧有运动模糊阈值太高框会一闪一闪。后面要做的训练微调也会以这个视频 demo 的结果作为基线对比。4. 用训练包微调 facedetection 模型数据集格式、命令与超参推理跑通只说明模型可用但不说明它对你的业务场景好用。人脸检测最常见的痛点是原模型在开源基准上不错但对你的俯拍角度、密集人群、昏暗光线效果一塌糊涂。这时就需要用训练包做微调。很多facedetection.zip里虽然带训练脚本但你要先解决数据格式问题。4.1 三种主流人脸检测标签格式VOC、COCO、YOLO你的标注数据可能是对方的格式也可能是你自己用标注工具导出的格式。最常见的是这三种格式典型文件坐标形式适合的训练代码VOCAnnotations/*.xml左上 右下绝对像素传统检测框架、部分 PyTorch 项目COCOannotations/train.json左上 宽高绝对像素通用检测项目pycocotools评估方便YOLOlabels/*.txt归一化的cx, cy, w, hYOLO 系列仓库读取最快我经常收到只有 VOC 标注的包但对方训练代码只吃 YOLO 格式。转换脚本不复杂但有两个边界坑一是 VOC 里类别名是中文需要先映射成names里的索引二是 XML 中有旋转或未剪裁的情况坐标可能超出图像边界转换时要 clip。另一个常见问题是 COCO 的语义有人把bbox写成[x1, y1, w, h]有人写成[x1, y1, x2, y2]对不上会直接导致训练 loss 不收敛。我的建议是转换后用一个小脚本可视化三张图确认框落到人脸再开始训练。不要怕这一步费时间训练一次几小时可视化确认只要三分钟省下的返工时间很可观。4.2 最小可用的训练命令与参数设置假设你已经把标注整理成face.yaml且代码基于常见的 YOLO 系列训练器那么最小命令是python train.py \ --data face.yaml \ --weights models/face.pt \ --batch 16 \ --epochs 30 \ --img 640 \ --device 0--weights models/face.pt表示在压缩包里那个预训练模型的基础上微调而不是从随机初始化开始--data face.yaml指向数据配置--batch 16表示每轮用 16 张图--img 640与推理时的输入尺寸保持一致。face.yaml最少需要四行train: ./data/train val: ./data/val nc: 1 names: [face]这里的nc是类别数量人脸检测通常就是 1。如果原模型是从 COCO 预训练的它可能有 80 类你把它改成 1 类后检测头最后一层权重会初始化成随机值。这没问题微调会把它学回来。但要注意--weights加载时如果报 shape mismatch是正常的说明检测头不匹配而骨干网络权重已经加载上了。看到这个错误不要慌先检查打印日志里加载权重是否 skip 了最后一层如果 skip 的只是检测头就继续跑如果整个权重都没加载那才是路径或 key 的问题。4.3 超参调整epoch、batch size、学习率怎么配不会翻车拿到一个压缩包里的预训练模型我不建议直接照搬作者的训练参数。数据量不同超参完全不同。如果你的标注只有几百张epochs设 30 就够了再多大概率过拟合如果数据量有上万张30 只是个起点。判断标准很简单看验证集 loss 是否在最后一个 epoch 还在下降如果还在降就继续加。batch size主要受显存限制。16 是常规值如果你 8GB 显存跑不动就降到 8同时学习率也要降。一个我可以给你的血泪经验是batch 从 16 改成 8 时学习率最好减半否则训练初期 loss 容易飞。学习率一般用0.001起步配合 cosine 衰减。如果训练时 NaN最常见原因是学习率太高或数据里有脏标注先把 lr 降到0.0001试别再调网络结构。另一个容易忽略的是workers参数它控制数据加载线程数。在 Windows 上workers设太大会死锁在 Linux 上设 8 通常没问题。如果你在训练时发现 GPU 利用率忽高忽低多半是workers太少数据供给跟不上而不是模型问题。4.4 迁移学习时要不要冻结骨干网络微调时冻结骨干可以显著加快训练但也会限制模型适应新场景的能力。我的做法是当新数据量少且与预训练分布差距不大时冻结前 10 层当新场景反差很大比如全是监控俯拍角度则不冻结让全部参数参与更新。冻结操作在不同仓库里写法不同通常是一个--freeze 10参数含义是冻结前 10 层。如果你用自研训练脚本就要在加载权重后遍历模型参数并设置requires_gradFalse。这里有个判断技巧先不冻结跑 5 个 epoch看验证集效果如果已经不错再开冻结重新训练做对比。冻结不是省事的手段是当你没有足够算力时的妥协方案。如果压缩包里没有训练脚本只有推理代码那最省力的路径是把你整理好的数据集移到通用训练框架里跑而不是自己写训练循环。通用框架对数据采样、增强、NMS 后处理都调试过比自己从零写稳定得多。你只需要把模型结构和权重迁移过去。迁移时注意保留原来的归一化参数也就是数据集里的均值方差如果框架默认用 COCO 的均值方差而你的模型是在自建数据上训练的结果会偏差很大。5. facedetection 实战避坑从解压到部署的 5 个典型翻车点人脸检测项目真正做到生产环境你会发现一半时间在排环境冲突。下面这 5 个坑我基本每个都踩过按“现象 → 原因 → 解决”写清楚你遇到时可以直接照做。5.1 依赖版本冲突OpenCV 和 PyTorch 的 CUDA 版本对不上现象跑detect.py到torch.load之后崩溃终端报一堆undefined symbol: cudnn_BatchNormalizationForwardInference或者onnxruntime直接提示找不到 CUDA 库。 原因机器上装了多个 CUDA 运行时PyTorch 自带的libcudnn和系统全局的 OpenCV 动态库版本不一致运行时加载顺序互相污染。这属于环境问题不是代码问题。 解决先别调代码用python -c import torch; print(torch.__version__, torch.version.cuda)和python -c import onnxruntime as ort; print(ort.__version__)查看版本。如果两者要求的 CUDA 版本不一致最简单的办法是推理阶段只用CPUExecutionProvider先保证流程通要上 GPU 时把 OpenCV 换成opencv-python-headless减少动态库冲突面。另一个操作是启动脚本前加export LD_LIBRARY_PATH$TORCH_HOME/lib:$LD_LIBRARY_PATH给 PyTorch 的库更高的优先级但这是后手治标不治本。5.2 小脸检测不到可能不是模型问题是预处理 resize现象单人照检测很好一到五六人的会议室合照就直接漏掉后排人脸。 原因大多数 YOLO 训练使用 640×640 输入如果图片直接 resize 到 640原图里 20×20 像素的小脸在输入图上只有 5×5特征完全丢失。这不是模型能力不行而是预处理方式有问题。 解决先把输入尺寸提高到 1280看小脸召回率是否明显上升。如果还不行使用分块推理tiling把原图按 640×640 的窗口切块每块单独检测再把结果合并。注意相邻块要有重叠区否则若人脸在切割边界会被截断。分块推理会增加耗时但对密集小脸场景是有效的。另外也可以在微调阶段加入“随机裁剪缩放”增强让模型见过更多小脸尺度的样本。有一种更激进的做法是训练专门的小脸分支但那是重活先通过调大输入尺寸和 tiling 解决别一上来就改模型结构。5.3 推理速度不达标可能只是没开半精度或线程池现象模型在 GPU 上跑但每帧耗时 80ms明显比作者说的慢或 CPU 推理时 CPU 占用率只有 20%。 原因默认加载模型是 FP32并且 ONNX Runtime 在 CPU 上只用一个线程GPU 也可能没启用 TensorRT 或 FP16。 解决CPU 场景设置os.environ[OMP_NUM_THREADS] 8并在InferenceSession里指定sess_options.intra_op_num_threads 8GPU 场景如果模型是 ONNX试着用 TensorRT provider开启 FP16。这些改动不动模型结构就能翻几倍速度。如果模型本来是 PyTorch也可以在推理时加一句model.half()并使用torch.cuda.amp。不过半精度对某些老 GPU 不友好需要实测。另一个容易被忽视的点是分辨率把输入从 640 降到 480速度提升明显精度损失通常可控要看业务是否接受。5.4 检测框和原图错位letterbox 坐标还原漏了现象检测框能画出来但位置明显偏左/偏上或者框比人脸大了一圈。 原因预处理用 letterbox 把原图缩放并填充到 640×640后处理时直接用了模型输出的坐标没有做“减 padding 再除 scale”的逆运算。 解决回到 3.2 节那一步严格把模型输出坐标还原。常见错误是只除了 scale忘了减 padding。可以用下面这段代码自我检查# 纠正 letterbox 坐标还原 x1_orig (x1 - pad_x) / scale y1_orig (y1 - pad_y) / scale然后在原图上画框和人眼位置对比。如果仍偏打印pad_x和scale的数值多半是取值用了全局变量而没从返回值拿。这类 bug 玄学就在于它在静态图上看着还行一到视频里框就会抖。更隐蔽的情况是训练时做了 mosaic 增强模型输出坐标里的 padding 不是预处理里的那一套这时需要回到配置文件里看模型作者是怎么算坐标的。5.5 压缩包里的权重和代码版本不匹配加载就报错现象用torch.load加载.pt时报KeyError: model.0.conv1.weight或权重文件能加载但model.forward()输出形状和预期不同。 原因压缩包里的权重是用旧版网络结构训练的而你手上的推理或训练代码是另一个版本。比如 YOLOv5 的权重被拿去给 YOLOv8 代码加载层名字匹配不上。 解决先诊断打印权重文件里的 keyimport torch ckpt torch.load(models/face.pt, map_locationcpu) if isinstance(ckpt, dict) and model in ckpt: print(list(ckpt[model].state_dict().keys())[:5]) else: print(type(ckpt), list(ckpt.keys())[:5])拿到 key 之后和当前代码里网络定义的层名对照确定是哪个版本的权重。如果版本差异大最好的选择不是手动改权重而是找对应版本的推理代码。避免以后出现这种问题的做法是在解压后立刻给压缩包内的代码和权重文件的 MD5 建一个“配套清单”记录谁和谁是一套。6. 让 facedetection 更可信先算 mAP再谈优化推理通了、微调也跑了但你还是没回答“这个方案到底行不行”。只靠肉眼在几张图上数框很容易被个别好结果迷惑。我的习惯是任何 face 检测模型到手先算一次 mAP用数据决定下一步。6.1 用测试集算一次 mAP10 分钟看懂模型真实水平你不用重写评估框架如果压缩包内代码有val.py就用它没有的话在训练包基础上写一个评估脚本逻辑很简单对每张测试图推理和真值框算 IoUIoU 大于 0.5 记为 true positive再按置信度排序画 PR 曲线。用 WIDER FACE 的公开验证集也可以但更推荐用你自己的业务数据因为 mAP 高只代表在特定分布上强不代表在你场景上强。这个数字可以直接用来决策如果 mAP 在 0.9 以上放心交付如果在 0.7 左右先补小脸场景的标注再微调如果低于 0.5别在这个模型上死磕换模型比调参更划算。6.2 一个高性价比改动让检测头顺带输出两个关键点如果业务下游还需要做人脸对齐、活体判断或裁剪归一化我通常会选择在检测头里顺带回归双眼中心点而不是另起一个新模型。检测框的两个顶点外加双眼两个点只需要给最后一层多加四个输出通道训练时多一次损失计算。这样做的好处是下游任务复用同一个特征图完全省掉一次前向推理。方案落地时注意给这两个关键点也做同样的坐标归一化并在微调数据里补齐人脸框内双眼标注没有标注就别硬改否则会拖累检测精度。最后说一点我的习惯任何拿到手的facedetection.zip我都会先重复一遍“解压——跑通——建测试集——算 mAP”这四步再决定要不要改结构、换框架。之前有次为了省时间跳过 mAP 直接上摄像头调参数结果小脸漏检问题在灰度环境里被放大返工了一整周。从那以后我再也不信“看起来还行”只信测试集上的数字。希望帮到你。本文还有配套的精品资源点击获取