YOLOv8实战:智慧校园人脸识别与公路车辆检测落地 简介这是一份基于YOLOv8的智慧校园人脸识别与公路汽车检测综合项目面向计算机视觉入门与进阶学习者适合毕业设计、课程设计、大作业或工程实训。项目实现校园门口学生出入场景的人脸识别采用YOLOv8模型检测并跟踪人脸通过dlib特征提取模型生成128维特征与预存学生特征比对识别成功为绿色未识别为红色并统计人数同时内置公路汽车检测模块。配套代码与模型权重齐全包含完整运行脚本、预训练模型、测试视频、人脸样本图片和说明文档便于直接复现识别与检测效果也支持二次开发。资源包共34个文件压缩后约334MB以脚本、模型、图片、视频为主可参考其数据组织与检测流程迁移至其他目标检测任务。已有459人学习浏览适合初学者作为完整实践案例也适合进阶者优化算法。1. 先想清楚YOLOv8怎么同时接住人脸识别和汽车检测基于YOLOv8的智慧校园人脸识别和公路汽车检测听起来是两个项目其实是一条技术主线用同一个检测框架覆盖两类差异极大的视觉目标。智慧校园场景里门口门禁机要认出刷卡人脸校外公路要统计进出车辆这两个需求经常同时出现在一份招标方案里。YOLOv8的价值在于人脸检测和车辆检测本质上都是目标检测数据标注、训练、部署流程可以完全复用人脸识别只需把检测框喂给特征提取模型车辆检测则直接做跟踪和计数。这个方案适合正在做毕业设计、校安集成或边缘部署的工程师尤其是想用一套代码同时交付两个场景的人。下面从环境搭建、数据集处理、训练调参、RK3588部署到避坑按我实际落地的顺序展开。2. 环境与数据Ubuntu 20.04 CPU版跑通再用Labelme数据喂给YOLOv8很多新手上来就装CUDA版PyTorch光在驱动问题上就能卡一天。我的做法是先在Ubuntu 20.04上把CPU版环境跑通确认YOLOv8能正常出框再做数据工程。别小看这一步它能帮你在没有GPU的机器上完成标注质量抽检和推理验证正式训练再丢到有卡的机器上。2.1 Ubuntu 20.04搭建YOLOv8环境CPU版本的最小命令先创建独立Python虚拟环境避免弄乱系统Python。YOLOv8的核心包是ultralytics环境搭建比YOLOv5时代简单许多mkdir -p ~/yolov8-project cd ~/yolov8-project python3 -m venv yolov8-env source yolov8-env/bin/activate pip install --upgrade pip pip install ultralytics这段命令的逻辑是python3 -m venv yolov8-env创建一个隔离的Python环境source yolov8-env/bin/activate激活它pip install ultralytics会顺带装上当前平台匹配的torch/torchvision CPU版。如果你的机器没有NVIDIA显卡这个安装方式就是最省事的如果有显卡需要先装好驱动和CUDA再从PyTorch官网选择对应CUDA版本的安装命令而不是直接pip install torch否则会装成CPU版而不自知。装完后用一条预测命令验证环境yolo predict modelyolov8n.pt sourcebus.jpg第一次运行会自动下载yolov8n.pt预训练权重并下载一张示例图片bus.jpg进行推理。看到输出里出现几个检测框坐标和类别名环境就算通了。CPU推理一张普通图片约几百毫秒到一两秒做验证够用做实时视频流就不太现实。这里还经常有人问GTX1660Ti能不能跑YOLOv8。能跑但显存6GB要克制一点模型选yolov8nimgsz设640以下batch不超过8训练慢一点但能出结果。2.2 两个场景的数据集怎么凑公开集、自采和标注人脸检测的公开数据集最常见的是WIDER Face、FDDB但标注格式不一样WIDER Face给的是txt格式的人脸框坐标需要先转成YOLO格式。车辆检测常用的有UA-DETRAC、BDD100K也可以自己抽帧标一套。实际项目里我更推荐先用公开集做预训练再用现场摄像头抽几百帧人工标注用来消除场景偏差校园门口的光线、公路摄像头的俯视角度和公开数据差别不小。人脸识别门禁系统要注意一件事YOLOv8在检测阶段只需要一个face类识别谁是谁是后面特征比对的事。公路车辆检测则根据需求定义类别常见的是car、bus、truck、motorcycle。类别不要一开始就定太多每加一个类标注成本和漏检率都可能上涨。还有一个尺度问题公路场景的远处车辆可能只有16x16像素训练输入尺寸尽量用640起步有条件用1280同时依赖Mosaic增强把小目标拼进大图。人脸检测同样要注意门禁机上人脸占画面比例不大输入尺寸不能压得太低。2.3 用Labelme标注并转成YOLO格式转换脚本与坐标边界坑Labelme生成的是多边形点集JSONYOLO需要的是归一化的中心点和宽高。这个转换看起来简单实际有三个坑一是部分JSON里imageWidth/imageHeight字段缺失二是多边形点集顺序不固定三是坐标刚好贴边时归一化值可能越界。我常用这样一个转换脚本import json import os import cv2 import glob class_names [face] # 车辆检测改成 [car, bus, truck] def convert_labelme_to_yolo(json_path, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) # 优先从文件系统读图避免JSON缺少宽高字段 img_path os.path.join(os.path.dirname(json_path), data[imagePath]) img cv2.imread(img_path) if img is None: print(图片读取失败:, img_path) return img_h, img_w img.shape[:2] base_name os.path.splitext(os.path.basename(json_path))[0] out_txt os.path.join(out_dir, base_name .txt) lines [] for shape in data[shapes]: label shape[label] if label not in class_names: continue class_id class_names.index(label) points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 过滤无效框 if x_max x_min or y_max y_min: print(跳过无效框:, json_path, label) continue x_center (x_min x_max) / 2.0 / img_w y_center (y_min y_max) / 2.0 / img_h box_w (x_max - x_min) / img_w box_h (y_max - y_min) / img_h # 边界裁剪防止训练时坐标越界 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) box_w min(max(box_w, 0.0), 1.0) box_h min(max(box_h, 0.0), 1.0) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) if lines: with open(out_txt, w) as f: f.write(\n.join(lines) \n) for json_path in glob.glob(labelme_data/*.json): convert_labelme_to_yolo(json_path, yolo_labels)逻辑说明脚本用cv2读取原图拿宽高而不是依赖JSON里的字段兼容性更好。然后遍历每个标注形状取多边形外接矩形再转成YOLO格式。坐标裁剪和无效框过滤是为了防止训练时出现NaN或loss跳变。参数说明class_names的顺序就是类别ID顺序必须和后面data.yaml里的names保持一致。如果转换后txt数量对不上图片数量多半是imagePath是绝对路径但文件被移动过优先检查路径。另外要注意Labelme里如果是旋转框或分割掩膜这个脚本只取外接矩形车辆如果非要旋转框得用YOLOv8-OBB那是另一套流程。转换完成后按这个目录结构放好datasets/face/ images/train/xxx.jpg images/val/xxx.jpg labels/train/xxx.txt labels/val/xxx.txt对应的face.yamlpath: datasets/face train: images/train val: images/val nc: 1 names: [face]注意path是到datasets/face的路径train和val是相对这个路径的图片目录YOLOv8会自动到同样的labels目录找txt。3. 训练自己的数据集读懂结构、参数和损失函数曲线数据集准备好后训练不是敲一条命令那么简单。很多人直接拿默认参数训练效果差又不知道改哪里。我建议先花十分钟理解YOLOv8的网络结构图和关键参数再动手。3.1 YOLOv8网络结构图检测头为什么对这两个任务都友好YOLOv8的结构可以拆成三块Backbone、Neck和Head。Backbone用CSPDarknet提取多尺度特征Neck是PAN-FPN把高层语义和低层细节融合Head采用解耦头分类和回归分成两个分支。相比YOLOv5YOLOv8改成anchor-free省去一堆anchor超参对密集小目标更友好。人脸和车辆检测都是目标检测网络结构不需要改改nc类别数和数据就行。对人脸识别门禁而言YOLOv8只负责“脸在哪”不负责“是谁”。如果直接把每个学生设成一个类别类别会随人员变动无限膨胀不现实。正确做法是YOLOv8出框后再送人脸特征模型做身份比对。但如果只是实验室里做班级小规模识别也可以训练一个YOLOv8分类模型输入裁剪后的人脸输出学生ID模型小且能跑通。检测和识别分离的设计加人、换人时不需要重新训练检测模型。3.2 YOLOv8训练自己的数据集train命令与参数含义第一次训练建议用yolov8n作为预训练权重文件小、收敛快。下面是一个可跑的train命令yolo train modelyolov8n.pt datadatasets/face.yaml \ epochs100 imgsz640 batch16 device0 \ workers4 optimizerAdamW lr00.001 \ close_mosaic10 cos_lrTrue参数说明model指定预训练权重yolov8n.pt会自动下载data指向自己的data.yamlepochs给100配合早停够用imgsz640是训练输入尺寸公路场景小目标多可以提至960或1280但显存占用会成倍上涨batch16是每批图片数显存不够就降workers控制数据加载进程数CPU环境设2-4optimizerAdamW收敛更稳SGD调好了也强但更依赖学习率lr00.001是AdamW常见起点close_mosaic10表示最后10个epoch关闭Mosaic增强因为Mosaic合成的图和真实场景差距太大最后几轮关掉能减少分布偏移cos_lrTrue用余弦退火后期loss下探更平稳。如果是GTX1660Ti这类6GB显卡imgsz640、batch8、yolov8n能跑但每个epoch时间会偏长。CPU版我也试过训练一个epoch能跑到二十分钟所以我只把CPU版用于环境验证和推理正式训练还是另找GPU。训练结束后runs/detect/train/weights/下会生成best.pt和last.pt部署时默认用best.pt。3.3 用损失函数曲线判断训练状态结果文件results.csvYOLOv8训练过程不会实时弹出图表所有指标都写进runs/detect/train/results.csv。我习惯在训练途中就画图看状态而不是等结束。画图脚本不长import pandas as pd import matplotlib.pyplot as plt results pd.read_csv(runs/detect/train/results.csv) results.columns [c.strip() for c in results.columns] fig, axes plt.subplots(2, 2, figsize(12, 8)) axes[0, 0].plot(results[epoch], results[train/box_loss], labeltrain box) axes[0, 0].plot(results[epoch], results[val/box_loss], labelval box) axes[0, 0].set_title(box loss) axes[0, 0].legend() axes[0, 1].plot(results[epoch], results[train/cls_loss], labeltrain cls) axes[0, 1].plot(results[epoch], results[val/cls_loss], labelval cls) axes[0, 1].set_title(cls loss) axes[0, 1].legend() axes[1, 0].plot(results[epoch], results[train/dfl_loss], labeltrain dfl) axes[1, 0].plot(results[epoch], results[val/dfl_loss], labelval dfl) axes[1, 0].set_title(dfl loss) axes[1, 0].legend() axes[1, 1].plot(results[epoch], results[metrics/mAP50(B)]) axes[1, 1].set_title(mAP50) plt.tight_layout() plt.savefig(loss_curve.png)这段代码先读results.csv清理列名空格再分别画出box_loss、cls_loss、dfl_loss和mAP50。判断标准很简单训练loss和验证loss同步下降是小幅波动是正常的如果验证loss先降后升、mAP50同步回落就是过拟合回退到验证loss最低的epoch权重即可。如果训练loss一开始就降不下去先查标注格式如果出现nan立刻停查数据和学习率。3.4 从检测框到“认人”人脸识别门禁系统的接法人脸检测模型训练好后门禁系统不能只画框。我的标准接法是YOLOv8输出人脸框先用OpenCV裁剪出人脸图再交给轻量特征模型提取向量最后与注册库算余弦相似度。裁剪这一步直接决定识别率import cv2 from ultralytics import YOLO detector YOLO(runs/detect/train/weights/best.pt) def crop_aligned_face(frame, box): x1, y1, x2, y2 map(int, box) w x2 - x1 h y2 - y1 pad_w int(w * 0.1) pad_h int(h * 0.15) x1 max(0, x1 - pad_w) y1 max(0, y1 - pad_h) x2 min(frame.shape[1], x2 pad_w) y2 min(frame.shape[0], y2 pad_h) face frame[y1:y2, x1:x2] face cv2.resize(face, (112, 112)) return face results detector(frame)[0] for box in results.boxes.xyxy.cpu().numpy().tolist(): face crop_aligned_face(frame, box) # 把face送进特征模型得到embedding逻辑说明YOLOv8默认框刚好包住五官直接裁剪会损失额头和下颚这里按框宽高的10%和15%向外扩再缩放到112x112与常见人脸识别模型输入对齐。如果识别效果仍不理想可以做关键点对齐但需要额外标注眼睛、嘴角工程量翻倍。门禁策略上还要加阈值判定余弦相似度大于0.6放行小于0.4拒绝中间区间做人脸质量筛选避免把模糊脸拿去比对。4. 部署到门禁机和公路摄像头RK3588转换与检测落地训练出来的模型最终要跑在设备上。人脸门禁和公路车辆检测的部署形态、算力选型和优化方向不一样不能一刀切。4.1 部署形态和算力选型人脸识别门禁机一般是一台带屏幕和摄像头的嵌入式设备常见算力平台是RK3588带6TOPs NPU可以跑一个yolov8n加一个轻量人脸特征模型。公路车辆检测如果是多路摄像头通常放在机房GPU服务器或高性能边缘盒子上跑yolov8s/m甚至yolov8l再接跟踪算法。一个简化的选型表场景路数建议模型建议平台帧率目标人脸识别门禁1YOLOv8n 特征模型RK3588 / RV110610-20 FPS公路车辆检测4-8YOLOv8s/m带GPU服务器或RK3588集群单路15-25 FPS这张表的核心是门禁是单人近距离场景模型越小越好公路是远距离多目标场景模型容量和输入尺寸都不能太低。4.2 RK3588部署YOLOv8从PyTorch权重到板端推理RK3588部署YOLOv8是目前常见的边缘方案。流程分为三步导出ONNX用RKNN-Toolkit2转rknn格式板端用RKNNLite加载推理。先导出ONNXyolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 opset12这条命令生成best.onnx同时保留检测头的原始输出。注意如果导出时加nmsTrue会把NMS也导进去板端代码简单但兼容性差我更倾向导出不带NMS的版本自己在板端做解码。接着转RKNNfrom rknn.api import RKNN rknn RKNN(verboseTrue) # mean/std要与训练预处理一致YOLOv8默认是除以255 rknn.config(mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588) ret rknn.load_onnx(modelbest.onnx) if ret ! 0: print(load onnx failed) exit(1) # do_quantizationTrue 做INT8量化加速明显 ret rknn.build(do_quantizationTrue, datasetcalib_dataset.txt) if ret ! 0: print(build rknn failed) exit(1) rknn.export_rknn(best.rknn)逻辑说明RKNN的mean_values和std_values是在模拟训练时的归一化。YOLOv8官方推理会把像素除以255所以这里std填255、mean填0。calib_dataset.txt每行写一张jpg图片路径建议从验证集挑50-100张覆盖白天、黑夜、正脸、侧脸量化效果才稳。转换后如果精度下降明显先看校准图多样性或者把do_quantization改为False跑FP16再评估。板端推理代码from rknnlite.api import RKNNLite rknn_lite RKNNLite() rknn_lite.load_rknn(best.rknn) rknn_lite.init_runtime(core_maskRKNNLite.NPU_CORE_0) # 输入前把BGR转RGB并resize到640 img cv2.cvtColor(cv2.imread(test.jpg), cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) outputs rknn_lite.inference(inputs[img])说明RKNNLite推理得到的是多个feature map还需要在板端做YOLOv8解码包括置信度计算、bbox解码和NMS。这段后处理不短建议先用项目里验证过的解码脚本不要轻视。core_mask可以指定NPU核心多路并发时各分一核。4.3 公路汽车检测的落地技巧跟踪、计数与参数调节公路场景检测跑通容易跑好要调细节。第一推理参数不能照搬默认值远距离小目标置信度天然偏低conf_thres一般从0.25降到0.2如果公交车和卡车经常重叠把iou_thres从0.45降到0.4减少NMS误删。第二要做车辆计数时单帧检测不够通常用ByteTrack或BoT-SORT把检测框关联成轨迹再在画面上设虚拟线用track id跨线计数。ultralytics封装了跟踪推理yolo track modelruns/detect/train/weights/best.pt sourceroad.mp4 \ conf0.2 iou0.45 imgsz1280 persistTrue这条命令会启用跟踪模式persistTrue表示跨帧保留track idimgsz1280能明显提升小目标召回但推理时间变长要实测。如果视频里没有需要跟踪的目标也可以不加persist。第三固定机位的公路监控可以加ROI区域把栏杆、树枝框起来屏蔽减少误检。第四训练数据里必须混入雨雾和夜晚样本否则夜间漏检会非常严重。5. 避坑指南训练、转换、识别三个环节的翻车实录这里整理了几条我实际踩过、也帮别人排过的坑按“现象→原因→解决”写清楚可以直接对照排查。5.1 训练Loss突然变NaN模型权重直接报废现象训练到三十轮左右控制台输出train/box_loss变成nan后面所有loss全变nanbest.pt不再更新。原因大概率是标注数据里有非法值比如txt里类别ID超出nc范围、x_center是负数、width为0另一种可能是学习率过高导致梯度爆炸。解决先写脚本扫描所有标注txt发现超出[0,1]的坐标或不存在类别ID就打印文件名数据没问题就把lr0从0.01降到0.001或把optimizer从SGD换成AdamW。我有一次就是这个原因最后发现是Labelme转换脚本对空标注文件处理不当生成了无内容的txt被YOLOv8当成无目标图干扰了loss计算。所以空txt要么删掉要么确保图片真的有目标。5.2 CPU版训练慢到怀疑人生现象Ubuntu 20.04 CPU版在笔记本上训练yolov8n一个epoch跑了近二十分钟100轮要一天半。原因CPU训练本来就不适合大卷模型batch设置过大还会频繁交换内存workers0则数据加载拖后腿。解决如果只是毕设或验证把imgsz降到416、batch设为4、workers设4模型用yolov8n.pt并在命令行显式加devicecpu。但节省时间的有效方式还是租一张入门级GPUGTX1660Ti级别就能跑yolov8n-640。我的原则是CPU只做环境冒烟和推理训练一律上GPU不硬扛。5.3 ONNX转RKNN后检测框整体偏移现象板端RKNN推理能看到框但框位置明显偏大小也不对PC上用ONNX Runtime跑同一张图却正常。原因预处理不一致最常见是BGR和RGB通道顺序搞反或归一化参数和训练时不匹配另一个被忽视的原因是量化校准图太少只有十几张误差在特征图上叠加。解决固定通道顺序转换前先用onnxruntime验证ONNX输出和PyTorch输出一致校准图至少凑50张并覆盖不同亮度、远近。如果偏移仍很大改回do_quantizationFalse跑FP16确认是量化损失还是代码问题。5.4 门禁识别误识率高把学生A认成学生B现象人脸识别门禁用YOLOv8检测框裁剪后直接送特征模型现场错报率超10%。原因检测框只包住五官裁剪丢了下巴和发际线送到识别模型的人脸不规整另外YOLOv8的框在人左右晃动时会突然缩放导致特征向量不稳定。解决裁剪时按比例扩边前面3.4节已经给过代码更严格的做法是用关键点对齐让两眼保持水平并统一尺度。放行阈值和告警阈值之间留一个“待复核”区间能省掉大量误识投诉。这个坑在门禁系统设计里最容易翻车最后往往不是模型问题而是图像预处理流程太简陋。5.5 公路夜间和雨天漏检严重白天好用晚上摆烂现象车辆检测模型白天mAP50有0.92一到傍晚或下雨天漏检率翻倍。原因训练集里干净场景占绝大多数模型没见过低光照、反光和雨丝干扰夜间尾灯让特征分布和白天完全不同。解决标注阶段一次性加入夜间和雨天抽帧训练时打开MixUp、HSV和gamma增强模拟不同光照如果现场相机有强光抑制也可以先做图像预处理再送模型。这个坑属于数据场景覆盖不是网络结构问题补数据重训比调参数有效得多。6. 上线前最后一步用验证脚本和量化测试留下后悔药模型部署不是训练完就结束交付前我会做两件事固定测试集基线、量化前后对比。否则现场出了问题你连“以前是不是更好”都无法回答。6.1 用yolo val和自定义脚本记录模型基线最小验证命令是这样yolo val modelruns/detect/train/weights/best.pt datadatasets/face.yaml splittestsplittest表示用data.yaml里test对应的目录如果没配test默认用val。这里有一个很容易忽略的点验证时的conf和iou参数会影响mAP保存基线时必须固定这些参数。我见过有人换了权重说效果变好实际只是conf从0.3改成了0.2。我只依赖mAP还不够会额外用脚本统计误检率。运行yolo val不会告诉你“哪张图把垃圾桶当成了人”。简单做法是对固定测试集逐张推理记录假正例数和总框数比值这个指标比mAP更接近现场观感。量化对比同样要走这个流程INT8量化后的rknn权重在板端和PC端跑同一个测试集对比每张图的框坐标和置信度。掉点小于2%-3%可以接受超过5%就换混合量化方案或者只量化部分层。我的一个习惯是任何模型替换前先把旧权重在同一天同一测试集上的结果导出成txt存档新权重来了直接对比不一致的地方用现场截图一张张过。测试命令和参数都写进项目README防止三个月后自己都忘了当初怎么跑的。希望帮到你。本文还有配套的精品资源点击获取