☰
基于YOLOv8改进的红绿灯倒计时识别:小目标检测与部署实践
2026/9/27 1:06:02 网站建设 项目流程

简介:面向智能交通与计算机视觉开发者,这套源码以改进YOLOv8模型为核心,实现红绿灯倒计时数字的实时检测与识别。针对真实路口的复杂光照与遮挡条件,算法在原始模型基础上完成70余项改进,涉及网络结构、特征提取与训练策略等,并配套完整数据集标注与训练流程,帮助读者掌握从数据准备、模型训练到部署验证的全链路方法。包内共26个文件,以19张图像样本、4个Python脚本及txt/docx/md说明文档为主,脚本覆盖训练、验证、预测与Web可视化交互等核心环节;说明文档则提供环境配置与复现指引,压缩包仅2.97MB,轻量易部署。配合前端界面,用户能直接查看信号灯状态与倒计时数字,并可扩展交通流量分析等功能,适用于智慧城市课题研究或项目原型开发。目前已有88人学习使用,按说明操作即可快速跑通整套识别流程。

1. 红绿灯倒计时识别为什么通用YOLOv8直接上线会翻车

红绿灯倒计时识别在城市智能交通里是一个典型的小目标场景:画面里的数字普遍不到30像素,白天有逆光反射,夜晚有灯盘光晕、车灯扫过和雨滴模糊。直接用COCO预训练的YOLOv8跑路口视频,白天勉强能用,一进夜间就漏检、误检成片,两位数变成一位数,红灯倒计时和绿灯倒计时互相串。标题里这套方案真正解决的不是“跑通YOLOv8”,而是把改进模型、数据集标注训练和Web前端可视化串成一条能复现、能部署的链路。适合正在做智能交通视觉、车路协同或基于yolov8的毕业设计的工程师和学生——你需要的不是单个权重文件,而是一套从采集到上线的完整闭环。

2. 拆解70余项创新点改进:倒计时数字场景的四类结构改动

拿到这类带“70余项创新点改进”字样的项目,我习惯先做减法。70多项听起来多,拆开就是四个方向:检测层、注意力、特征融合、轻量化。后面再加训练策略和数据增强来凑数,真正顶用的就那几个。下面按部署目标为GPU加边缘盒子双轨的场景讲,避免为了凑创新点把模型改到没法落地的程度。

2.1 小目标检测层:P2浅层特征为什么不能省

倒计时数字在1080p画面里的高度通常不到30像素,超过一半的字符集中在10到20像素之间。YOLOv8默认的三个检测头分别对应stride 8、16、32,stride 32那条分支对10像素以下的目标基本没有响应,stride 8是主力但特征图分辨率仍然不够,字符的笔画细节到了深层已经被卷积稀释得差不多。常见做法是加一个P2检测头,把backbone浅层stride 4的特征引出来,再和深层特征做融合。

在ultralytics的模型配置文件里,一种改动示意是这样的:

# yolov8_custom.yaml 片段(示意,需对齐你自己yolov8.yaml的行号) backbone: - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 1, C2f, [128, True]] # 2-P2 输出 # ... 继续到深层 head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 上采样后和P2层融合 - [[-1, 2], 1, Concat, [1]] - [-1, 1, C2f, [128]] # 检测头从1个变成4个,对应stride 4/8/16/32 - [-1, 1, Detect, [nc, [128, 256, 512, 1024]]]

这段yaml示范的是“加一路P2”的思想:先用上采样把深层特征放大到P2的尺寸,再通过Concat把浅层的笔画纹理拼进来。关键参数是最后一行Detect里的nc,必须和数据集的类别数一致;channels列表要和前面的C2f输出对齐,否则一加载就报shape mismatch。

加P2层之后FLOPs大约涨30%,显存占用同步上升。对于数字字符普遍大于30像素的数据集,P2带来的提升可能不到1个点,却明显拖慢推理。我的习惯是先看标注框尺寸分布:如果小框占比超过30%,P2就值得上;如果全是近景大字符,改输入分辨率比加检测层更划算。另一个折中是把训练图从640升级到960或1280,但这会让batch必须调小,训练时间拉长,不算白赚。

2.2 注意力机制:ECA/CBAM/SE三种选哪个

