简介:基于YOLOv8的智慧校园人脸识别与公路汽车检测项目,面向目标检测与人脸识别方向的学生,适合用于毕业设计、课程设计或工程实训。项目以校园门口学生出入监控为场景,使用YOLOv8模型完成人脸检测与跟踪,再利用dlib模型提取128维特征,判断是否为校内学生,绿色放行、红色告警,同时统计识别数量;并提供车辆检测功能,覆盖公路场景应用。压缩包共34个文件,包括预训练模型、Python脚本、示例图片、演示视频、特征数据文件等,大小约334MB,目录结构清晰,便于按模块对照学习。已有459人学习使用,完整覆盖从模型加载、人脸识别到结果判断的实现流程,可独立运行体验,也可继续改进或集成到更大系统,适合快速掌握目标检测与人脸识别的实战方法。
1. 基于YOLOv8的智慧校园人脸识别与公路汽车检测:一次跑通两套任务
学校门口早晚高峰的人脸识别通行,和公路监控里的车辆检测计数,看上去是两种完全不沾边的任务,但在这份基于YOLOv8的智慧校园人脸识别和公路汽车检测项目里,它们被同一个 YOLOv8 推理框架串成了一个可直接运行的工程包。压缩包里既有 Face_Main.py 这套校园人脸识别主程序,也有 Car_Track.py 这套公路车辆检测程序,还附带 yolov8l-face.pt、yolov8l.pt、dlib 特征提取模型和中文字体,解压后把依赖补齐就能跑起来。适合拿来做课程设计、毕业设计,也适合想从零接触 YOLOv8 目标检测、目标跟踪和 dlib 人脸识别的进阶学习者。它解决的痛点很具体:校园闸机怎么通过人脸特征判断校内人员,公路视频怎么稳定跟踪并统计车辆,而不是只停留在训练一个检测器的层面。
2. 项目链路拆解:YOLOv8 检测 + 目标跟踪 + dlib 128 维特征比对,为什么不是一套模型走到底
2.1 两个模型分路:人脸与车辆对检测器的要求完全不同
YOLOv8 是目前目标检测领域最常用的框架之一,它把 Anchor-Free、C2f 特征层和任务解耦头结合到一起,推理速度比上一代更快,训练和部署流程也简单。项目里分别放了 yolov8n.pt、yolov8l.pt、yolov8n-face.pt、yolov8l-face.pt 四个权重文件,这不是随便堆的,而是针对两个场景做了分工:yolov8n.pt 和 yolov8l.pt 是 COCO 预训练的通用目标检测权重,能识别人、车、狗等 80 类物体;yolov8n-face.pt 和 yolov8l-face.pt 是人脸专用权重,专门在 WIDER FACE 这类人脸数据集上训练过。校园门口的人脸框很小,而且经常半遮挡,如果用通用模型去框人脸,会出现框偏大、框漂移的情况,所以在 Face_Main.py 里优先用 yolov8l-face.pt 做人脸检测,公路场景里的车是大目标,用 yolov8l.pt 就够了。
实际加载模型时,常见写法是先用 ultralytics 库的 YOLO 类加载权重文件,然后调用 predict 或 track 方法。项目中的 Car_Track.py 和 Face_Main.py 也是基于这套 API 写的,只是不同脚本传入的模型路径不同。这里要特别注意,YOLOv8 官方权重里没有 face 系列,yolov8l-face.pt 这种是第三方在 YOLOv8 基础上用人脸数据集训练出来的,所以下载资源时不要用官方的 yolov8l.pt 去替代,否则人脸检测会退化成通用行人检测,识别逻辑直接失效。
from ultralytics import YOLO # 人脸场景:必须使用人脸专用权重 face_model = YOLO("model/yolov8l-face.pt") results = face_model.predict("media/Student.mp4", conf=0.45, save=True) # 公路场景:使用 COCO 通用权重,并过滤出车辆类别 vehicle_model = YOLO("model/yolov8l.pt") results = vehicle_model.predict("media/car.mp4", classes=[2, 5, 7], conf=0.30)这段代码的逻辑很直接:先加载两个不同的模型,分别处理人脸视频和车辆视频。conf参数控制置信度阈值,高于阈值的检测框才会保留。classes=[2, 5, 7]是 COCO 数据集中 car、bus、truck 三个类别的编号,用来把检测结果过滤到只保留车辆,避免把行人、骑自行车的人也算成车。如果你在项目里看到 Car_Track.py 的检测框包住了行人,多半就是没有做类别过滤,后面避坑章节会再展开。
2.2 Face_Main.py 的识别流程:track 之后才轮到 dlib
Face_Main.py 的完整识别流程可以拆成四步。第一步用 yolov8l-face.pt 在视频帧上检测人脸框;第二步用 YOLOv8 内置的 track 能力对检测框做跨帧跟踪,给每个人脸分配一个稳定的 track id;第三步对跟踪到的人脸区域调用 dlib 的 shape_predictor_68_face_landmarks.dat 提取 68 个关键点,再用 dlib_face_recognition_resnet_model_v1.dat 计算 128 维人脸特征向量;第四步把这 128 维向量和 dataset 里的已知学生特征做欧氏距离比较,距离小于阈值就判定为校内学生,框变绿色,否则是外来人员,框变红色。
这里最容易混淆的是「检测」和「跟踪」的分工。YOLOv8 本身是单帧检测器,它不知道当前画面里的人脸和上一帧是不是同一个人。track 技术的作用就是给检测框排队,通过相似度和运动信息把同一张脸在前后帧里串起来。项目摘要里强调“通过该模型中的 track 技术实现检测出的人脸进行自动跟踪”,实际用到的就是 ultralytics 里的 model.track(),它会自动给每个目标分配box.id。后续 dlib 特征提取只针对跟踪成功的人脸做,而不是每一帧对全画面做,这样能减少重复计算,也能让识别结果更稳定。
import cv2 import numpy as np import dlib from ultralytics import YOLO # 初始化模型 face_model = YOLO("model/yolov8l-face.pt") sp = dlib.shape_predictor("model/shape_predictor_68_face_landmarks.dat") facerec = dlib.face_recognition_model_v1("model/dlib_face_recognition_resnet_model_v1.dat") cap = cv2.VideoCapture("media/Student.mp4") while cap.isOpened(): ret, frame = cap.read() if not ret: break # 1. YOLOv8 检测人脸 results = face_model.track(frame, persist=True, conf=0.45) if results[0].boxes is not None: boxes = results[0].boxes.xyxy.cpu().numpy() ids = results[0].boxes.id.cpu().numpy() if results[0].boxes.id is not None else None for i, box in enumerate(boxes): x1, y1, x2, y2 = map(int, box) face_crop = frame[y1:y2, x1:x2] if face_crop.size == 0: continue # 2. dlib 关键点与 128 维特征 dets = dlib.get_frontal_face_detector()(face_crop, 1) if len(dets) == 0: continue shape = sp(face_crop, dets[0]) descriptor = np.array(facerec.compute_face_descriptor(face_crop, shape)) # 3. 与预存特征比较,distance < 0.5 认为是校内学生 # distance = compare_with_dataset(descriptor) # 绿色或红色框这里省略这段代码演示了主循环的骨架。persist=True是 track 的关键参数,意思是让跟踪器保留上一帧的目标状态,否则每次调用 track 都等于重新检测,无法维持同一目标的稳定 id。conf设 0.45 是我在校园监控场景里常用的值,太低会有大量误检框,太高又会漏掉远距离小脸。face_crop是当前人脸区域,dlib 在这个小图上做关键点提取,比全图提取快很多。项目里预存的 128 维特征放在 dataset 中,你可以理解为是一个“注册库”,每位学生用一张正脸照片注册,程序运行时会逐帧拿现场人脸去库里查找最接近的向量。
2.3 Car_Track.py 的车辆检测:track id 就是计数的钥匙
公路汽车检测相对人脸识别要简单一些,难点不在识别,而在计数。如果只做单帧检测,同一辆车在视频里连续出现几十帧,计数器就会算几十次,数据完全失真。Car_Track.py 的做法是利用 YOLOv8 的跟踪模式,让每辆车在离开画面之前保持同一个 track id,计数时按 id 去重。这样一辆车从画面右侧驶入左侧,不管中间被其它车遮挡,只要跟踪器没有丢,最后就只算一次。
from ultralytics import YOLO model = YOLO("model/yolov8l.pt") results = model.track("media/car.mp4", persist=True, classes=[2, 5, 7], conf=0.30, iou=0.50, save=True) for r in results: if r.boxes is None or r.boxes.id is None: continue boxes = r.boxes.xyxy.cpu().numpy() clss = r.boxes.cls.cpu().numpy() ids = r.boxes.id.cpu().numpy() for box, cls, track_id in zip(boxes, clss, ids): print(f"track_id={int(track_id)}, class={int(cls)}, box={box}")iou=0.50是 NMS 的 IoU 阈值,值越大越容易保留重叠的检测框,值越小越容易合并相邻框。车辆检测场景里 0.5 属于中间值,能避免道路上的两辆并排车被误合并成一辆。classes过滤同样重要,公路视频里出现行人和自行车很正常,如果不过滤,最后的计数结果会包含非机动车,报告就没法看了。
从整个链路来看,项目的技术选型是合理的:人脸检测要求模型对密集小目标敏感,所以用 face 专用权重;车辆检测要求类别过滤和稳定跟踪,所以用通用权重加 track id。dlib 的 128 维特征向量可以做到几十毫秒级比对,对校园门口这种人数不多的场景完全够用,不需要上大型人脸识别服务。
3. 环境配置与第一次运行:从压缩包到 UI 窗口出现
3.1 安装依赖:ultralytics、dlib、opencv 的版本坑
这份资源里没有 environment.yml 或固定 requirements.txt 的完整内容,从 README.md 和文件结构看,核心依赖主要是 ultralytics、opencv-python、dlib、numpy、torch。如果你在 Ubuntu 20.04 上搭建 YOLOv8 环境且只有 CPU 版本,最需要注意的是 torch 和 torchvision 的版本匹配,以及 dlib 编译依赖。常见做法是先装好 cmake 和 gcc,再装 dlib,否则会报CMake was not found或Failed to build wheel for dlib。
# Ubuntu 20.04 CPU 环境 sudo apt update sudo apt install -y cmake build-essential python3-pip pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics==8.1.34 opencv-python dlib numpyWindows 用户如果遇到 dlib 编译失败,需要先安装 Visual Studio Build Tools,并且勾选 C++ 桌面开发工具。这里多说一句,dlib 版本不要盲目追新,19.24.x 对 Python 3.8 到 3.10 的支持比较稳定,我之前在 Python 3.11 上装 dlib 翻过车,编译报一堆语法错误,后来退回 Python 3.10 一次通过。
3.2 文件清单里哪些权重必须保留
压缩包里的 model 目录是项目的命脉,少了任何一个都会导致运行时报文件不存在。按照项目正文,model 文件夹下至少需要保留以下六个文件:
| 文件 | 用途 | 缺失后果 |
|---|---|---|
| yolov8n.pt | 通用轻量检测权重,可用于重训 | 训练车辆模型时无法加载预训练 |
| yolov8l.pt | COCO 通用大模型,用于车辆检测 | Car_Track.py 无法运行 |
| yolov8n-face.pt | 轻量人脸检测权重 | 低配机器无法做人脸推理 |
| yolov8l-face.pt | 高精度人脸检测权重 | Face_Main.py 无法运行 |
| shape_predictor_68_face_landmarks.dat | dlib 人脸关键点模型 | 无法提取 68 点特征 |
| dlib_face_recognition_resnet_model_v1.dat | dlib 人脸识别模型 | 无法生成 128 维特征 |
另外还有 SimHei.ttf 中文字体,这个容易忽略。OpenCV 默认的 putText 画中文会乱码,项目里用这个字体在识别结果上显示“校内学生”“校外人员”等中文标签,如果路径不对,程序会跑到绘制阶段才报错,而且报错信息不一定是指字体,很莫名。第一次运行前,把 SimHei.ttf 放到和主脚本同一个目录,或者直接把项目里原有的相对路径保持住。
3.3 修改 Face_Main.py 的输入源和参数
拿到资源后的第一个目标不是理解代码,而是让识别窗口弹出来。Face_Main.py 默认可能是读取视频文件,也可能读摄像头,这取决于 README 里的说明。我一般会先在代码开头找到视频读取那一行,把它固定改成media/Student.mp4,确认数据来源可控。
import cv2 # 把视频源改成项目自带的学生视频,便于第一次跑通 video_path = "media/Student.mp4" cap = cv2.VideoCapture(video_path) # 如果要用摄像头,改成 cap = cv2.VideoCapture(0) conf_thres = 0.45 # 人脸检测置信度阈值 iou_thres = 0.50 # NMS IoU 阈值 match_distance = 0.55 # dlib 特征欧氏距离阈值参数不是越大越好。conf_thres调到 0.6 以上,人脸识别框会明显变少,远距离的同学直接被忽略;conf_thres调到 0.2 以下,墙上的人像海报也会被识别成人脸。match_distance决定了判断“校内学生”的松紧程度,阈值越小越严格,0.55 是我按项目里 rec-face 目录下这些登记照测试过的经验值,如果发现认识的人也变成红色,可以适当放宽到 0.65,但要注意误识率也会上升。第一次运行时先不要改 dlib 部分,只改视频路径,等窗口正常显示检测框后,再逐步调参。
3.4 运行 Car_Track.py 与识别阈值调整
车辆检测脚本的运行方式类似,但更推荐在命令行里通过参数控制,方便反复测试。假设项目里 Car_Track.py 已经支持命令行解析,可以这样跑:
python Car_Track.py --video media/car.mp4 --weight model/yolov8l.pt --conf 0.30 --iou 0.50如果脚本不支持命令行参数,就直接改文件里的默认值。车辆检测的conf阈值推荐 0.25 到 0.35,太高的代价是漏检远处的小车,太低的代价是把路牌阴影误检成车。公路场景光照变化比校园门口大,建议先跑一遍 car.mp4,观察结果目录 result 下生成的视频,重点看弯道和树荫处的检测框是否稳定,再决定阈值方向。
运行结束后,项目会在 result 目录里输出带框的视频。这里有一个判断标准:如果同一辆车在连续帧中 track id 频繁变化,比如从 12 变成 45,说明跟踪断开,需要降低 conf 或调整 iou。如果 id 稳定但计数重复,说明去重逻辑没生效,要去检查 Car_Track.py 里是否用了集合存储已统计的 id。第一次跑通不需要追求完美,能看到绿色或红色框在视频上跟着目标走,就说明环境配置和权重文件都是完整的。
4. 训练自己的数据集:把校园名单和公路车辆接进 YOLOv8
4.1 数据准备:视频抽帧、标注与 YOLO 格式转换
项目自带的 rec-face 是登记照,model 里的权重是现成的,但如果想把某个班级的学生或者某种特定车辆识别得更准,就得训练自己的数据集。第一步是收集图像。如果你有监控视频,不要直接把视频扔给 YOLO 训练,要先按间隔抽帧,避免相邻帧高度相似影响训练集的多样性。
import cv2 import os os.makedirs("images", exist_ok=True) cap = cv2.VideoCapture("media/car.mp4") frame_id = 0 saved = 0 while True: ret, frame = cap.read() if not ret: break # 每 10 帧抽 1 帧,车辆高速运动时可以每 5 帧抽 1 帧 if frame_id % 10 == 0: cv2.imwrite(f"images/frame_{frame_id:06d}.jpg", frame) saved += 1 frame_id += 1 cap.release() print(f"保存了 {saved} 张图片")抽帧之后需要用 labelme 或 LabelImg 手动标注。labelme 标注保存的是 JSON 格式,YOLO 训练需要的是每个图片对应一个同名 txt 文件,每行格式为类别序号 x_center y_center width height,坐标都归一化到 0~1。我习惯在标注完成后写一段转换脚本,而不是手工改文件,因为几十张图手改还能忍,几百张图手改会崩溃。
import json import os import glob # 假设 labelme 生成的 JSON 都在 json_dir 下 json_dir = "labelme_json" txt_dir = "yolo_labels" os.makedirs(txt_dir, exist_ok=True) class_map = {"person": 0, "car": 1, "truck": 2, "bus": 3} for json_path in glob.glob(os.path.join(json_dir, "*.json")): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] txt_name = os.path.splitext(os.path.basename(json_path))[0] + ".txt" lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_map: continue points = shape["points"] x_min = min(p[0] for p in points) x_max = max(p[0] for p in points) y_min = min(p[1] for p in points) y_max = max(p[1] for p in points) x_center = (x_min + x_max) / 2 / img_w y_center = (y_min + y_max) / 2 / img_h width = (x_max - x_min) / img_w height = (y_max - y_min) / img_h lines.append(f"{class_map[label]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(os.path.join(txt_dir, txt_name), "w", encoding="utf-8") as f: f.write("\n".join(lines))转换脚本里最需要注意的是坐标归一化,很多人漏掉除以宽高,训练时报 shape mismatch。另外类别编号必须和 data.yaml 中一致,不要随意调整顺序,否则模型学到的类别和推理时的映射对不上。
4.2 训练命令:yolov8n.pt / yolov8l.pt 的合理超参
数据准备好之后,在项目根目录新建 dataset 文件夹,把 images 和 yolo_labels 放进去,再写一个 data.yaml。然后就可以开始训练了。项目自带的 yolov8n.pt 和 yolov8l.pt 都是很好的预训练起点,不要从零初始化,否则小数据集很难收敛。
# data.yaml path: dataset train: images val: images names: 0: person 1: car 2: truck 3: bus# 用 yolov8n.pt 做轻量训练,适合先验证数据没问题 yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 device=0 optimizer=AdamW lr0=0.001 # 用 yolov8l.pt 做高精度训练,显存够时就选这个 yolo detect train data=data.yaml model=yolov8l.pt epochs=150 imgsz=640 batch=8 device=0 optimizer=SGD patience=30这里每个参数都有实际意义。epochs不是越大越好,关键看损失曲线;imgsz是输入尺寸,640 是训练和推理的速度平衡点,想提高小目标检测能力可以试 768,但显存占用会明显上升;batch受显存限制,常见做法是先用 batch=16 试,显存不足就减半;optimizer会影响收敛速度,我自己的经验是小数据集用 AdamW 更容易快速调到低 loss,数据集超过 5000 张时 SGD 会更稳。patience=30表示 30 个 epoch 内验证集指标没有提升就提前停止,避免过拟合。
训练完成后会在 runs/detect/train 下生成 best.pt,用它替换 project 里的对应权重文件即可。注意替换前备份原模型,因为模型经过微调后特征分布会变化,再检测原始视频的效果不一定更好。
4.3 dlib 人脸特征库的更新方法
YOLOv8 训练解决的是“人脸的框在哪里”,dlib 解决的是“这张脸是谁”。如果学校换了新班级,不需要重新训练 dlib 模型,只需要把新学生的登记照加入特征库。项目正文中 rec-face 目录下的图片命名是“编号-姓名.png”,比如 47-唐晶.png,这就是一个现成的注册机制。你的任务是把这些图片转成 128 维特征向量并保存,新版学生重新放一张正脸照即可。
import dlib import numpy as np import glob import os sp = dlib.shape_predictor("model/shape_predictor_68_face_landmarks.dat") facerec = dlib.face_recognition_model_v1("model/dlib_face_recognition_resnet_model_v1.dat") detector = dlib.get_frontal_face_detector() feature_list = [] label_list = [] for path in glob.glob("rec-face/*.png"): img = dlib.load_rgb_image(path) dets = detector(img, 1) if len(dets) != 1: print(f"跳过 {path}, 检测到 {len(dets)} 张脸") continue shape = sp(img, dets[0]) descriptor = np.array(facerec.compute_face_descriptor(img, shape)) feature_list.append(descriptor) # 文件名去掉编号前缀,保留姓名 label = os.path.basename(path).split("-", 1)[1].replace(".png", "") label_list.append(label) np.save("face_features.npy", np.array(feature_list)) np.save("face_labels.npy", np.array(label_list))这段逻辑每次运行会把 rec-face 目录下的所有登记照重新生成特征库,所以新增照片后重跑一次即可。len(dets) != 1的检查很实用,有些登记照是合影,dlib 可能检测出多张脸,直接跳过能避免错误特征入库。如果你的登记照角度偏侧,dlib 正脸检测器可能检测不到脸,这是 dlib 模型的固有限制,解决办法是尽量使用证件照式正脸图。
4.4 用 test.py 批量验证精度与速度
项目里有 test.py,这类脚本的作用通常是对指定视频或图片做一次性推理,输出检测结果和时间。训练完新模型后,不要直接上 UI,先用 test.py 跑几个测试文件,确认模型没有退化。
python test.py --weights runs/detect/train/best.pt --source media/Student.mp4 --conf 0.45如果 test.py 支持打印每帧耗时,你能直观看到模型推理速度。以 yolov8l-face.pt 为例,在 GPU 上大约能跑到 30 到 50 FPS,在 CPU 上可能只有 5 到 10 FPS。如果发现 FPS 太低,先确认代码里是否用了model.predict(frame)而不是model(frame),两者逻辑相同,但 predict 多了一层预处理封装,在循环里会慢不少。这个细节我在低配机器上踩过,换成直接调用模型对象后速度提升明显。
5. 避坑指南:人脸识别与车辆检测里最容易翻车的 5 个细节
5.1 人脸框闪烁:track 参数和 NMS 阈值的关系
现象:视频中同一个人脸上的人脸框忽大忽小,并且每隔几帧就消失一次,重新出现后 track id 已经变了,导致 dlib 重复提取特征。
原因:模型对人脸的置信度本来就波动,特别是侧脸和低头时分数会掉到 0.45 以下。如果conf设得太低,会有大量低质量检测框参与 NMS,框的位置上下跳;如果conf设得太高,又会出现丢帧。跟踪器persist参数没有保持 True 时,每次调用 track 都重新初始化跟踪器,会更加放大这种闪烁。
解决:把conf稳定在 0.4 到 0.5 之间,iou设为 0.5,并确保track(..., persist=True)。如果还是闪烁,对人脸框做一次轻度平滑,例如保留前几帧的框坐标平均值再画框。我在校园场景里的经验是,宁可漏掉单帧的人脸,也不要让跟踪器频繁重建,因为 dlib 特征提取是针对跟踪到的人脸,一旦 id 断开,识别结果就会跳变。
5.2 dlib 特征比对误报:欧氏距离阈值要分开设
现象:实际不在名单里的人,视频里却显示绿色学生框;而名单里的学生,有时候识别成红色。
原因:dlib 的 128 维特征在同一校园场景下,不同人之间的欧氏距离可能只有 0.5 左右,特别是发型和眼镜相似时。项目里如果用单一全局阈值 0.5 判断所有陌生人,必然有交叉。另一个隐性原因是登记照和现场摄像头分辨率差太多,导致特征向量细节丢失。
解决:先用已有的 rec-face 目录计算库内两两距离,统计出最大类内距离作为基准。具体做法是每张登记照生成特征向量后,任意两张算欧氏距离,如果最大距离已经超过 0.5,阈值就要跟着放大。更稳妥的方式是不设全局阈值,而是给每个学生生成均值向量后,再为每个人都保存一个容忍半径,比对时取最小距离除以该学生的半径,得到归一化分数。
5.3 车辆检测把行人当车:类名过滤与输入尺寸
现象:Car_Track.py 输出的视频里,路人被框成 car,计数结果明显偏大。
原因:yolov8l.pt 是 COCO 全类别权重,能同时检测行人和车辆。如果代码直接使用model.track(frame, persist=True),没有指定 classes,检测器会把所有 COCO 类别都留下,再靠后续计数组件去区分是哪一类,一旦组件没写类别判断,人就被算成车。
解决:在 track 调用中强制加classes=[2, 5, 7]。如果过滤后仍然发生误检,比如把路牌误检成车,则把imgsz从 640 提升到 736 再试一次,大输入尺寸对小细节更友好。如果误检来自树影和光照,则提高conf到 0.35,并限制检测区域只保留道路区域,可以用像素坐标裁剪掉画面两边的建筑区域。
5.4 GPU 显存不足:batch 与 imgsz 的妥协
现象:训练刚开始就报CUDA out of memory,进程直接被杀,没有生成任何权重。
原因:yolov8l 模型参数量本身就比 n 系列大,训练时的梯度占据大量显存。如果 batch 设 16、imgsz 设 640,常见 6G 显存会直接爆掉。很多教程默认给你 batch=16,这是坑,因为你不知道对方显存多大。
解决:第一步把 batch 降到 4,imgsz 降到 640;如果还报错,imgsz 降到 512。第二步开启amp=True,混合精度训练能省约三分之一显存。我一般会先用yolo detect train ... batch=4 imgsz=640 amp=True做一次 20 epoch 的快速验证,确认数据和流程没问题后,再用大 batch 跑正式训练,不要把正式训练直接压在第一次运行上。
5.5 中文标签乱码:SimHei.ttf 到底该放哪
现象:识别框上的中文名字全部显示成□□□□,或者程序在画框时报奇怪的font type错误。
原因:OpenCV 内置的cv2.putText只能绘制 ASCII 字符,对中文直接返回乱码。项目准备了 SimHei.ttf,但没有把字体路径正确传给绘制函数。另一个常见原因是字体文件在项目根目录,而脚本在 model 目录下执行,相对路径解析不到。
解决:在绘制中文前,先用cv2.imread确认字体文件存在,然后通过 PIL 加载字体绘制到图像上,不要用cv2.putText。简单做法是把字体转成图片后叠加到原帧。如果只是想让中文不报错,可以把所有中文标签改成拼音或英文,但毕业设计展示时会显得不够专业。把那句代码一次性封装好,后续换任何模型都不再碰这个坑。
import os from PIL import Image, ImageDraw, ImageFont font_path = os.path.join(os.path.dirname(__file__), "SimHei.ttf") font = ImageFont.truetype(font_path, 24)6. 进阶用法:用识别结果驱动门禁闸机和车辆计数上报
6.1 把结果写成 JSON 行协议
识别结果如果只停留在视频框上,很难和校园闸机、车辆统计系统对接。我一般会在 Face_Main.py 的识别循环里增加一个结构化输出,把每一帧的识别内容写成 JSON,行协议便于实时消费。
payload = { "time": timestamp, "track_id": int(track_id), "label": name, "is_student": bool(is_student), "distance": float(distance), "box": [x1, y1, x2, y2] } with open("output.log", "a") as f: f.write(json.dumps(payload, ensure_ascii=False) + "\n")这段代码的价值在于,下游门禁系统只要读这个日志,就能根据track_id和is_student判断是否开闸,不需要重新跑一次检测。
6.2 车辆计数的稳定化:用 track id 去重
车辆计数的进阶技巧是用集合记录已经过线车的 id。常见做法是在画面中央画一条虚拟线,当车的跟踪框中心跨过这条线且 id 不在集合中,就计数加一。这样能避免同辆车在画面中反复被计。注意在 id 进入集合后,不要立刻删除,否则车辆在画面里绕一圈会被再计一次。
6.3 一个验证技巧:用 result 目录回放对比
每次改完参数,不要只看控制台输出,要回放 result 目录下生成的视频。我会把同一段视频用不同阈值各跑一遍,导出到不同子目录,然后用双屏对比。这个方法在项目迭代里帮了我很多次,尤其是调 dlib 匹配阈值时,肉眼对比绿色框和红色框的数量比看数值更直观。
我从这个项目里最大的教训是:模型权重和跟踪参数都是“场景相关”的,校园门口和公路监控需要完全不同的阈值,不能一套参数走天下。从那以后我每次部署这类 YOLOv8 工程,都强制自己先准备一段目标场景的真实视频,跑通后再调参数,避免在项目自带演示视频上表现完美、换到实际现场就翻车。希望这份拆解能让你少走弯路,把这套资源真正用到自己的场景里。
本文还有配套的精品资源,点击获取