简介:这套资源是“基于YOLOv8的智能工厂危险区域电子围栏系统”完整实现包,主要面向毕业设计、课程设计学生以及工厂安全信息化工程师,解决危险区域人员闯入的实时监控与自动告警问题。系统基于YOLOv8目标检测算法,可自动识别人员或物体是否进入设定的电子围栏区域,检测到异常后触发警报或联动安全设备,源码全部开放,便于按需定制。
资源包共97个文件,以70个Python源码文件为核心,辅以4个PT模型权重、5个XML配置文件、2个TXT说明文档和1个MP4演示视频,压缩包总大小24.21MB,涵盖可视化页面设计、主程序、模型训练等清晰模块。目前已有39人学习下载。
下载后可获得可直接运行的检测服务、界面原型、模型训练脚本和最佳权重,配套文档详细说明安装步骤、功能操作与参数调优思路,适合快速搭建演示系统,也为课程设计或论文实验提供完整基础。
1. 危险区域电子围栏为什么绕不开 YOLOv8:从车间监屏到自动告警
车间里最让人头皮发麻的画面,是监控屏前没人盯着,而叉车正倒进吊装区。传统监控只能录像,不能判断人员是否越界,所以很多工厂开始上电子围栏:把危险区域在画面上圈出来,检测到人或设备进入就告警。这套基于YOLOv8的智能工厂危险区域电子围栏系统,就是把目标检测、区域判定、可视化界面打包好的一套完整源码,带训练好的权重、测试视频与部署教程,解压后按 README 装好环境就能跑起来。它解决的不只是“画面里有什么”,而是“画面里的人是否进入了不该进的地方”。适合正在做毕设或课程设计的学生,也适合工厂安全方向想快速搭一套原型验证的工程师,拿去做二次开发也顺手。
2. 目标检测选型与模型原理:YOLOv8、对比配置与训练收敛
2.1 为什么最终选 YOLOv8:与 Faster R-CNN、RTMDet 的横评
打开压缩包时,我注意到一个细节:config 目录下不只有 YOLOv8 相关配置,还躺着 rtmdet_m_8xb32-300e_coco.py、faster-rcnn_r50_fpn_2x_coco.py、rtmpose-m_8xb64-270e_coco-wholebody-256x192.py 几份配置。这说明打包者在选型阶段把主流方案都放在一起比过,而不是随手抄了一个模型。这个习惯很实用——你拿到手不仅能跑通,还能知道“为什么不选另外两个”。
目标检测选型要同时看三点:单帧延迟、小目标表现、部署成本。Faster R-CNN 是典型的两阶段检测器,RPN 先提候选框,再逐框分类回归,精度在老模型里算稳,但两阶段的结构在推理时绕不开区域提议这一步,工厂里多路视频同时跑时,GPU 占用很快就顶上去。RTMDet 是训练效率很高的单阶段模型,COCO 上的表现也亮眼,但它的生态和工具链在部署阶段没有 YOLO 系那么顺,很多边缘盒子对 YOLO 的转换支持最成熟。RTMpose 则是姿态估计的配置,出现在这里主要是做“人体关键点辅助判定”的备选,但对于纯区域闯入这类需求,关键点反而是多余开销。
YOLOv8 能被选为主模型,主要是三个结构点刚好卡在工业场景的诉求上。第一是 C2f 模块,它把不同层的梯度流拼接起来,特征复用更好,小尺寸目标比前代 YOLOv5 少丢框。第二是 Anchor-Free 检测头,直接预测目标中心到四条边的距离,不需要像 Anchor-Based 那样逐 anchor 匹配,配置省心,训练也更稳。第三是 TaskAlignedAssigner 正负样本分配,它把“分类得分高”和“回归 IoU 高”两个条件联合起来选正样本,对遮挡和密集场景比单纯 IoU 分配更抗干扰。
在电子围栏这个场景里,真正要检测的不是大而清晰的物体,而是远处走来的人、叉车的局部、被货架挡了一半的身体。这类小目标和截断目标对模型的召回率要求很高,宁可错报不能漏报,YOLOv8 在这类数据上的表现比 Faster R-CNN 更贴近实时筛选的使用习惯。下表是我对三份配置的横向理解,具体性能以你机器实测为准:
| 模型 | 阶段类型 | 单帧耗时(消费级 GPU) | 中小目标表现 | 部署与生态 |
|---|---|---|---|---|
| YOLOv8n/s | 单阶段 Anchor-Free | 10~40ms | 中上,召回高 | 转 ONNX/TensorRT 资料最多 |
| RTMDet-M | 单阶段 Anchor-Free | 10~30ms | 中上 | 配置较新,工具链相对少 |
| Faster R-CNN R50 | 两阶段 | 50~150ms | 中,小目标需调 anchor | 可转 ONNX,但组件多 |
2.2 数据集结构与预处理:从测试视频到 YOLO 标注
压缩包里的测试视频 abnoenal_video_five_type_test/ 目录下有一段连续监控片段,文件名带着完整时间戳,是典型的原始监控素材。项目命名 five_type 也暗示了训练时把它当多类别任务来处理。拿到这种原始视频,第一步不是急着标注,而是抽帧清洗:监控视频大量相邻帧几乎一样,直接全量标注会让训练集冗余,正样本重复刷高 loss。
我一般按每秒 5 帧的密度抽帧,既能覆盖动作连续性,又不至于让数据集膨胀到几万张。模糊帧和镜头切换造成的撕裂帧直接丢掉,判断标准用拉普拉斯方差,小于阈值的帧不保存:
# 从原始监控视频抽帧,清洗模糊帧,构建 YOLO 训练集 import cv2 from pathlib import Path video_path = Path("abnoenal_video_five_type_test/gB_9_s5_2019-03-07T16;31;48+01;00_rgb_body_005.mp4") frame_dir = Path("dataset/images/train") frame_dir.mkdir(parents=True, exist_ok=True) cap = cv2.VideoCapture(str(video_path)) fps = cap.get(cv2.CAP_PROP_FPS) frame_interval = max(1, int(round(fps / 5))) # 每秒抽 5 帧 frame_idx = 0 save_idx = 0 while True: ret, frame = cap.read() if not ret: break if frame_idx % frame_interval == 0: gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) score = cv2.Laplacian(gray, cv2.CV_64F).var() if score > 30: # 拉普拉斯方差低于 30 的帧视为模糊帧 out_name = frame_dir / f"frame_{save_idx:06d}.jpg" cv2.imwrite(str(out_name), frame) save_idx += 1 frame_idx += 1 cap.release() print(f"total frames: {frame_idx}, saved: {save_idx}")抽帧密度和方差阈值是两个最影响数据质量的参数。每秒 5 帧适合叉车、人员这类中低速目标,如果现场有快速运动的 AGV,把 5 改成 10 也能接受,代价是训练集翻倍。拉普拉斯方差 30 是个经验值,光照正常的监控画面通常在 30 到 200 之间,如果场景本身多暗角,阈值可以降到 20。抽出来的帧用常见标注工具标成 YOLO 格式:每张图对应一个同名 txt,每行是 class_id x_center y_center width height,坐标都归一化到 0 到 1。这个转换最容易忽略的细节是,类别 id 必须和 data.yaml 里 names 列表的下标严格对应,否则训练时类别全部错位,模型能收敛但预测结果全乱。
2.3 训练参数与收敛判断:train_mode.py 里值得抄的参数
压缩包“模型训练”目录下有两个预训练权重 yolov8n.pt 和 yolo11n.pt,还有 train_mode.py 和 Detection_video.py。从文件命名看,这是作者训练时留下的工作目录,可以直接在这个基础上改数据配置再训一个新的权重。训练脚本的核心还是 ultralytics 的 API,关键在超参怎么设。我用下面这组参数作为起点,跑工厂监控类数据集基本能稳:
# train_mode.py 中的训练核心逻辑(关键参数已加注释) from ultralytics import YOLO # 基础模型用 nano 版,显存占用最低,后续可以换 s/m 版提精度 model = YOLO("yolov8n.pt") results = model.train( data="dataset/data.yaml", # 类别名与训练/验证路径配置 epochs=120, # 监控场景数据量不大,120 轮足够收敛 imgsz=640, # 640 是速度与精度的常见折中点 batch=16, # 8G 显存可跑,显存小就降到 8 device=0, # 有 GPU 填 0,纯 CPU 训练填 "cpu" patience=30, # 连续 30 轮指标不涨自动停止 cache=True, # 图片预加载到内存,缩短每轮耗时 pretrained=True, # 加载 COCO 预训练权重做迁移学习 optimizer="SGD", # 数据量小时 SGD 比 AdamW 更稳 lr0=0.01, # 初始学习率,迁移学习不建议大于 0.01 augment=True, # 开启 Mosaic、随机仿射等数据增强 )这组参数里最值得说的是 patience 和 lr0。工厂监控数据一般几千到几万张,120 轮基本能走到平台期,但每台机器的数据分布不一样,patience=30 能在 mAP 停止增长时提前停掉,省时间也不容易过拟合。lr0=0.01 是迁移学习的保守起点,如果加载的是预训练权重,学习率过大会把已经学好的特征直接冲掉,表现为前几轮 loss 不降反升。我会先只看前 5 轮的 loss 曲线判断学习率方向,而不是直接调结构。
判断训练是否收敛,不要只看 loss 曲线,建议每 10 轮存一次验证集的 PR 曲线和混淆矩阵。YOLOv8 默认会输出 results.csv,我用它看 mAP50 和 mAP50-95 的走势:当 mAP50 连续 10 轮波动小于 0.5 个百分点,模型基本到瓶颈了,再训下去大概率是过拟合。这个“波动小于 0.5”的习惯我从跑监控场景开始一直在用,比盯着 loss 手感更直接。
3. 系统架构与核心代码:推理服务、围栏判定与界面联动
3.1 推理链路:detect.py、five_type_det_service.py 与 my_func.py 的分工
这套系统的代码结构是典型的“入口—服务—工具”三层。main.py 是界面入口,负责启动窗口和调度;five_type_det_service.py 把 YOLOv8 封装成一个可复用的检测服务类,输入一帧图像,输出检测框、置信度和类别;detect.py 是不依赖界面的命令行推理脚本,适合先验证权重能不能用;my_func.py 放的是辅助函数,比如坐标转换、告警联动里会用到的杂项。先跑 detect.py 再开 main.py,是排查环境问题最省事的方法。
five_type_det_service.py 的核心是一个 predict 方法,内部调 ultralytics 的 YOLO 类,返回的是 numpy 数组而不是模型原始结果对象,这样上层不管是界面还是告警模块都不用关心 torch tensor 的细节:
# five_type_det_service.py 中的检测服务核心片段 import numpy as np from ultralytics import YOLO class FiveTypeDetService: """把 YOLOv8 封装成面向画面的检测服务""" def __init__(self, weights_path="model/best.pt", conf=0.4, iou=0.5): self.model = YOLO(weights_path) # 加载训练好的权重 self.conf = conf # 置信度阈值,低于它直接丢弃 self.iou = iou # NMS 阈值,目标密集时调高 def predict(self, frame): # frame 是 BGR 格式 ndarray,推理前不需要额外预处理 results = self.model.predict( frame, conf=self.conf, iou=self.iou, verbose=False, # 关闭逐帧打印,避免刷屏拖慢速度 ) boxes = results[0].boxes.xyxy.cpu().numpy() # [x1, y1, x2, y2] 左上角右下角 scores = results[0].boxes.conf.cpu().numpy() # 每个框的置信度 clss = results[0].boxes.cls.cpu().numpy() # 每个框的类别 id return boxes, scores, clssconf 和 iou 是这套系统里调整手感的两个旋钮。conf=0.4 是在漏报和误报之间的常规起点:如果现场对漏报零容忍,把 conf 降到 0.25 左右,代价是画面里会出现少量误触发的框;如果误报导致告警太频繁,将 conf 提到 0.5,但要注意召回率会下降。iou=0.5 是 NMS 的常见默认值,如果场景里人员密集、互相遮挡,NMS 阈值提高到 0.6 能减少相邻框被合并的情况。我在现场调参时一般会先固定 conf,只动 iou 看视频,等框的稳定性满意了再去动 conf。
3.2 围栏判定:多边形区域与框底点命中逻辑
电子围栏的判定逻辑比很多人想的简单,但细节决定了误报率。工业现场划定危险区域通常是任意多边形,不能用矩形框一刀切。我的做法是把危险区域用多边形顶点列表描述,然后判断每个检测框的“底部中心点”是否落在多边形内部。
为什么不判断整个框是否在区域里?因为检测框有误差,框的上边缘经常飘出区域外,但人的脚部位置代表实际站位,误差最小。框的底部中心点比框中心点更接近“人站在哪”这个物理事实。
# 围栏判定:用底部中心点做多边形命中判断 import cv2 def is_inside_polygon(pt, polygon): """判定点是否在多边形内,polygon 为 [[x,y],...] 顶点列表""" poly = np.array(polygon, dtype=np.float32) # pointPolygonTest 返回 1 表示在多边形内,0 在边上,-1 在外部 return cv2.pointPolygonTest(poly, pt, False) >= 0 def check_fence(box, polygon, cls_id, fence_cls_ids): """围栏触发判定:只对指定的目标类别做区域检查""" if int(cls_id) not in fence_cls_ids: return False # 不在监控类别的目标直接跳过 x1, y1, x2, y2 = box foot_pt = (int((x1 + x2) / 2), int(y2)) # 底部中心点 return is_inside_polygon(foot_pt, polygon)这段逻辑里有三个容易被忽略的点。第一是 fence_cls_ids,这是一个类别白名单,不是所有检测目标都触发围栏。比如叉车可能被允许停在区域边缘装卸,但人不能进入,所以这两个类别在围栏判定里要区别对待。第二是 pointPolygonTest 的第三个参数设为 False,只返回位置关系不做距离计算,性能开销小。第三是顶点顺序,多边形顶点按顺时针或逆时针都可以,但不能自我交叉,否则判定结果完全不可信。
实际部署时我还会在触发判定上加一个去抖机制:连续 N 帧命中才真正告警,N 通常取 3 到 5。因为单帧检测偶尔会闪断,如果每帧弹一次告警,现场最后会关掉告警功能。这个去抖不在核心代码里,得自己在系统外面包一层状态机。
3.3 可视化界面:main.py 的线程模型与告警联动
main.py 用的是 Qt 界面,视频流读取和推理如果放在主线程,界面会卡死,移动窗口都成问题。正确做法是用 QThread 跑视频读取和推理,通过信号把帧回传到界面线程绘制。我拆过不少类似的界面工程,凡是界面卡死的,八成是把循环放在了主线程里。
# main.py 中基于 QThread 的视频检测线程示意 from PyQt5 import QtCore import cv2 class DetectThread(QtCore.QThread): # 第一个参数是原始帧,第二个参数是检测结果数据 frame_signal = QtCore.pyqtSignal(object, object) def __init__(self, video_source, fence_polygons): super().__init__() self.video_source = video_source # 摄像头索引或视频文件路径 self.fence_polygons = fence_polygons def run(self): cap = cv2.VideoCapture(self.video_source) if not cap.isOpened(): return # 打不开视频源时静默退出,界面层再提示 while not self.isInterruptionRequested(): ok, frame = cap.read() if not ok: break results = self.det_service.predict(frame) # 见 3.1 # 在这里做围栏判定,把命中结果叠加到 frame 上 self.frame_signal.emit(frame, results) cap.release()这里有个细节:cap.read() 在视频文件结束时返回 False,循环 break,线程自然结束;如果是摄像头,长时间运行可能遇到帧丢失,最好加一个重连机制。isInterruptionRequested 是让线程能够被界面安全中断的关键,关闭窗口时调用 thread.requestInterruption(),线程会在下一帧退出,不会出现退不掉的僵尸线程。
界面层拿到信号后,只需要把结果画到 QLabel 或 QGraphicsView 上。信号槽机制保证了帧绘制的频率不会被推理速度拖垮,但要注意 emit 的 frame 是同一个对象,多次 emit 之间如果修改它,上一帧的画面会被改动,稳妥做法是在 emit 前 copy 一份,或者只传绘制结果。工程上我还会在界面上加一个“阈值实时调整”的输入框,不用改代码就能调 conf 和 iou,这在现场调参时能省很多重启界面的时间。
4. 部署运行:环境准备、目录结构与两步启动
4.1 环境版本:Python 3.9 与依赖库匹配
部署的第一步是让环境和模型匹配。压缩包里能看到 detect.cpython-39.pyc,这说明作者打包时用的是 Python 3.9。虽然 ultralytics 现在支持更新的 Python 版本,但按原环境的版本搭最稳,避免第三方库出现兼容性差异。基础依赖包括:opencv-python、PyQt5、torch、ultralytics、numpy、pandas。其中 torch 的版本要和 CUDA 匹配,比如 CUDA 11.8 或 12.1 都有对应的 torch 轮子,装错版本最常见的现象是 import torch 正常但 model.predict 卡住或报错。
强烈建议用虚拟环境部署,不要往系统 Python 里灌包。监控场景的项目往往要给不同客户跑不同环境,一个 venv 出了问题直接删掉重建,比在系统里排查依赖快得多。下面是常规环境搭建流程:
# 创建并激活虚拟环境 python -m venv venv # Windows 下执行 venv\Scripts\activate,Linux/macOS 下执行 source venv/bin/activate # 安装核心依赖(按 README.txt 中给定的版本安装最稳妥) pip install opencv-python PyQt5 numpy pandas pip install torch --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics如果你拿到压缩包时里面带了 requirements.txt,直接 pip install -r requirements.txt 就行,不要手动逐个装,版本容易乱。torch 的安装方式我用的是官方 wheel 源指定 CUDA 版本的方式,这样能保证和 GPU 驱动匹配。纯 CPU 机器则不需要 CUDA 版本,装默认 CPU 版即可,只是训练速度会慢很多,但跑推理还能接受。
版本不对时最先出现的报错往往不是“版本错误”四个字,而是 predict 卡在加载权重那一步,或者报 KeyError。前者基本是 torch 与 CUDA 不匹配,后者多半是 ultralytics 版本与导出的权重结构对不上。遇到这两类报错,优先检查 pip list 里的版本号,而不是去改代码。
4.2 目录结构:每个路径都是干什么的
拿到压缩包先别急着运行,花两分钟把目录结构过一遍能省一半排查时间。下面是项目里的关键目录和文件清单:
| 路径 | 作用 | 说明 |
|---|---|---|
| main.py | 界面入口 | 启动后弹出监控界面,选视频源开始检测 |
| detect.py | 命令行推理脚本 | 不依赖界面,可单独验证权重与视频 |
| five_type_det_service.py | 检测服务封装 | 被界面和其他脚本调用 |
| my_func.py | 辅助工具函数 | 坐标转换、绘制等杂项 |
| model/best.pt | 训练好的模型权重 | 系统默认加载的检测模型 |
| 模型训练/ | 训练工作目录 | 含预训练权重、train_mode.py、Detection_video.py |
| config/ | 备选模型配置 | RTMDet、Faster R-CNN 等对比配置 |
| utils/ | 训练辅助模块 | loss、metrics、dataloaders 等底层实现,一般不用动 |
| abnoenal_video_five_type_test/ | 测试视频目录 | 用于验证检测效果 |
| UI/ | 界面资源 | 图标等静态资源 |
model/best.pt 是整个系统的核心,它决定检测效果,界面和逻辑都围绕它运转。如果你要换模型,把新的权重覆盖到 model/best.pt 就行,但要注意新权重如果类别数不同,five_type_det_service.py 里的类别名和 fence_cls_ids 也要同步改,否则出现类别 id 越界或围栏逻辑失效。
utils 目录是从训练框架里带出来的底层模块,日常使用不需要碰。看到里面有 loss.py、metrics.py、augmentations.py 这些文件,别手痒去改,你只是部署运行,改到训练逻辑反而会让权重加载报错。config 里那几份对比配置,是在做模型选型报告或毕设答辩时用的,想证明“为什么选 YOLOv8”就可以把这些配置和跑出来的指标贴进文档。
4.3 启动流程:先验证权重再开界面
我一般不建议第一次运行就直接 python main.py,因为一旦界面卡住或者视频源打不开,你分不清是推理的问题还是界面线程的问题。正确的启动顺序是分两步走。
第一步,用 detect.py 在测试视频上跑一遍,验证环境与权重是否正常:
# 用命令行推理验证环境,视频不存在时 detect.py 会报错提示 python detect.py --weights model/best.pt \ --source "abnoenal_video_five_type_test/gB_9_s5_2019-03-07T16;31;48+01;00_rgb_body_005.mp4" \ --conf 0.4 --iou 0.5注意视频文件名里带分号和加号,在 bash 里建议用双引号包住整个路径,否则分号会被解析成命令分隔符。这一步能正常弹出检测画面或输出结果视频,说明依赖环境没有问题。如果这一步就报错,优先检查 torch 和 ultralytics 版本,其次是视频文件路径。
第二步,启动可视化界面:
python main.py界面启动后选择视频文件或摄像头,加载完成后即可看到检测画面。如果界面能启动但画面黑屏,大概率是视频源路径错误或视频文件编码格式 OpenCV 不支持;如果画面有检测框但界面响应缓慢,问题出在 3.3 节讲的线程模型,检查 main.py 是否把推理放进了主线程。摄像头输入时注意索引,本机自带摄像头一般是 0,外接 USB 摄像头通常是 1,如果画面一直黑但视频文件正常,先试换索引。
5. 避坑与常见问题:训练、推理、界面的四类翻车现场
5.1 训练 loss 不降但 mAP 也不动
现象:train_mode.py 启动训练后,前 20 轮 loss 从 2.0 缓慢下降但 mAP50 几乎为 0,到 40 轮还在原地徘徊。
原因:这种情况九成是数据集标注与 data.yaml 的类别顺序对不上。YOLO 格式的 txt 里类别 id 是一个数字,它对应的是 data.yaml 中 names 列表的下标,而不是类别名称字符串。如果标注工具导出时 id 从 1 开始,而 names 下标从 0 开始,所有框都会被错判成相邻类别,损失函数能计算但梯度方向是乱的。
解决:抽 50 张图直接读 txt 首列数字范围,确认与 names 下标一一对应:
# 检查标注文件的类别 id 范围,看是否超出 names 长度 # 下面命令会递归打印 labels 目录下所有 txt 的第一列最大最小值 find dataset/labels -name "*.txt" -exec awk '{print $1}' {} \; | sort -n | uniq -c输出里的最大 id 如果大于等于 len(names),说明标注导出的 id 和 data.yaml 没对齐,回去改标注工具或重新映射,不要硬训。
5.2 推理画面掉帧,检测框一抖一抖
现象:同一段视频,detect.py 跑起来只有十几帧每秒,且画面中静止的人的框在左右跳动。
原因:掉帧首先检查模型尺寸,yolov8n 在消费级 GPU 上单帧应该在 20ms 以内,如果用了 yolov8m 或 x 版本,速度自然慢。框抖是因为置信度阈值偏低,检测框在目标边缘反复横跳,NMS 没能完全压制。
解决:先用模型尺寸做减法,界面场景优先用 n 或 s 版;框抖把 conf 从 0.4 提到 0.5,并适当调高 iou 到 0.6。如果还抖,需要检查推理代码里是否误开了数据增强,YOLO 的 predict 接口默认是不做增强的,如果自己往推理流程里加了随机仿射,必须去掉。
5.3 界面运行时间长了卡死,内存持续上涨
现象:main.py 连续跑 30 分钟后,界面无响应,任务管理器看到内存和显存都在缓慢上涨。
原因:这是典型的推理结果对象未释放。ultralytics 的 predict 返回的 Results 对象里包含 torch tensor,如果每帧都保留在信号队列里没清理,GPU 显存会被积压的检测结果撑爆。界面线程如果还存了每一帧的 QImage 用于重绘,内存也会涨。
解决:在信号槽中处理完结果后立即释放引用,或者只保留当前帧的绘制结果;视频读取线程用队列长度限流,队列超过 10 帧就丢旧帧。检测服务里对 predict 结果要显式 detach 和 cpu(),避免 tensor 一直留在 GPU 上:
# 推理服务中及时释放 GPU tensor 的写法 boxes = results[0].boxes.xyxy.detach().cpu().numpy() scores = results[0].boxes.conf.detach().cpu().numpy() clss = results[0].boxes.cls.detach().cpu().numpy() # detach() 断掉梯度链,cpu() 把数据搬到内存,numpy() 转数组5.4 电子围栏误报多,或该报的不报
现象:叉车路过区域边缘一直告警,而人员弓着身子从监控死角进入却没有任何反应。
原因:误报多是因为把叉车也加入了围栏判定类别,或者区域多边形画得太靠近通道,检测框底点稍微一晃就入线。漏报是因为用检测框中心点而非底点做判定,人的框在远处时中心点还在区域外,实际脚已经越线。
解决:围栏判定只用底部中心点做多边形命中;区域多边形的边要留至少 10 到 20 像素的缓冲区。如果现场有多路摄像头,每个摄像头的区域多边形不同,不能把一组顶点复用到所有画面,这是一定要分开配的。边界去抖的 N 帧机制也要按摄像头帧率调整,30 帧视频和 15 帧视频的连续命中判断单位不一样。
6. 换自己的场景重训:数据、标注与迁移学习的三个技巧
如果你想把这套系统从示例视频换到真实的车间布局,重训比直接改参数更靠谱。先说数据:不要只盯着测试视频抽帧,真实的危险区域监控一定包含大量“没有人的场景”,这类负样本反而最重要。我建议正样本和负样本按 1:3 混入训练集,负样本会让模型收敛后不轻易对空区域乱报。
再说标注:YOLO 的矩形框在标注人员时,最容易犯的错是框只包住了躯干,漏了脚。电子围栏判定用底部中心点,如果框在脚的位置提前截断,实际越线时框底点还没进入区域,判定就慢了半拍。我自己的习惯是标注时先让框底边贴住脚底,再回头收紧左右,这个顺序比反过来容易得多。如果标注工具只能出多边形,记得转换时取多边形的最小外接矩形,而不是手拉一个更大的框。
最后是训练策略:类别变了之后,直接在项目 best.pt 基础上继续训练会残留旧类别影响,不如删掉最后的检测头重新训。具体做法是用官方预训练权重 yolov8n.pt 或 yolo11n.pt 重新开始,train_mode.py 只改 data 路径和类别数。批次大小可以试着减半,保证单个 batch 里正样本数不至于太少。作为快速验证,先训 30 轮看 mAP50 是否上 0.6,如果上了再拉长到 120 轮。哪怕效果不理想,也先别急着堆数据,回去抽查 100 张标注图的框位置,大多数场景问题都出在这里。
有一次我带着一套现成权重直接换到新车间,偷懒没做负样本补充,结果一晚上告警响了七十多次,全是空的传送带偶尔闪过的反光。从那以后,我每次换场景重训都强制走一遍“抽帧 500 张、看一眼标注、先训 30 轮看趋势”的老流程,再也不直接拿现成模型硬扛新环境。希望这套流程和这篇拆解能帮到你,拿到压缩包后建议先跑通原有的 best.pt,再按自己的数据逐步替换。
本文还有配套的精品资源,点击获取