夜间红绿灯最大的干扰不是目标小,而是灯盘光晕把数字包住。光晕本质上是高亮背景,会让特征图的主通道被“过曝区域”占满,数字的纹理被压得很低。注意力机制在这里的作用就是重新分配通道权重,让网络更关注字符纹理而不是整片发光区。

SE只做通道注意力,参数极少,效果稳定;CBAM在通道基础上加了空间注意力,对遮挡有帮助;ECA用一维卷积替代SE里的全连接,计算量更小,是三者里最轻的。红绿灯数字场景我一般用ECA,插在C2f输出后面,因为空间注意力在字符粘连严重时会误增强整片光晕,反而帮倒忙。一个可用的ECA模块长这样:

# model/common.py 里新增ECA模块,插在C2f之后即可 class ECA(nn.Module): def __init__(self, c, k_size=3): super().__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.conv = nn.Conv1d(1, 1, kernel_size=k_size, padding=(k_size - 1) // 2, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): b, c, _, _ = x.size() y = self.avg_pool(x) # 全局平均池化,得到通道描述 y = y.view(b, 1, c) y = self.conv(y) # 1D卷积做跨通道交互 y = y.view(b, c, 1, 1) return x * self.sigmoid(y) # 对原特征图做通道重标定

参数说明:k_size控制跨通道交互范围,小模型用3,容量大的结构可以试5;类别的c是输入通道数,不需要手工指定。ECA不改变输出shape,所以插入后不需要改后续任何层。在夜间训练时,可以观察tensorboard里ECA输出的激活分布:如果光晕区域的通道权重被明显压低,说明注意力发挥了作用;如果激活分布和没加时差不多,问题不在注意力,而在特征融合。

2.3 检测头与特征融合:BiFPN、可变形卷积与YOLOv8 head改进的边界

yolov8 head改进是检索热度很高的方向,但很多人忽略了一个前提:YOLOv8默认的decoupled head已经把分类和回归分支拆开了,这在倒计时数字场景里已经够用,真正值得动的是neck部分的特征融合。BiFPN是常见选择,它给每条融合路径分配一个可学习权重,夜间某层特征被光晕污染时,网络自己会把这条路径的权重压低。

一个工程上够用的加权融合实现思路是这样:

# BiFPN加权融合的核心逻辑(示意,非完整实现) # p2_up是从深层上采样到P2的特征,p3是当前层原始特征 w1 = torch.relu(self.w1_fuse) w2 = torch.relu(self.w2_fuse) feat = (w1 * p2_up + w2 * p3) / (w1 + w2 + 1e-4)

这里的w1_fuse和w2_fuse是初始化为1的可学习参数,分母加1e-4避免除零。训练完后去tensorboard看这两个权重的最终值,哪个被压到接近0,就说明对应那条通路在帮倒忙,可以直接剪掉来提速。BiFPN的完整实现还有反复上下采样,对红绿灯这类单目标小场景收益不大,做一层加权融合就够了。

可变形卷积DCN对形变字符友好,但显存和推理时间都涨得太猛,边缘盒子上基本不用考虑。我的判断标准是:如果数字本身不形变,坏的只是光照,DCN的收益会被它的代价完全吃掉。真正要试的是给检测头加一层轻量上下文聚合,比如在分类分支前加一个3x3卷积扩大感受野,让数字附近的灯盘状态也能被感知。

2.4 轻量化方向:为RK3588和GPU双轨留好开关

城市路口组网部署不可能每路摄像头后面挂一张2080Ti。rk3588部署yolov8是现在被问得很多的落地路径:它走NPU需要转RKNN,int8量化后通常掉1到3个点。改进模型时如果一开始就堆满注意力、BiFPN,转RKNN时大概率遇到算子不支持,一步一回退。

常见的处理方法是先把backbone里的普通卷积替换成GhostConv,用通道剪枝砍掉冗余,再导出onnx转rknn。GhostConv的思路是让一部分通道走线性变换,另一部分走标准卷积,量化和算子映射都友好很多。下表是我常用的部署目标对比:

部署目标推理框架典型耗时主要踩坑
服务器GPUTensorRT FP163到8ms动态shape要固定,batch=1
RK3588 NPURKNN int820到40ms算子兼容、量化校准集偏差

参数说明:RKNN int8的量化校准集最好用夜间和白天各一半的样本,只用白天图片校准会导致夜间低亮度通道被压缩,掉点更严重。改进结构时尽量只用Conv、ReLU、Concat这些基础算子,少用softmax密度大的模块,否则rknn toolkit转换阶段会报不支持或反复回退CPU。这就是为什么我在改模型前先问清楚部署平台,而不是先把70项改进全叠上去。

