简介:基于YOLOv8的智慧校园人脸识别与公路汽车检测项目,面向计算机视觉、毕业设计及智能交通应用开发者,提供一套完整可运行的源码与预训练模型。项目整合了人脸识别、车辆检测两大场景,包含Face_Main.py、Car_Track.py等核心脚本,并附有训练好的yolov8n-face.pt、yolov8l.pt等权重文件,以及dlib人脸关键点模型与测试视频,可快速复现识别、跟踪与统计流程。资源共40个文件,涵盖Python源码、pt模型、xml配置、png示例图片、mp4演示视频等类型,压缩包约334MB,结构清晰,便于按模块查阅。已有189人学习下载,项目经本地编译运行,评审分达95分以上,难度适中,适合毕业设计参考、课程实践或算法入门,能够帮助使用者理解YOLOv8在目标检测与人脸识别中的实际部署方法。
1. 基于 YOLOv8 的智慧校园项目,先分清人脸识别和车辆检测的任务边界
这个题目看起来是把两个模块塞进一个毕设,但「人脸识别」和「公路车辆检测」在工程上是两种相反的任务:一个求「不能漏人」,一个求「不能误检」。基于 YOLOv8 做这个项目,最可靠的落地路线是训两个检测权重——人脸分支只负责出框,后面再接特征比对模型完成身份确认;车辆分支直接用 YOLOv8 的输出画框、计数。适合正在做毕业设计、想跑通代码又能答上答辩问题的读者,也适合需要快速搭一套校园门禁加卡口原型的工程师。先把这条双任务的链路想清楚,后面的环境配置、训练、部署才不会绕路。
2. YOLOv8 的结构选型:C2f、解耦头与双任务模型怎么配
2.1 从 C3 到 C2f:YOLOv8 backbone 的改动到底解决了什么
YOLOv8 的网络结构可以拆成三段理解:backbone 负责特征提取,neck 负责多尺度融合,head 负责输出。backbone 里最核心的改动是 C2f 模块,它替代了 YOLOv5 的 C3(CSP Bottleneck with 3 convolutions)。C2f 的做法是先把输入经过一个 1x1 卷积分成两支,一支直连,一支串过 n 个 Bottleneck,然后把中间每一层的输出都 concat 到最终特征上。
这里和 C3 的关键差异在于:C3 只把最后一个 Bottleneck 的输出并入主路,C2f 把每一层的输出都收进来再拼接,相当于给梯度回传开了多条短路。对于人脸这种尺寸小、边缘特征密集的目标,C2f 的多层拼接让浅层细节和深层语义能同时进到 neck,对小目标召回有直接帮助。neck 部分仍然是 PAN-FPN,自顶向下传语义、自底向上补定位。head 则换成了 anchor-free 的解耦头,分类和回归分成两个分支输出,不再依赖预设 anchor。
anchor-free 对公路车辆检测的影响常被忽略。公路场景里车辆尺度跨度很大,近处的车能占画面三分之一,远处的车可能只有二三十个像素,而且长宽比从轿车到卡车差了几倍。用 anchor 的方案需要为每种尺度和长宽比预先聚类,聚不好就会拉低计数准确率。YOLOv8 的解耦头在每个特征图位置上直接预测「这个点离目标中心有多远」,配合 DFL(Distribution Focal Loss)学习边界框的分布,对尺度变化的鲁棒性比 anchor 类方案好一截。
2.1.1 模型尺寸怎么选:人脸和车辆不是同一个最优解
YOLOv8 提供 n/s/m/l/x 五个尺寸,参数量从 3.2M 到 68.2M。人脸检测的目标是别漏人,门禁漏一次就形同虚设;车辆检测的目标是别把树影、灯杆当车。常见做法是车辆检测用 YOLOv8s 或 YOLOv8m 就够,人脸检测在固定机位场景用 YOLOv8n 或 YOLOv8s 出框,把算力预算留给后面的特征提取模型。
| 模型 | 参数量 | COCO mAP50 | GTX1660Ti FP16 推理耗时 | 适合任务 |
|---|---|---|---|---|
| YOLOv8n | 3.2M | 37.3 | 约 3ms | 人脸检测、边缘设备 |
| YOLOv8s | 11.2M | 44.9 | 约 6ms | 人脸检测、车辆检测 |
| YOLOv8m | 25.9M | 50.2 | 约 12ms | 高密度车辆场景 |
| YOLOv8l | 43.7M | 52.9 | 约 20ms | 离线分析 |
| YOLOv8x | 68.2M | 53.9 | 约 35ms | 离线分析、精度优先 |
别把 COCO 的 mAP 当成自己数据集的预期值,那是 80 类平均值,换到自己标注的数据集后数值会完全不同。我见过不少毕设把 YOLOv8n 在车辆数据上刷到 0.9 以上 mAP50,但一到夜间或逆光就崩,原因是训练数据根本没覆盖这些光照条件,模型选型要跟着场景走,不是越大越好。
2.2 人脸识别为什么必须拆成「检测 + 特征比对」两段
这是整个项目里最容易在答辩时被问住的地方。YOLOv8 是人脸检测器,输出的是框和置信度,框里这个人是谁,YOLOv8 不负责。识别需要把一张脸映射成固定长度的特征向量,再和库里注册的特征做距离计算。
工程上有两种常见选型。一是 dlib 的 face_recognition 库,内置 ResNet 特征提取器,输出 128 维向量,调用简单,但遮挡和角度变化下精度一般。二是 InsightFace(ArcFace),输出 512 维向量,在遮挡、侧脸上的鲁棒性好很多,更符合智慧校园这种需要长期稳定使用的场景。ArcFace 的核心是在 softmax 里给类别间加角度间隔约束,让同类特征的夹角比传统 softmax 更紧,学出来的特征在阈值判断时区分度更高。
两段式的数据流用代码表示就是下面这个顺序,检测模型和特征模型各自独立加载:
from ultralytics import YOLO import insightface # 1. YOLOv8 只负责出人脸框 face_detector = YOLO("runs/detect/face/weights/best.pt") boxes = face_detector(frame, conf=0.4, imgsz=640) # 2. 按框裁剪并做仿射对齐(112x112) # 3. InsightFace 提取 512 维特征 rec_model = insightface.model_zoo.get_model("buffalo_l/recognition.onnx") emb = rec_model.get_embedding(aligned_face) # 4. 与注册库做余弦相似度比对,超过阈值判同一个人这段流程里,检测和识别用两个模型意味着两套推理开销。在 GPU 上不是问题,但如果要部署到香橙派这类边缘设备,就得考虑把两个模型都导出成 ONNX 放进同一个推理进程,或者用更轻的特征模型。
2.3 公路车辆检测的类别设计:多一类就多一分误检
公路汽车检测的类别数一般控制在 4 到 6 类:car、bus、truck、motorcycle、bicycle,最多加一个 person。不要一开始就分 sedan、suv、van 这种细类,标注一致性很难保证,同一个车型在不同标注员眼里会落进不同类别,训练出来的类别边界会互相污染。公开数据集里,UA-DETRAC 以车辆检测为主,适合做基准;BDD100K 包含 car、bus、truck、motorcycle、bicycle,还自带天气和时段标注,最适合公路场景;Cityscapes 侧重城市道路。做毕设的话,直接从 BDD100K 裁剪出需要的类别转成 YOLO 格式,比自己标几千张图高效得多。
3. YOLOv8 环境配置与训练自己的数据集:从安装到损失曲线判读
3.1 Windows + PyCharm 下 YOLOv8 环境搭建的最小可行组合
环境配置是检索热度最高的环节,pytorch、CUDA、显卡驱动的版本组合不对,第一个 import 就报错。当前这个阶段最稳的组合是 Python 3.10 + PyTorch 2.1 + CUDA 11.8,或者 Python 3.10 + PyTorch 2.3 + CUDA 12.1。GTX1660Ti 这类 6G 显存的老卡用 CUDA 11.8 更稳,原因不是性能差异,而是 CUDA 12.x 对较老的驱动要求更高,驱动版本停在 5xx 以下就会出现「no kernel image is available」的运行时错误。
用 conda 建独立虚拟环境是必做步骤,不要直接装在 base 里。下面这套命令在 Windows 和 Ubuntu 20.04 上都验证过,唯一前提是 N 卡驱动版本不低于 452.39(CUDA 11.8 的最低要求)。
conda create -n yolov8 python=3.10 -y conda activate yolov8 pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics装完先别急着训练,用最小推理验证 GPU 是否真的被 PyTorch 识别:
yolo predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg device=0输出里出现device:cuda且耗时正常,说明 GPU 可用。常见坑是 torch 装成了 CPU 版,在 PyTorch 官网命令里漏掉--index-url参数就会这样。验证命令是python -c "import torch; print(torch.cuda.is_available())",返回 True 才继续做数据集。
注意:如果
torch.cuda.is_available()返回 False,先检查pip list里 torch 的版本号是否带+cu118,不带就是装成 CPU 版了,从安装源开始排查,不要先怀疑显卡。
3.1.1 ubuntu20.04 上多一个 opencv 兼容步骤
Ubuntu 20.04 上装 ultralytics 会自动带上 opencv-python,但系统自带的 libGL 库可能缺失,报错libGL.so.1: cannot open shared object file。执行sudo apt install libgl1 libglib2.0-0 -y即可,Windows 没有这个问题。另外 pip 建议先升级到最新,否则解析 ultralytics 依赖链失败时会报莫名其妙的 ValueError,看起来像网络问题,实际上是 pip 版本太旧。
3.2 把自己的数据集转成 YOLO 格式:目录结构与 data.yaml
人脸和车辆训练数据的格式完全一样:每张图片对应一个同名 .txt 文件,每行是class_id cx cy w h,坐标是归一化到 0 到 1 的相对值,cx、cy 是中心点,w、h 是宽高。标注工具用 labelImg 或 X-AnyLabeling,X-AnyLabeling 支持用 YOLO 模型预标注后人工修正,标车辆能省一半时间。
数据集目录结构必须严格按下图组织,ultralytics 的 dataloader 只认这种结构:
datasets/ ├── vehicle/ │ ├── images/ │ │ ├── train/ # 训练图片 │ │ └── val/ # 验证图片 │ ├── labels/ │ │ ├── train/ # 对应的 yolo 标注 txt │ │ └── val/ │ └── data.yaml # 数据配置data.yaml 内容如下,path是数据集根目录路径,names的索引必须和标注文件里的 class_id 一一对应:
path: D:/projects/vehicle train: images/train val: images/val nc: 5 names: 0: car 1: bus 2: truck 3: motorcycle 4: bicycle训练集和验证集按 9:1 或 8:2 划分时,要按整个视频片段或整个场景切,不要把一个视频的连续帧同时分进 train 和 val。否则验证集会因为和训练集帧高度相似而虚高,答辩时被质疑数据泄漏很难解释。
3.3 训练自己的数据集:训练命令与损失曲线判读
数据集准备好之后,最简训练命令是:
yolo detect train data=vehicle/data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 patience=20 device=0参数说明:epochs=100是总轮数上限,配合patience=20早停,连续 20 轮验证集指标无改善就自动停止,实际训练通常停在 60 到 80 轮;imgsz=640是输入尺寸,车辆是大目标没必要上 1280,人脸数据集可以试 800 或 960 提升小脸召回;batch=16在 6G 显存跑 YOLOv8s 的 640 分辨率下是上限,爆显存就降到 8,并同步把 epochs 加大。训练到一半想看趋势可以随时打开runs/detect/train/results.png,那是最新的损失曲线。
| 参数 | 示例值 | 作用与注意事项 |
|---|---|---|
| imgsz | 640 | 车辆 640 即可;人脸可试 800 提升小脸召回 |
| batch | 8~16 | 6G 显存跑 YOLOv8s 建议 8,16 是上限 |
| epochs | 80~120 | 配合早停使用,不必跑满 |
| patience | 20 | 连续 20 轮验证指标无改善即停止 |
| lr0 | 0.01 | 学习率曲线锯齿明显时降到 0.001 试跑 10 轮 |
训练完成后重点看results.png里的三条线:
train/box_loss和val/box_loss:训练损失在降、验证损失升到某个点开始反弹或震荡,就是过拟合信号,此时应回退到验证损失最低的 epoch,ultralytics 会自动保存best.pt。metrics/precision和metrics/recall:门禁场景重 recall,卡口计数场景重 precision,两个指标的平衡点靠后面调 conf 阈值完成。val/box_loss一直降不下去且伴随val/cls_loss反弹,通常是类别不平衡,比如 car 占 80% 样本而 motorcycle 只有 3%,需要给少样本类别补数据或加强在线增强,而不是盲目加 epochs。
注意:
results.png里曲线剧烈锯齿,通常说明 batch size 太小或学习率过高,先试lr0=0.001跑 10 轮看趋势,不要直接重训。
4. 人脸识别与车辆检测的推理实现:两段式流程与参数调校
4.1 车辆检测推理代码:conf、iou 与 classes 三个参数的实际意义
模型训练完,推理代码的核心是把 YOLOv8 的原始输出转成业务结果。用 ultralytics 的 Python 接口,最短的车辆检测推理是这样的:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source="test_video.mp4", # 视频文件路径或摄像头设备号 0 conf=0.45, # 置信度阈值,低于此值的框丢弃 iou=0.5, # NMS 的 IoU 阈值 imgsz=640, # 推理尺寸,与训练一致 classes=[0, 1, 2], # 只输出 car/bus/truck device=0, ) for r in results: for box in r.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) xyxy = box.xyxy[0].tolist() # [x1, y1, x2, y2] 像素坐标 print(f"class={model.names[cls_id]} conf={conf:.2f} box={xyxy}")conf=0.45不是随便设的:它只过滤单次检测的置信度。iou=0.5是 NMS 时两个框重叠超过 50% 就合并。公路场景里追尾或并排时两个车的框 IoU 容易超过 0.5,NMS 会把其中一个吞掉。如果遮挡场景漏检严重,把iou调到 0.3 减少框被合并的概率;反过来,一辆车被框两遍说明太低,回调到 0.6。classes=[0,1,2]是业务过滤,在只统计机动车的卡口场景,排除摩托车和自行车比训练时多花一个类别的精力更干净。
4.1.1 视频流的线程化处理:先保帧率再谈精度
处理视频流时不要在主循环里又读帧又推理。OpenCV 的VideoCapture.read()是阻塞的,直接串行跑会导致画面掉帧。常见做法是开一个生产者线程读帧放进queue.Queue(maxsize=8),主线程从队列取帧做推理,队列积压超过 4 帧就丢弃旧帧。这样推理永远处理最新画面,对安防场景来说,丢帧比延迟更可接受,因为目标从入画到出画通常有几十帧的窗口。
4.2 人脸识别两段式实现:YOLOv8 检测框 + InsightFace 特征比对
人脸识别的推理比纯检测多一个特征提取环节。用 face_recognition 库实现最简单,但既然项目基于 YOLOv8,更合理的人脸识别组合是:YOLOv8 人脸检测模型负责出框,InsightFace 负责特征,检测头针对人脸充分训练,特征质量也远好于 face_recognition 的 128 维向量。
import cv2 import numpy as np from ultralytics import YOLO import insightface # 1. 加载人脸检测(YOLOv8) 与 特征提取(InsightFace) face_detector = YOLO("runs/detect/face/weights/best.pt") feature_model = insightface.model_zoo.get_model( "buffalo_l/recognition.onnx", # 512 维特征模型 providers=["CUDAExecutionProvider"], ) # 2. 注册库:姓名 -> 512 维特征向量,程序启动时加载一次 known_faces = {} for name, img_path in {"zhangsan": "db/zhangsan.jpg", "lisi": "db/lisi.jpg"}.items(): known_faces[name] = feature_model.get_embedding(cv2.imread(img_path)) def recognize(face_img): emb = feature_model.get_embedding(face_img) best_name, best_sim = "unknown", -1.0 for name, ref_emb in known_faces.items(): sim = np.dot(emb, ref_emb) / (np.linalg.norm(emb) * np.linalg.norm(ref_emb)) if sim > best_sim: best_sim, best_name = sim, name return best_name, best_sim cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break faces = face_detector(frame, conf=0.5, imgsz=640, device=0) for r in faces: for box in r.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) face_crop = frame[y1:y2, x1:x2] if face_crop.size == 0: continue face_resized = cv2.resize(face_crop, (112, 112)) name, sim = recognize(face_resized) if sim > 0.45: # 余弦相似度阈值 cv2.putText(frame, f"{name} {sim:.2f}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) else: cv2.putText(frame, "unknown", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)这段代码有两个容易踩的坑。一是get_embedding内部不做对齐,假设输入已经是对齐后的人脸,直接用检测框裁剪会有角度偏差导致特征质量下降。InsightFace 官方流程是先跑自带的 det 再跑 recognition,YOLOv8 的框直接拿过来用,最好注册和识别都走同一套裁剪逻辑,保持流程统一。二是相似度阈值 0.45 只是起点,不同摄像头、光照下要重新标定,方法在下一章说。
注册库的加载也要注意:不要每帧从磁盘读模板图,程序启动时把所有已知特征加载进内存,识别只做矩阵运算。模板数量到几千人时,把所有参考特征堆成一个大矩阵,一次性做矩阵乘法算全部相似度,比 Python 循环快两个数量级。
| 部署场景 | conf 阈值 | 相似度阈值 | 侧重点 |
|---|---|---|---|
| 人脸门禁 | 0.3~0.4 | 0.45~0.55 | 高召回,别漏人 |
| 公路车辆计数 | 0.5~0.6 | 不适用 | 高精确,别误检 |
| 夜间 / 低光照 | 0.2~0.3 | 0.40~0.50 | 容忍虚检,靠相似度兜底 |
提示:InsightFace 的 buffalo_l 模型首次初始化会自动下载权重,部署前先把权重文件缓存到本地,避免现场初始化时卡在下载上。
4.3 性能瓶颈分析与香橙派边缘部署
人脸双模型在 GTX1660Ti 上的实测参考值:YOLOv8s 人脸检测约 6ms,InsightFace buffalo_l 特征提取约 8ms,一帧单脸总耗时 15ms 左右。实际卡顿几乎总是出现在视频解码上而不是模型推理。用摄像头时把CAP_PROP_BUFFERSIZE设为 1,避免 OpenCV 内部缓冲堆积旧帧;设定CAP_PROP_FPS后不做额外 sleep,让推理速度自然限制帧率。
边缘设备上,香橙派 5 或 Jetson 系列的第一件事是导出 ONNX 并开启半精度。双模型从 GPU 换到 ARM 平台,推理延迟可能从十几毫秒涨到三百毫秒以上,此时换 YOLOv8n 检测加 MobileFaceNet 特征模型,比任何代码层面的优化都有效。显存或内存有限时,模型变小带来的收益是决定性的。
5. 阈值标定、ONNX 导出与部署前的验证技巧
5.1 用验证集的正负样本标定 conf 阈值
很多人把 conf 阈值当成拍脑袋的值,实际上它有客观标定方法。把 val 分割里所有检测框按置信度从高到低排序,再和真实标注框算 IoU,IoU 大于 0.5 的记为 TP,否则记为 FP。取不同 conf 阈值统计 precision 和 recall:
yolo val model=best.pt data=vehicle/data.yaml conf=0.1 iou=0.5然后看输出的 PR 曲线数据。人脸场景高召回优先,选误检还能接受的最高 recall 点;车辆计数高精确优先,宁可漏检也不把一个阴影当车,阈值就右移。这个标定过程应该在训练完当天做,因为不同数据集、不同光照下最优阈值差异很大。
5.2 导出 ONNX 并用半精度做部署推理
部署时脱离 ultralytics 的 Python 依赖,常见做法是导出 ONNX 后用 onnxruntime 推理,延迟比 PyTorch 推理低 30% 到 50%,还可以在无 GPU 的机器上跑 CPU 推理:
yolo export model=best.pt format=onnx opset=12 simplify=True dynamic=True导出后检查dynamic=True的效果,它允许 batch 维和宽高维动态变化。如果部署端固定输入尺寸,把 dynamic 关掉能再压几个百分点的延迟。需要 TensorRT 加速就在目标设备上执行trtexec --onnx=best.onnx --saveEngine=best.engine --fp16,TensorRT 版本必须和设备的 CUDA 版本匹配,否则报 engine could not be loaded。
5.3 一个值得复用的技巧:为两类任务维护两份独立的阈值配置
这个项目是双任务,但很多实现把两个模型共用一个 conf,这是最常踩的坑。正确做法是把人脸和车辆完全解耦:人脸模型的 conf 定在 0.3 到 0.4,因为侧脸和遮挡时置信度会掉到 0.4 以下,阈值太严会漏人,识别兜底靠相似度阈值;车辆模型的 conf 定在 0.5 左右,因为树影、水渍都可能产生 0.3 到 0.5 之间的虚检,必须用更严的阈值压掉。把两组阈值写进一个 YAML 配置文件,部署时只改配置不动代码。最后在真实场景录一段 5 分钟视频,分别统计漏报数和误报数,对照阈值调整方向,用数据说话,而不是答辩前临时调参。
本文还有配套的精品资源,点击获取