简介:这套基于深度学习的车辆特征分析系统资源包,面向Python初学者与车辆识别方向学习者,可用于课程设计、毕业设计或算法入门,解决车辆品牌、类型、颜色及车牌的自动识别问题。包内提供完整的Python源码、训练脚本、前端展示页面以及训练好的模型权重,可通过上传图片直接体验识别流程。资源共1826个文件,压缩包约925MB,其中包含1561张jpg车辆图片构成的数据集,另有py/pyc代码文件、pth模型参数文件、html/js/css前端交互文件、docx说明文档及字体图标等辅助资源,目录结构清晰,便于按需取用。目前已有27人学习,适合作为学习深度学习视觉任务的完整参考,能帮助读者理解图像数据收集、模型训练、推理展示与界面封装的全流程,也可在此基础上扩展车牌识别等应用。
1. 这个标题真正要解决的,是让机器“看懂”每一辆车
项目标题把“车辆”写成了“车俩”,但要做的事其实很明确:用 Python 和深度学习,把监控视频或抓拍图片里的每一辆车,自动识别出颜色、车型、品牌、车牌号这些特征,组成一条结构化的车辆档案。这个方向在停车场月卡识别、安防平台按“红色 SUV”检索、交警卡口的车流分类统计里都是刚需,而且相比传统的图像处理方案,深度学习在复杂光线、多角度、遮挡场景下确实更稳。
我做这类系统的主要诉求很简单:摄像头拍到的车,进了系统就该变成一行文字记录,而不是一张无法检索的图片。这个标题指向的方案,本质上就是一套“检测 + 分类 + 车牌识别”的串联流水线,全程用 Python 落地。适合谁?适合手上已经有一批车辆图像数据、想从零搭一套可用系统的工程师,也适合需要评估“这个方向到底值不值得做”的团队。接下来我按自己实际做过的方案拆开讲,从选型到避坑,每一层都可以照着复现。
2. 车辆特征分析系统:先拆解特征维度,再定模型架构
2.1 车辆特征分析到底在分析什么:从颜色、车型、品牌到车牌
做系统之前,必须先想清楚“特征”两个字指什么。在我的项目里,车辆特征通常拆成四个维度:车身颜色、车辆类型、品牌型号、车牌号码。这四个维度的技术难度完全不一样,不能混在一个模型里处理。
车身颜色是全局特征,但受光照影响极大,同一辆车在正午和路灯下拍出来可能是两种颜色。车辆类型(轿车、SUV、卡车、巴士)相对简单,类间差异大,一个轻量分类网络就能拿很高的准确率。品牌型号是最难的部分,比如说区分“宝马 3 系”和“宝马 5 系”,属于细粒度识别,类间差异极小,必须用更强的骨干网络。车牌号则是另一个技术栈,它对单字符的精度要求极高,而且不同省份的车牌格式不同,新能源车牌还多一位。
在实际系统里,我会把车牌单独拆出去,因为它是“身份标识”而不是“外观特征”,识别逻辑也完全不一样。很多刚入门的同学把颜色、车型、车牌放在同一个模型里输出,结果训练时 loss 互相干扰,车牌识别精度上不去。正确的做法是,先跑一个目标检测模型把所有车辆框出来,然后对每个框并行做颜色分类、车型分类和车牌识别,三条支路互不干扰,这也在工程上方便单独迭代——车牌不准就只换车牌模型,不会动到颜色分类。
这里还有一个容易忽略的点:车头车尾的特征分布差异。车头有进气格栅、车标、大灯,车尾只有尾灯和文字标牌,同一个品牌的车头和车尾在视觉上差别很大。如果你的数据源是卡口抓拍,通常只有车头或者只有车尾,那训练出来的模型在另一视角下会明显掉点。我在做方案设计时,第一件事就是确认数据来源的视角分布,这决定了后面所有标注工作的方向。
2.2 三层模型架构:目标检测、细粒度分类、车牌识别的选型逻辑
明确了特征维度之后,模型选型就顺理成章了。我采用的是“检测 + 分类 + 车牌识别”三层架构,每层各选一个主力模型,用部署代价和精度表现来做取舍。
第一层是目标检测,负责找出画面里的所有车辆。这个环节我首选 YOLO 系模型,目前常用的是 YOLOv8,因为它端到端训练、部署生态成熟,CPU 和 GPU 都能跑。YOLO 输出的每个检测框附带置信度和类别标签,这里类别只做“车辆”和“背景”,不做细分类,因为细分类交给第二层专门处理。这样检测模型的负担小,小目标召回率也能保住。如果画面里车辆密集,比如高速收费站排队,可以换 YOLOv8 的更大尺度版本,但推理耗时相应增加。
第二层是特征分类,包括颜色、车型、品牌。颜色和车型用 ResNet 或 EfficientNet 这类图像分类网络就够了,输入是检测框裁剪出来的车辆图片。品牌细粒度识别需要更高分辨率和更强骨干,我一般用 ResNet101 或者 EfficientNet-B4,在 ImageNet 预训练权重上做微调,比自己从零训练收敛快得多,效果也好得多。这一层的输出是多个属性的独立概率分布,注意是“独立多标签”而不是“单标签”,因为一辆车同时有颜色、车型、品牌三个属性,不能做成互斥的多分类。
第三层是车牌识别。这个环节很多项目直接用目标检测加 OCR 两段式,但更高效的做法是用 LPRNet 这类端到端车牌识别网络,它把定位和字符识别合在一起,支持不定长车牌。考虑到实际部署环境,车牌识别对算力的要求要严格控制,否则整个链路延迟会被它拖垮。下表是我在项目中常用的选型方案,直接拿来参考:
| 任务 | 推荐模型 | 输入尺寸 | 单张推理耗时(GPU) | 备注 |
|---|---|---|---|---|
| 车辆检测 | YOLOv8s | 640×640 | 约 5ms | 密集场景可换 YOLOv8m |
| 颜色/车型分类 | ResNet50 | 224×224 | 约 2ms | 轻量且精度够用 |
| 品牌细粒度识别 | ResNet101 | 224×224 | 约 4ms | 类间差异小时再上 |
| 车牌识别 | LPRNet | 94×24 | 约 1ms | 端到端不定长识别 |
这个架构看起来直接,但落地时每一步都有隐藏的麻烦。比如 YOLOv8 检测框如果裁得太紧,把车牌区域切掉了一半,后续车牌识别模型必然翻车;分类模型的输入如果是检测框,那么检测框的定位精度直接决定了分类输入的质量。所以整个系统不是四个独立模型拼在一起,而是需要把前后级联的接口设计好,数据维度对齐好,才算真正“搭起来”。
3. 准备车辆数据集:从采集到标注的完整流程
3.1 数据集来源与目录规划:把分类数据集和检测数据集分开放
模型架构定好之后,最大的工作量落到数据上。车辆特征分析系统对数据质量极其敏感,我见过太多项目模型结构抄得一模一样,只因数据集不同,效果天差地别。先说公开数据集,车检方向常用的是 UA-DETRAC、Cityscapes 这类自动驾驶数据集,品牌细粒度识别可以用 CompCars 或 PKU-VD。但它们各有局限:UA-DETRAC 偏车辆检测,没有品牌标注;CompCars 有品牌和车型标注,但图片大多是网图,和监控视角的实拍图差异很大。所以我的建议是公开数据集只用来做预训练和验证流程,最终的系统训练还是得基于自己的场景数据。
自采数据集的第一步是目录规划。我习惯把检测和分类的数据分开管理,因为它们的标注格式完全不一样。检测数据需要边界框坐标,分类数据只需要文件夹名称代表类别。一个规整的数据目录结构大概是这样的:
vehicle_data/ ├── detection/ │ ├── images/ # 检测用原始图片 │ │ ├── train/ │ │ └── val/ │ ├── labels/ # YOLO 格式标注,和 images 一一对应 │ │ ├── train/ │ │ └── val/ │ └── classes.yaml # 类别配置文件 ├── color_cls/ │ ├── train/ │ │ ├── black/ │ │ ├── white/ │ │ └── red/ │ └── val/ │ ├── black/ │ ├── white/ │ └── red/ ├── brand_cls/ │ ├── train/ │ │ ├── audi/ │ │ ├── bmw/ │ │ └── toyota/ │ └── val/ │ ├── audi/ │ ├── bmw/ │ └── toyota/ └── plate_rec/ ├── train/ # 车牌图片,按字符序列命名 └── val/这里有几个关键决策。检测数据用 YOLO 格式而不是 COCO 格式,因为 YOLO 格式每个图片一个 txt,训练时读取效率高,也不用维护庞大的 json 文件。分类数据的文件夹直接按类别名称命名,用 PyTorch 的ImageFolder可以一行代码加载。车牌识别数据不用文件夹分类,而是按文件名命名字符序列,因为 LPRNet 的标注是变长字符串。
数据量参考我做过的最小可行方案:检测 5000 张、颜色分类每类 2000 张、品牌每类 1500 张、车牌 3000 张。这个规模足以支撑一个内部演示系统,但离生产级别还差得远——生产级至少翻五倍。数据采集渠道主要是自建摄像头、公共监控回放、合作单位的脱敏数据,不要直接爬取网图,因为版权和场景偏差都是大坑。
3.2 把 COCO 标注转成 YOLO 格式:转换脚本与四个边界坑
拿到原始标注后,第一步通常是格式转换。很多已有数据是 COCO 格式,即一个总的 json 文件存放所有图片的标注信息。要把数据喂给 YOLOv8,必须转成每张图片一个 txt 的格式。我在这里写过一个转换脚本,踩过几个边界坑,直接分享出来:
import json import os from PIL import Image def coco_to_yolo(json_path, img_dir, out_dir, img_width, img_height): """ 将 COCO 格式标注转换为 YOLO 格式 txt json_path: COCO 标注文件 img_dir: 图片目录 out_dir: 输出 txt 目录 img_width, img_height: 统一缩放的尺寸,训练时用 """ with open(json_path, 'r') as f: coco = json.load(f) # 建立 image_id 到 标注列表 的映射 img_id_to_info = {img['id']: img for img in coco['images']} anns_by_img = {} for ann in coco['annotations']: img_id = ann['image_id'] anns_by_img.setdefault(img_id, []).append(ann) os.makedirs(out_dir, exist_ok=True) for img_id, anns in anns_by_img.items(): img_info = img_id_to_info[img_id] # 坑1: 原图尺寸可能和训练尺寸不一致,必须归一化到实际宽高 # 这里 img_info['width'] 是原图宽,不要写死用 json_path 里的全局宽 orig_w = img_info['width'] orig_h = img_info['height'] txt_path = os.path.join(out_dir, img_info['file_name'].replace('.jpg', '.txt')) with open(txt_path, 'w') as out: for ann in anns: # COCO 的 bbox 是 [x, y, width, height] x, y, w, h = ann['bbox'] # 坑2: 坐标必须归一化到 0-1,否则 YOLOv8 训练直接报错 x_center = (x + w / 2) / orig_w y_center = (y + h / 2) / orig_h w_norm = w / orig_w h_norm = h / orig_h # 坑3: 坐标裁剪,有些标注超出图片边界会导致训练 loss 异常跳变 x_center = min(0.999, max(0.001, x_center)) y_center = min(0.999, max(0.001, y_center)) w_norm = min(0.999, max(0.001, w_norm)) h_norm = min(0.999, max(0.001, h_norm)) # 坑4: 类别 id 从 0 开始,COCO 原始 category_id 可能不连续 # 需要自己维护一个映射表,不要直接用 category_id cat_id = category_map.get(ann['category_id'], 0) out.write(f"{cat_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n") print("转换完成,输出目录:", out_dir)这段脚本的逻辑就是把 COCO 的绝对坐标框转为相对坐标的 YOLO 格式。参数说明:img_width和img_height我留着没在计算里用,因为每个原图尺寸不同,必须从img_info里读取真实尺寸。如果所有输入图片统一缩放后再标注,那才用全局尺寸,但那样会丢失原始精度,我倾向于保留原图尺寸训练,YOLOv8 内部会自动缩放。
四个边界坑值得记一下:一是全局宽高和单图宽高混用,导致归一化错位;二是坐标没有裁剪,超出边界的框让模型收敛变慢;三是类别 ID 没有重映射,COCO 数据集里 category_id 往往从 1 开始且不连续,而 YOLO 要求从 0 开始连续编号;四是训练尺寸和原图尺寸不一致时输入标签错误。这些坑我都在实际项目里踩过,花了半天排查 loss 不收敛,最后发现是标注坐标归一化算错了。
4. 训练车辆特征模型:YOLO 主线加分类支线
4.1 训练车辆检测模型:YOLOv8 训练脚本与五个关键参数
数据准备好后,先跑检测模型。YOLOv8 的好处是命令行工具和 Python API 都齐全,数据配置用 yaml 文件搞定。先写数据配置文件,再写训练脚本,整个流程不用自己写网络结构。
# vehicle_det.yaml # YOLOv8 数据配置文件 path: /data/vehicle_data/detection train: images/train val: images/val test: images/val # 类别名 names: 0: vehicle# train_yolo.py from ultralytics import YOLO # 使用 yolov8s.pt 预训练权重,比从零开始收敛快很多 model = YOLO('yolov8s.pt') results = model.train( data='vehicle_det.yaml', epochs=100, batch=16, imgsz=640, workers=8, lr0=0.01, patience=15, # 连续 15 轮验证集没提升就提前停止 save_period=10, # 每 10 轮存一次权重,防止训练中断白跑 device=0, # 单 GPU 训练 )参数说明:epochs=100是起步值,车辆检测是相对简单的任务,50 轮左右基本收敛,100 轮留余量配合 early stopping。batch=16取决于显存大小,如果显存报错就降到 8。imgsz=640是训练输入尺寸,如果监控画面里车辆很小,建议提高到 960,但训练时间会变长。lr0=0.01主要用于迁移学习阶段,如果从零训练要降到 0.001。
训练过程中要盯两个指标:train/loss和val/box_loss。如果验证集 loss 持续上升而训练集 loss 下降,那就是过拟合,增加数据增强的mosaic比例或者加 Dropout。如果两个 loss 都在高位徘徊,优先检查标注格式和类别配置,不要盲目调参。YOLOv8 训练完成后会输出best.pt,用这个权重做后续推理和导出。
4.2 用 ResNet 微调车型颜色分类:冻结骨干层与学习率设置
检测模型把车辆框出来之后,每个框要送去分类。分类模型我强烈建议微调预训练权重而不是从零训练,因为车辆外观的底层特征(边缘、纹理、形状)和 ImageNet 学习到的通用特征高度重合,微调可以大幅减少需要的训练数据量。
import torch import torch.nn as nn from torchvision import models, transforms from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder # 数据增强:颜色抖动对车辆颜色分类非常重要 transform_train = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # 加载数据 train_dataset = ImageFolder('/data/vehicle_data/color_cls/train', transform=transform_train) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4) # 微调 ResNet50 model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) # 冻结前几层:前 3 个 stage 不更新,只微调最后 1 个 stage 和 FC 层 ct = 0 for name, param in model.named_parameters(): ct += 1 if ct < 0: # 这里改成按需冻结 param.requires_grad = False # 更实用的冻结方式:冻结除 layer4 和 fc 之外的所有层 for name, param in model.named_parameters(): if not name.startswith('layer4') and not name.startswith('fc'): param.requires_grad = False # 替换最后的分类头为车辆颜色类别数 num_classes = len(train_dataset.classes) model.fc = nn.Linear(model.fc.in_features, num_classes) # 用 AdamW,学习率给到 1e-4,比默认的 1e-3 稳妥 optimizer = torch.optim.AdamW( [p for p in model.parameters() if p.requires_grad], lr=1e-4, weight_decay=1e-4 ) criterion = nn.CrossEntropyLoss() # 训练循环 for epoch in range(30): model.train() total_loss = 0.0 for images, labels in train_loader: images, labels = images.cuda(), labels.cuda() outputs = model(images) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() avg_loss = total_loss / len(train_loader) print(f"Epoch {epoch+1}/30, Loss: {avg_loss:.4f}")逻辑说明:冻结骨干层的原因是小数据集下微调全模型容易过拟合,只更新最后几层相当于让模型在已有特征表示上做轻量适配。ColorJitter的亮度和饱和度扰动专门针对车辆颜色受光照影响的问题,这是我在实际项目里验证过最有效的增强手段。学习率设为1e-4而不是1e-3,因为冻结后可学习的参数量少,太激进的学习率会把预训练学到的特征冲掉。
参数说明:batch_size=64在常见单卡上没问题,如果显存不足降到 32。30个 epoch 对微调来说足够,我见过最快的在第 15 轮就收敛。关键参数是requires_grad的冻结逻辑——这段代码里我自己注释了两种方式,实际用第二种:只解锁layer4和fc。如果做品牌细粒度识别,建议改用 ResNet101 并把layer3也解锁,因为品牌差异比颜色差异微弱得多,需要更多层参与微调。
5. 车辆特征分析系统避坑指南:从标注到部署的 5 个血泪经验
5.1 视角偏置:训练集全是车头,测试集全是车尾
现象:模型在验证集上测试准确率 96%,但到了现场实际跑,识别率掉到 70% 左右。我早期做的第一个版本就是这样,拿卡口的车头数据训练,到停车场落地后发现大量车辆只有车尾视角,品牌分类几乎全错。
原因:车辆检测和品牌分类模型都只学到了车头特征,比如进气格栅、车标位置、大灯轮廓,而车尾只有尾灯、文字标牌、后备箱线条,特征分布完全不同。测试集里恰好也是车头数据,所以指标虚高。
解决:数据准备阶段强制按视角分区。训练集里车头、车尾、侧面各占三分之一,更严格的做法是检测模型和分类模型独立训练,检测模型用全视角数据,分类模型按视角分别训练两个分支,推理时先判断视角再路由到对应分类网络。这个改动之后,现场识别率从 70% 提升到 91%。
5.2 颜色分类受光照影响:夜间车灯反光把黑色车识别成白色
现象:夜间场景下黑色车辆的识别结果经常变成深蓝色,白色车被路灯的暖黄色光照成浅黄色,系统输出的颜色档案和人工标签对不上。
原因:分类网络学到的颜色特征很大程度来自整体亮度分布,而夜间图像的亮度主要来自路灯和车灯反射,不是车身本身的颜色。色温变化让 RGB 分布整体偏移,模型把偏亮的暗色车判成了浅色。
解决:训练数据里混入不同时段的图像,至少包含白天、黄昏、夜间三组。另外在数据增强中加入色温扰动和亮度扰动,ColorJitter的brightness参数调到 0.3 以上。如果夜间样本仍然不够,可以在预处理阶段先做白平衡矫正再送分类网络。
5.3 检测框贴太紧:车牌区域被切掉导致识别率骤降
现象:车牌识别模型的单字符准确率很高,但整牌识别率一直上不去。排查后发现,上游 YOLOv8 检测框输出的是紧贴车身的矩形,车牌位于车身最下方,有一部分被框边缘裁掉了。
原因:标注车辆检测框时,标注员习惯把框贴紧车身边缘,导致车牌被“切出去”了一部分,后级的车牌识别模块输入图像不完整。
解决:调整标注规范——车辆检测框四周预留 3%~5% 的边距,确保车牌和车灯完整落在框内。如果标注已完成没有重标的预算,可以在检测框输出后做“外扩处理”,把每个框按长宽各扩大 5% 再裁图送后续模块。这个操作用代码实现也就是几行 OpenCV 的事,但对车牌识别的提升立竿见影。
5.4 品牌分类滑向黑匣子:数据不平衡比模型结构更致命
现象:品牌分类结果里,销量大的车型(比如大众、丰田)准确率极高,但稀有车型(比如高性能车、小众进口车)几乎全被误判成常见品牌。
原因:这是一个典型的长尾分布问题。数据集中保有量大的品牌占据了 70% 以上的样本,模型在训练时学到的先验概率严重偏向高频类别,这是个“黑匣子”问题——表面看整体准确率 90%,分解到每个类别,长尾类别的召回率可能只有 20%。
解决:先做统计,把每个品牌类别的样本数画出来,小于 500 张的类别不参与训练,单独归入“其他”类。想要真正识别这些稀有车型,就得针对性补数据,或者用数据合成做扩充。训练层面用 Focal Loss 替代 CrossEntropyLoss,降低高置信度样本的权重,让模型更关注难分的稀有类别。我实际测试下来,Focal Loss 在这个场景里能把长尾类别召回率提升 12 个百分点。
5.5 推理速度与并发:没有 GPU 的服务器别硬上 YOLOv8x
现象:系统开发完成,申请到的部署服务器只有 CPU,YOLOv8x 跑一张图耗时 2.8 秒,并发 5 路视频时 CPU 直接打满,系统彻底失去实时性。
原因:模型选型时只考虑了精度,定了过大的网络结构。YOLOv8x 的参数量是 s 的 5 倍,在 CPU 上推理速度完全不可接受。深度学习模型的部署落地,必须从第一天就考虑算力约束。
解决:部署环境确定后再定模型体积。CPU 服务器用 YOLOv8n 或 YOLOv8s,精度下降但速度能到 200ms 之内。再配合 ONNX 导出和 OpenVINO 推理,把整体延迟压到 100ms 左右。如果必须上大模型,就把推理框架换成 TensorRT(仅限 NVIDIA GPU)并用 FP16 精度,我的实测效果是在不损失明显精度的情况下,推理速度提升约 3 倍。
6. 把系统跑起来:模型导出到服务化部署的进阶技巧
6.1 模型加速与接口封装:ONNX 导出加 FastAPI 服务
训练好的 PyTorch 模型不能直接用于生产环境,一方面推理框架对 PyTorch 模型的支持有限,另一方面模型的启动和预热时间太长。我习惯先导出 ONNX 格式,再通过 ONNX Runtime 或 OpenVINO 做推理加速。导出命令很简单:
import torch from ultralytics import YOLO # 加载训练好的模型并导出为 ONNX model = YOLO('best.pt') model.export(format='onnx', imgsz=640, half=True) # half=True 导出 FP16 # 分类模型导出 import torch from torchvision import models clf_model = models.resnet50() clf_model.fc = torch.nn.Linear(clf_model.fc.in_features, 8) checkpoint = torch.load('best_cls.pth', map_location='cpu') clf_model.load_state_dict(checkpoint['model']) clf_model.eval() dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export(clf_model, dummy_input, 'color_cls.onnx', input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch'}, 'output': {0: 'batch'}})参数说明:half=True只在 GPU 推理时有用,如果最终部署在 CPU 上就不要导 FP16 模型,因为很多 CPU 不支持 FP16 加速,反而转回 FP32 更稳。dynamic_axes设置动态 batch,这样接口层可以灵活传入多张图片。
服务化封装我通常用 FastAPI,代码量小、性能足够。接口接收图片,返回车辆特征结构化 JSON:
from fastapi import FastAPI, UploadFile import onnxruntime as ort import numpy as np import cv2 app = FastAPI() # 初始化 ONNX Runtime 会话 det_session = ort.InferenceSession('best.onnx') clf_session = ort.InferenceSession('color_cls.onnx') @app.post("/vehicle/analyze") async def analyze(file: UploadFile): # 读取并预处理图片 img_bytes = await file.read() img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR) # 检测车辆 det_input = preprocess_det(img) # 缩放、归一化、转 NCHW det_outputs = det_session.run(None, {'images': det_input}) # 对每个检测框做分类 cars = [] for box in det_outputs[0]: crop = img[int(box[1]):int(box[3]), int(box[0]):int(box[2])] color = clf_session.run(None, {'input': preprocess_cls(crop)}) cars.append({ "bbox": box[:4].tolist(), "confidence": float(box[4]), "color": color_label[int(np.argmax(color[0]))] }) return {"vehicle_count": len(cars), "vehicles": cars}这个接口能直接跑起来,给前端或业务系统调用。注意部署时要做三件事:设置并发上限防止 OOM、增加超时控制、把主要耗时环节做异步化。不然同一时间涌入多路视频时服务会无响应,这是我踩过的真实教训。
6.2 系统验收方法:拿一段真实监控视频做端到端回放
模型部署完不等于系统做完。我的习惯是拿一段 10 分钟的真实监控视频做端到端验收,而不是单张图片测试。记录三个维度:每辆车的检出数是否正确、车牌识别准确率、颜色和品牌分类是否符合人工判断。具体做法是先把这段视频抽帧,用系统跑一遍生成标注结果,再把结果画回视频上逐帧人工核对。
验收时特别注意跨天时段。上午 10 点和晚上 10 点的同一批车,识别结果差异可能很大。我在项目里吃过这个亏——白天测完觉得万无一失,结果第二天领导晚上去看现场,识别率肉眼可见地下降。后来我在验收报告里强制要求分时段打表,白天的准确率和夜间的准确率分开统计,达不到标准不验收。
最后说一个我个人的教训:做这类系统,永远不要只看整体准确率,一定要按“颜色/车型/品牌/车牌”四个维度分别统计。品牌准确率 90% 而车牌准确率只有 60%,这不叫系统达标,只能算半成品。宁可每项都是 80 分,也不要单项 99 分、另一项 30 分——因为现场用户只会用最短的那块板子评价你。现在我做验收的第一件事,就是先把各特征维度拆开跑一次指标,再决定要不要继续调。希望帮到你,少走我走过的弯路。
本文还有配套的精品资源,点击获取