3. 从路口视频到YOLO格式数据集:标注规范与转换脚本

数据集是这套系统里投入产出比最高的一环。很多人把精力花在堆改进模块上,最后发现夜间漏检的根因是夜间样本太少。这一章把采集、标注、转换、增强讲透,每一步都按可复现的标准来。

3.1 采集和抽帧:覆盖白天、夜晚、逆光与球机俯视角

红绿灯倒计时数据的来源通常是架设在路口的高位摄像头录像。常见做法是找一个允许临时架设设备的路口,录制一个下午加一个晚上,再把视频按固定间隔抽帧保存。抽帧脚本不复杂,但有几个细节容易翻车。

import cv2 cap = cv2.VideoCapture("intersection_01.mp4") interval = 5 # 每5帧取1帧 idx = 0 out_idx = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break if idx % interval == 0: # 裁掉天空和路面,只保留灯盘区域,缩小图片体积 roi = frame[100:900, 200:1600] cv2.imwrite(f"raw/im_{out_idx:06d}.jpg", roi, [cv2.IMWRITE_JPEG_QUALITY, 95]) out_idx += 1 idx += 1 if idx > 6000: # 控制单视频总量 break cap.release()

逻辑说明:interval决定样本量和相邻帧冗余度。5帧抽1帧能保证同一个数字在画面不同位置出现多次,又不至于全是近重复帧。裁剪ROI这步很重要:1080p原图里灯盘只占一角,裁掉无关区域后标注时能省大量时间,训练时也少很多背景干扰。抽完帧之后一定要人工快速刷一遍,把运动模糊、过曝和车辆遮挡灯盘的帧删掉,这步是整个标注流程里最便宜的清洗。

3.2 标注策略:数字区域框还是单字符框

这是第一个要拍板的设计决策,直接影响后续识别逻辑。方案A是把整个倒计时区域框成一个目标,类别叫countdown,数字识别交给后面的OCR;方案B是把每个字符单独框出来,类别直接是digit_0到digit_9,模型输出即识别结果。标题里要“检测与识别”一次做完,所以最终系统通常走方案B。

方案标注成本小目标难度识别准确率适合场景
A:区域框+OCR低低依赖OCR鲁棒性快速验证
B:单字符框高高模型端到端可控最终交付

两个方案的取舍很清楚:方案A标注量少,但两位数字粘连时OCR会读错,而且又多一个子系统要维护;方案B的模型直接输出数字类别,后处理只需要按x坐标排序拼成int,链路短,夜间的泛化能力掌握在模型手里。方案B的标注规范有三条硬规矩:字符只框发光的数字,不要把灯盘的暗色底框包进来;两位数字中间有间隙时务必分开框;遇到数字闪烁或半暗状态,宁可不标也不要框半个字符。

3.3 Labelme转YOLO格式:坐标归一化与过滤小框

用labelme标注用于yolov8是主流流程,输出是json,YOLO训练要的是txt。转换脚本本身不难,难在细节:坐标要除以宽高变成相对值,类别ID必须和data.yaml里的names顺序一致,太小或太偏的框要过滤。

import json, glob, os class_map = { "digit_0": 0, "digit_1": 1, "digit_2": 2, "digit_3": 3, "digit_4": 4, "digit_5": 5, "digit_6": 6, "digit_7": 7, "digit_8": 8, "digit_9": 9 } def convert(json_path, size_filter=8): with open(json_path, encoding="utf-8") as f: data = json.load(f) img_w, img_h = data["imageWidth"], data["imageHeight"] lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_map: continue pts = shape["points"] x1 = min(p[0] for p in pts) y1 = min(p[1] for p in pts) x2 = max(p[0] for p in pts) y2 = max(p[1] for p in pts) w, h = x2 - x1, y2 - y1 if w < size_filter or h < size_filter: continue cx = (x1 + x2) / 2 cy = (y1 + y2) / 2 lines.append( f"{class_map[label]} {cx/img_w:.6f} {cy/img_h:.6f} " f"{w/img_w:.6f} {h/img_h:.6f}" ) out = os.path.splitext(json_path)[0] + ".txt" with open(out, "w", encoding="utf-8") as f: f.write("\n".join(lines)) for j in glob.glob("labelme_json/*.json"): convert(j)

参数说明:size_filter单位是像素,过滤阈值按你的最小数字尺寸定,一般取8到12。小于这个尺寸的框学习不到纹理,只会给loss引入噪声。所有坐标都归一化到0到1之间,训练时才不会被letterbox拉伸搞错。脚本跑完要抽查几个txt:行数是否和json里的有效框数一致,有没有出现大于1.0的坐标值,空图是否对应空txt。这一步漏检,后面训练出来全是NaN loss。

3.4 训练集划分与增强:mosaic、mixup和模拟夜间光晕

数据划分建议用脚本随机切7:2:1,但注意一个前提:同一个路口视频连续抽出来的帧,不要同时落在train和val里,否则验证集等于看了训练集的内容,指标虚高。按“视频片段”为单位划分比按帧划分更诚实。

yolov8训练自己的数据集时,增强参数在训练命令里直接暴露:

yolo detect train \ data=traffic_light.yaml \ model=yolov8_custom.yaml \ imgsz=640 \ mosaic=1.0 \ mixup=0.2 \ fliplr=0.5 \ hsv_h=0.015 \ hsv_s=0.4 \ hsv_v=0.3

参数说明:mosaic前10个epoch可以开着,后面建议关掉或降到0.5,让模型在接近真实分布的数据上收敛;mixup不要超过0.3,红绿灯数字的边界纹理在mixup下容易被糊掉;fliplr可以大胆开,左右翻转不改变数字语义;hsv_v增强用来模拟各种LED亮度和屏幕老化色偏,夜间数据不够时它的价值最大。

提示:数据增强是最便宜的泛化手段,但它替代不了真实夜间样本。光晕模拟只能让模型不崩,做不到和真实车灯扫过完全一致。夜间样本至少要占到30%,否则一切结构改进都是缘木求鱼。

4. 用Ultralytics在Ubuntu20.04上跑通完整训练流程

这一章是从零到权重文件的完整路径。环境、配置、训练、验证四步走,每一步都会遇到让你想摔键盘的问题,这里把最常见的先踩掉。

4.1 环境搭建:CPU版本先跑通,GPU版本不踩CUDA坑

ubuntu20.04搭建yolov8环境cpu版本是很多人的第一步,因为可以先验证数据和代码链路。命令就那么几条,坑全在版本匹配。

# CPU版本,先验证代码和数据集 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics # GPU版本,先查驱动支持的CUDA版本再动手 nvidia-smi | grep "CUDA Version" conda create -n yolo python=3.10 -y conda activate yolo pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics

逻辑说明:CPU版本的关键是下载Pytorch的cpu wheel,否则pip会在nvidia源里绕圈子。装完在Python里跑import torch; print(torch.cuda.is_available()),输出True才继续,False说明装成了CPU版。GPU版本最常翻车的不是CUDA版本本身,而是torch和torchvision版本没对齐,安装时把它们固定成同一索引版本能省掉大半问题。

参数说明:cu121对应CUDA 12.1,如果驱动版本不够就降到cu118。CPU训练时把batch调到8,workers降到2,否则机器响应都困难。训练中途显存溢出时,先看batch和imgsz,别急着换小模型——多数情况下是cache=True把数据集全塞进显存了,关掉cache就行。

4.2 数据配置文件与模型参数:yaml里最容易写错的两个字段

训练前要准备好数据配置yaml和模型配置yaml。数据yaml最常见的两个错误:一是path写成绝对路径但不带train和val的相对子路径,二是names写成dict但代码期望list。

# traffic_light.yaml path: /home/workspace/traffic_dataset train: images/train val: images/val names: 0: digit_0 1: digit_1 2: digit_2 3: digit_3 4: digit_4 5: digit_5 6: digit_6 7: digit_7 8: digit_8 9: digit_9

说明:path是根目录,train和val写相对路径,这是ultralytics的标准读法,别在train里写绝对路径再让path多余重复。names保持list风格,严格按下标顺序对应类别;如果模型配置里的nc和这个列表长度不一致,训练时loss会异常抖动或直接报错。模型配置如果只做数字识别,nc就是10;如果还带红黄绿状态,nl要另算,别混在一个nc里。

我一般先用yolov8n.yaml跑通数据链路,确认loss能降下来再换改进结构。数据链路没跑通就上大模型,遇到问题根本分不清是数据错了还是模型错了。

4.3 训练命令与关键参数含义:从imgsz到epochs一次看懂

yolov8训练自己的数据集最常用的命令就是下面这一行。很多人照着别人的命令抄,结果batch太大OOM、lr太高loss爆炸,根本原因是不理解每个参数的含义。

yolo detect train \ data=traffic_light.yaml \ model=yolov8_custom.yaml \ pretrained=yolov8n.pt \ imgsz=640 \ batch=16 \ epochs=200 \ optimizer=SGD \ lr0=0.01 \ lrf=0.01 \ warmup_epochs=3 \ patience=30 \ cache=True \ device=0

参数说明:imgsz=640是速度与精度的平衡点,字符小于10像素先试960,显存不够就降batch。epochs不用一上来就300,先用100看loss曲线是否还在降,早停patience=30会帮你省时间。optimizer首选SGD,倒计时数字这种小目标对lr敏感,lr0从0.01起步,loss爆炸就降到0.005。lrf是最终学习率与初始学习率的比值,0.01表示最后收敛到初始值的百分之一,适合长时间训练。cache=True适合小数据集,能把全部图片加载到显存或内存显著提速;几万张的大数据集不开cache反而省事。

注意:同一个命令在不同GPU上的loss曲线不会完全一样。关键不是复现别人的loss值,而是复现“下降趋势”。趋势对,训练就在往前走。

4.4 训练完成后的验证:损失函数曲线图、PR曲线和混淆矩阵怎么看

训练完会在runs/detect/trainN/目录下生成一堆产物。results.png就是yolov8画损失函数曲线图最直接的来源,train/val的box_loss和cls_loss都在里面。先看val曲线有没有在最后阶段向上翘,翘了就是过拟合,epochs减到拐点附近重新训。然后看混淆矩阵里background被误判成digit的比例,这个数值往往占夜间误检的大头。

yolo detect val \ data=traffic_light.yaml \ model=runs/detect/train/weights/best.pt \ imgsz=640 \ batch=16

跑完自动生成混淆矩阵和F1曲线。如果background列亮点多,说明负样本不足或conf阈值太低,需要回到数据集补背景样本。我还会把置信度低于0.25的预测单独拉出来看图,通常一半是夜间小数字漏检,那是结构改进没到位,不是阈值问题;另一半是标注框画偏了,模型学歪了。

验证时不要只盯mAP,红绿灯倒计时场景更关心“连续帧数字串是否正确”。mAP高但两位数只认出其中一位,对系统毫无意义。所以验证阶段要加一个序列检查:把连续10帧的识别结果拼成时间序列,看数字是否递减、是否有跳变,这比单帧mAP更能反映真实路口体验。

5. 智能交通视觉系统的避坑清单:训练与部署阶段的五个常见翻车点

这个方向我实际做过的次数不少,踩过的坑往往不在模型结构上,而在数据语义和部署交界处。下面五条是按出现频率排序的真实经历,每条按现象、原因、解决展开。

5.1 两位数字粘连,模型把“15”识别成“1”或“5”

现象:10和9这种两位数字,模型输出只有一个框,类别在digit_1和digit_0之间漂移,最终秒数变成1或9。原因:标注时两个字符框距离太近,训练时NMS把两个框合并,或者特征图分辨率不足以把两个字符的纹理分开。解决:标注时强制分离,两个框之间至少留2像素间隙;后处理按x坐标排序后,如果两个框中心距小于0.6倍平均框宽,就判定为粘连,回炉重标。这个问题的根子在数据,不在模型。

5.2 夜间车灯反光把数字“洗白”

现象:白天mAP有0.93,晚上布设后漏检率明显上升,数字字符在画面上看起来还在,但模型就是检测不到。原因:训练集里夜间样本比例不到20%,光度分布失衡;车灯反光和LED高亮区处于同一灰度区间,特征被高亮背景淹没。解决:训练集重做,白天和夜晚各占一半;开启hsv_v增强,并在自定义增强里叠加高斯噪声模拟夜噪;给网络加ECA注意力压制大光斑。这类问题不改结构,光靠调阈值是堵不住的。

5.3 一个路口很准,换个路口就掉点

现象:A路口效果很好,迁移到B路口后数字识别率从0.9掉到0.7。原因:采集时只录了一个方向、一个球机高度,B路口的灯盘安装角度、LED色温、镜头透视完全不同。解决:至少录三个不同朝向的路口,按“路口”为单位划分train和val,不要按帧随机分;训练时加大hsv扰动,让模型学的是数字结构而不是某个路口的LED色。域随机化是这里性价比最高的手段。

5.4 测试集指标很高,接上视频流后FPS骤降

现象:离线评测一帧10ms,连上摄像头后只剩5fps,监控画面肉眼可见地卡顿。原因:视频流丢帧、推理和采集串行、前端可视化抢了推理线程。解决:推理线程只做resize加infer,显示线程另开;用队列存帧,队满丢旧帧,保证推理永远处理最新帧。这个坑和模型无关,但是上线观感最直接的翻车点,十个人里有八个先怀疑模型慢,实际是线程模型写错了。

5.5 手机屏幕被识别成倒计时数字

现象:路侧画面拍到行人手机,手机屏幕上的数字被模型当成digit_x输出,告警乱报。原因:训练负样本不足,模型学到了“发光数字”特征,而不是“红绿灯数字”的特征。解决:从采集视频里把非信号灯的发光体抽出来做负样本,用随机位置stamp到训练图像里;部署时加ROI约束,只允许信号灯区域产生检测框,ROI由前端界面绘制并保存。负样本策略对误检的抑制效果,比调conf阈值有用得多。

6. 从PyTorch权重到Web前端可视化:推理接口与实时交互界面

权重文件只是中间产物,智能交通系统最终要以Web界面呈现给值班人员。这一章用一个最小闭环把模型和前端串起来。

6.1 FastAPI封装推理接口:视频帧进、JSON结果出

模型训练完要落地成服务。FastAPI是目前最省事的方案,把推理循环包进独立进程,别和Web服务抢GIL。常见接口设计如下:

from fastapi import FastAPI, UploadFile import numpy as np, cv2 from ultralytics import YOLO app = FastAPI() model = YOLO("best.pt") @app.post("/detect") async def detect(file: UploadFile): data = np.frombuffer(await file.read(), np.uint8) img = cv2.imdecode(data, cv2.IMREAD_COLOR) results = model.predict(img, imgsz=640, conf=0.35, verbose=False) boxes, labels = [], [] for r in results: for box in r.boxes: labels.append(int(box.cls[0])) boxes.append([float(v) for v in box.xyxy[0]]) return {"digits": sort_digits(boxes, labels), "boxes": boxes}

逻辑说明:sort_digits按x坐标把单字符框从左到右排序,拼成字符串再转int,得到完整倒计时秒数。conf=0.35是调试出来的经验值,夜间可以降到0.25——这个参数要开放给前端配置。

6.2 WebSocket推送检测结果:从“问一次答一次”变成“主动推帧”

前端要实时,HTTP轮询不够用。WebSocket是这类大屏可视化最常用的通道,服务端每隔200ms推一次当前识别结果,前端按帧渲染。

const ws = new WebSocket("ws://192.168.1.100:8000/ws"); ws.onmessage = (evt) => { const data = JSON.parse(evt.data); document.getElementById("countdown").innerText = data.countdown; drawBoxes(data.boxes); // canvas画检测框 };

参数说明:推送间隔200ms意味着1秒最多5次刷新,人眼足够,带宽也很低。如果前端要做视频流叠加效果,最好把原始帧经RTSP转HLS播放器显示,检测框走WebSocket叠加,两者分离才不会互相卡。

6.3 前端交互界面的核心模块:监控画面、数字面板与异常告警

界面我习惯分成三块:左侧实时监控画面连视频流,画面上用canvas叠加检测框和当前秒数;右侧面板按红、绿、黄状态列出当前相位和倒计时;底部是异常告警条,当模型连续N帧识别出的数字不递减时,触发“信号灯状态异常”提示。ROI绘制功能放在设置面板里,值班人员可以手动框出信号灯范围,后端据此过滤误检。

这个界面的价值不只是展示,它也是排查模型的工具:当告警频率异常升高时,值班人员能直接看到是哪路相机的哪个ROI在误报,从而快速定位是新路口没标好还是模型需要补数据。我现在的习惯是上线任何可视化系统前,先拿一段录制的路口视频跑“回放测试”,因为浏览器播放和WebSocket推送各自有延迟,显示层的抖动用户会直接归因到模型头上。先把红线画成ROI约束、把conf参数留在前端配置项里,再谈模型优化。这套思路希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询