简介:这套基于Python与OpenCV、YOLOv的车辆多维特征识别系统源码包,面向智能交通、安防监控及计算机视觉学习者,提供可直接运行的车色、车品牌、车标、车型识别方案。压缩包共8个文件、约8.7MB,主要包含两个Python主程序(界面与核心识别逻辑)、YOLO模型配置与names类别文件、OpenCV运行依赖DLL、参数配置INI以及使用说明文档,结构清晰便于二次开发。系统通过摄像头或图像输入,利用预训练权重完成多目标检测与特征提取,已在智能交通场景中具有实用价值。已有180人学习下载,适合希望在真实项目中掌握OpenCV+YOLO流程、快速搭建车辆识别系统的开发者参考。
1. 车辆多维识别系统:从“能检测到车”到“认出是哪辆车”的关键一跳
停车场门口拍下一辆车,系统要在几秒内回答:品牌、车标、车型、颜色。很多团队用YOLO做车辆检测非常熟练,但要把这些属性真正拼出来时,代码就开始发散——有人只做车辆检测,有人给车身区域取个像素均值当颜色,有人试图让一个检测头同时输出位置和品牌,最后效果都不稳定。Python基于OpenCV和YOLOv的车辆多维特征识别,本质是把一个问题拆成“定位+分类”:YOLO负责找车、找车标,OpenCV和多个分类头负责认颜色、认品牌、认车型。这套方案适合正在做智慧停车、卡口数据分析或车辆检索的开发者,也适合想理解检测与分类边界的新手。按章节复现一遍,你能得到一条单图和视频都能跑通的识别链路,以及别人踩几周才摸清的参数经验。
2. 环境与目录结构:用Python 3.9、OpenCV 4.8和Ultralytics把项目从零搭起来
2.1 环境选型:为什么固定用Python 3.9和OpenCV 4.8而不是最新版
很多项目翻车都在环境上而不是算法上。照着python安装教程装完环境,import cv2报ModuleNotFoundError: No module named 'opencv',在anaconda prompt里面没有opencv,这类问题几乎每个做opencv图像处理项目的人都遇到过。这里给一个我长期使用最稳的组合:Python 3.9或3.10,opencv-python固定4.8.x,轻量检测用Ultralytics的YOLOv8,分类网络直接复用Ultralytics的分类接口。
conda create -n vehicle python=3.9 -y conda activate vehicle python -m pip install --upgrade pip pip install opencv-python==4.8.1.78 opencv-contrib-python==4.8.1.78 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pandas numpy第一行创建独立虚拟环境,保证后面装多少包都不污染系统Python。第三行必须用python -m pip而不是直接pip,当你在anaconda prompt里面找不到opencv时,多半是pip指向了另一个site-packages。torch按显卡驱动选择cu118或cu121,纯CPU也能跑,只是训练和视频流会慢。OpenCV选4.8.1.78是因为它对numpy 1.x兼容最稳,而最新版opencv和ultralytics往往同时要求不同numpy版本,装在一起必然打架。
如果你要用linux安装cuda版本opencv,那要走上另一条路:源码编译opencv,configure、make跑一遍,同时还要处理GPU模块的cmake编译步骤,耗时通常是半小时起步。做车辆识别这个场景,官方预编译包里的模块已经足够,不用自己编译。vscode配置python或pycharm配置python环境时,记得解释器一律选到vehicle这个conda环境,否则你在终端里能import cv2,IDE里依旧报错。
提示:nvcc、CUDA和PyTorch的CUDA版本是三个不同东西,pip装torch时只要保证torch内部的cuda运行时和显卡驱动匹配即可,不需要系统装全套CUDA Toolkit。
2.2 权重文件与数据集目录:四层分离,别把训练产物混进源码
很多半成品项目把源码、数据集、权重文件都堆在同一个文件夹,训练结束后根本分不清哪个权重对应哪个数据集,换一次样本就要重来一遍。我通常的目录结构如下。
vehicle-multifeature/ ├── weights/ │ ├── vehicle_det_20250601.pt │ ├── brand_cls_20250601.pt │ ├── logo_cls_20250601.pt │ └── type_cls_20250601.pt ├── datasets/ │ ├── detection/ │ ├── color_train/ │ ├── brand/ │ └── logo/ ├── config.py ├── utils/ │ ├── preprocess.py │ └── postprocess.py ├── train_det.py ├── train_cls.py └── inference.pyweights和datasets独立出来的用意是:换数据集时只改config.py里的路径,不改任何处理逻辑。权重文件名必须带日期或训练批次后缀,否则一周后你就分不清哪一个才是调过参的版本。分类数据集按ImageNet风格分目录,目录名就是类别名,Ultralytics分类接口直接认这个结构;检测数据集建议保留VOC原始标注,转YOLO格式的脚本单独放,不混进训练代码。
config.py里集中维护每类目标的类别表、置信度阈值、颜色区间。这个文件是整个系统的“开关面板”,后面调参全部在这里改,不要在推理脚本里硬编码阈值,否则同一个模型换到夜间场景时,你会到处找那个写死在代码里的0.35。
2.3 最小推理链路:先跑通“检测+裁剪”,再谈多维识别
环境与目录准备好后,先写一个最小脚本验证三件事:权重能否加载、模型能否预测、裁剪区域是否正确。这段代码是每次新环境我都要先跑的“体检单”。
import cv2 from ultralytics import YOLO # 加载检测权重,YOLO只负责回答“车在哪” vehicle_det = YOLO("weights/vehicle_det_20250601.pt") img = cv2.imread("test.jpg") results = vehicle_det.predict( source=img, conf=0.35, iou=0.45, imgsz=640, verbose=False, )[0] # 只保留车辆类别的框,并按x坐标排序 boxes = [b for b in results.boxes if int(b.cls[0]) in VEHICLE_CLASSES] boxes.sort(key=lambda b: b.xyxy[0].tolist()[0]) for i, box in enumerate(boxes): x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) crop = img[y1:y2, x1:x2] cv2.imwrite(f"crop_{i}.jpg", crop)参数说明:conf是置信度门槛,低于该值的框直接丢弃,测试阶段0.35合适,标注质量好可以提到0.5,质量差就降到0.25,再低误检会大量涌入。iou是NMS的IoU阈值,0.45到0.5区间最稳,调太小会把同一辆车切成两个框,调太大会把并排车辆合并成一个框。imgsz是推理时的输入边长,车辆检测640够用,后面车标检不出时再考虑提到960。
predict传入numpy数组时,返回的boxes.xyxy坐标就是原图坐标,可以直接用来切片裁剪。如果传图片路径,模型内部会做letterbox缩放,返回坐标是letterbox坐标系,自己切片时容易错位。这个差别是后面踩坑的高发点。
跑通这段后,整个系统就有了地基:后续识别颜色、车标、品牌、车型,全部基于这里的crop对象。crop先保持原始分辨率,不要提前resize,因为颜色统计需要原始像素,品牌分类网络需要224分辨率,两者预处理逻辑不同。
3. 车色识别:用OpenCV的HSV与Lab空间把颜色从光照里剥出来
3.1 为什么车色识别不丢给深度学习,而是回到HSV统计
车色分类用深度学习不是不行,而是投入产出比太差。第一是样本不平衡,停车场里白、黑、灰占六成以上,红蓝黄等颜色样本稀少,训练出来的网络对红色车经常误判成橙色或黑色。第二是颜色在物理世界里不是一个稳定的语义标签,同一辆白色车,晴天、阴天、路灯下的RGB差异巨大,网络很容易学到光照纹理而不是颜色本身。
常见做法是回到OpenCV的HSV颜色空间做像素统计。HSV把色调、饱和度、亮度拆开,其中H分量对光照相对不敏感,这是颜色识别最稳定的依据。做opencv学习时你会看到大量HSV调色示例,它的核心优势就是能用几行代码解决传统RGB空间需要精心设计特征的问题。车色识别用opencv图像处理项目的方式实现,不训练、不调参、单帧耗时仅一两毫秒,比跑一个小分类网络更划算。
3.2 车身主色区域怎么取:避开玻璃、镀铬和地面反光
颜色统计最怕把车窗、银色镀铬条、地面反光也算进车身像素。裁剪出的车辆区域并不全是可用漆面,我通常只取一块“核心区域”参与统计。
def extract_body_center(crop): h, w = crop.shape[:2] # 取图像中部偏下的矩形,跳过车顶高光和前挡玻璃 x1, y1 = int(w * 0.15), int(h * 0.30) x2, y2 = int(w * 0.85), int(h * 0.85) return crop[y1:y2, x1:x2]这块比例来自实际经验:车顶高光集中在顶部,前挡玻璃在前三分之一处,底部常带保险杠阴影。取高度30%到85%的区间,能把面积最大的干净漆面包进来。侧面视角的车型需要把区域整体上移,因为侧面车身下沿往往有黑色裙边,底部15%的像素基本都是黑色。
3.3 HSV颜色区间字典与主色占比判断
颜色区间表是车色识别的核心参数。下面是一组白天光照下可用的范围,注意OpenCV里H范围是0到179,不是很多教程写的0到359。
import cv2 import numpy as np COLOR_RANGES = { "red": [(0, 43, 46), (10, 255, 255)], "orange": [(11, 43, 46), (25, 255, 255)], "yellow": [(26, 43, 46), (34, 255, 255)], "green": [(35, 43, 46), (77, 255, 255)], "blue": [(78, 43, 46), (124, 255, 255)], "purple": [(125, 43, 46), (155, 255, 255)], "white": [(0, 0, 180), (180, 30, 255)], "gray": [(0, 0, 50), (180, 30, 180)], "black": [(0, 0, 0), (180, 40, 90)], } def recognize_color(crop): crop = extract_body_center(crop) # 中值滤波半径5,去掉轮胎和路面带来的椒盐噪声 hsv = cv2.cvtColor(cv2.medianBlur(crop, 5), cv2.COLOR_BGR2HSV) total = crop.shape[0] * crop.shape[1] best_name, best_ratio = None, 0.0 for name, (lower, upper) in COLOR_RANGES.items(): mask = cv2.inRange(hsv, np.array(lower), np.array(upper)) ratio = cv2.countNonZero(mask) / total if ratio > best_ratio: best_name, best_ratio = name, ratio return best_name, best_ratio这段代码有三个设计点需要理解。第一,中值滤波放在BGR空间做,比转到HSV后再做更安全,在HSV通道上滤波会把邻近色相搅到一起。第二,白、灰、黑三个区间都让H全段通过,完全靠S和V区分,因为消色差颜色在HSV里就是低饱和度的表现。第三,判断标准是“占面积最大”而非“绝对占比超过阈值”,因为车辆外观可能撞色,主色就是面积最大的那块漆面。
3.4 临界颜色怎么判:HSV出粗结果,Lab色差做复核
HSV区间表对鲜艳颜色好用,但对“深蓝还是黑”“香槟金还是银”这种临界颜色很尴尬。一个像素刚好卡在蓝色区间外,就会被硬判成黑色。解决这个问题,我会加第二层用Lab空间和CIE76色差距离做复核。
LAB_REF = { "white": np.array([255, 128, 128]), "black": np.array([0, 128, 128]), "red": np.array([255, 145, 155]), "blue": np.array([80, 170, 80]), "green": np.array([150, 70, 160]), "silver": np.array([220, 128, 125]), } def recognize_color_lab(crop): lab = cv2.cvtColor(crop, cv2.COLOR_BGR2LAB) # 下采样到32x32取中位数,天然抵抗灰尘和反光像素 small = cv2.resize(lab, (32, 32), interpolation=cv2.INTER_AREA) mean_lab = np.median(small.reshape(-1, 3).astype(np.float32), axis=0) dists = {name: np.linalg.norm(mean_lab - ref) for name, ref in LAB_REF.items()} return min(dists, key=dists.get), min(dists.values())LAB_REF的标准值不要照抄,用二十张典型车色图片各跑一遍,取Lab均值落到这张表里,比任何手工设定都可靠。中位数比均值抗干扰,车身上的灰尘和点状反光不会把结果拉偏。实际使用时,HSV先出粗分类,如果最优颜色占比刚刚过半,说明颜色不纯,再用Lab色差复核一次。两套方法叠加后,深蓝和黑色的混淆率能明显降下来。
youcans的opencv例程里关于颜色空间转换那部分值得翻一翻,它在HSV和Lab的通道拆分上解释得很清楚,比直接看OpenCV官方文档容易理解。
4. 品牌、车标与车型识别:检测与分类解耦后的训练与权重落地
4.1 为什么检测和分类要拆成两个网络而不是一个多任务大模型
做车辆多维识别,最稳的架构不是训练一个多任务大模型一次输出全部属性,而是检测和分类解耦。原因是检测头和分类头对分辨率的需求冲突:车辆检测需要看整张图的语义,输入640就够;品牌分类需要看细节,车标在画面里常常只有三十几个像素,不放大根本分不清。硬塞进一个网络,输入分辨率要拉到1280以上,训练成本翻倍,小目标精度还不一定上来。
我一般这样分工:YOLO负责车辆检测,顺带检测车标区域;品牌和车型各用一个小分类网络;车色交给OpenCV。整个系统输出一行JSON,含车框、颜色、品牌、车标、车型以及各自的置信度。这套结构最大的好处是每个模块可以独立升级:检测权重换了,分类权重不动;某类车型样本攒多了,只重训品牌分类网络。
4.2 车标这种小目标:框外扩和放大到112是两个关键动作
车标识别是整套系统最容易翻车的地方。常见做法分成两步:检测模型先输出车标候选框,然后把车标框放大到分类网络需要的输入尺寸。放大这一步,我自己在工程里加了“外扩”处理。
def crop_logo_regions(img, det_results, logo_class_id): crops = [] for box in det_results.boxes: cls_id = int(box.cls[0]) if cls_id != logo_class_id: continue x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) # 中心不动,边向外扩30%,把进气格栅边缘也带上 cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 w, h = (x2 - x1) * 1.3, (y2 - y1) * 1.3 x1, y1 = int(cx - w / 2), int(cy - h / 2) x2, y2 = int(cx + w / 2), int(cy + h / 2) crop = img[max(0, y1):y2, max(0, x1):x2] if crop.size == 0: continue # INTER_CUBIC放大,保留边缘细节 crop = cv2.resize(crop, (112, 112), interpolation=cv2.INTER_CUBIC) crops.append(crop) return crops车标外扩是因为纯车标区域信息量太小,周围进气格栅的形状反而是区分品牌的重要线索。放大到112而不是224,是平衡速度和精度,车标分类难度没有品牌分类高,224会让整个视频流的分类耗时翻一倍。外扩的裁剪坐标要做边界约束,max(0, y1)防止车标太靠近画面边缘时越界。
如果检测阶段干脆检不出车标,多半不是分类问题而是检测问题。把车辆检测的imgsz从640提到960,训练数据里车标框标注得尽量紧,这两个动作比单纯调低conf有效得多。
4.3 训练品牌和车型分类权重:用Ultralytics分类接口省掉一堆样板代码
品牌和车型分类我之前用ResNet自己搭,后来换成了Ultralytics的分类模型,它能直接复用YOLO骨干的预训练参数,收敛速度明显更快。训练入口非常简单。
from ultralytics import YOLO # yolov8n-cls.pt是ImageNet分类预训练权重 model = YOLO("yolov8n-cls.pt") model.train( data="datasets/brand", # 目录下每个品牌一个子文件夹 epochs=80, imgsz=224, batch=32, lr0=0.01, lrf=0.001, cos_lr=True, device="cuda:0", # CPU就跑train,速度慢但能出结果 patience=15, dropout=0.05, label_smoothing=0.05, # 品牌种类多,防止模型过于自信 )参数说明:data目录结构直接决定类别表,子文件夹名就是标签,换数据集不需要改代码。epochs设80,品牌分类是细粒度任务,前二三十轮模型会在头部几个类别上先收敛到99%,但冷门品牌还欠拟合,早停太紧反而废。patience是早停的耐心值,配合cos_lr使用,学习率后期已经很小,连续15个epoch没有提升就保存最佳权重退出。
训练完用model.val()看top1和top5,不要只盯着top1。品牌和品牌之间太容易混淆,大众和斯柯达、丰田和日产的top1置信度在0.3到0.5之间是常态。输出结果时保留top3候选列表,让后面系统用先验表去消歧,比在分类网络这里硬选一个强得多。训练完成后用model.export(format="onnx")把权重导出,部署阶段会比直接跑PyTorch更快。
4.4 多维度结果合并:用先验表过滤,而不是简单取各自top1
颜色、品牌、车标、车型四个维度独立预测,但现实存在强先验:车标和品牌必然对应,车型和品牌也有关联。把这张表用起来,能灭掉大量“车标显示宝马、品牌却输出大众”的矛盾结果。
def merge_predictions(brand_pred, logo_pred, type_pred): merged = [] # 每个维度只取top3参与融合,减少无效计算 for brand, b_conf in brand_pred[:3]: for logo, l_conf in logo_pred[:3]: if logo not in BRAND_LOGO_MAP.get(brand, []): continue # 品牌与车标矛盾,直接丢弃 for model, t_conf in type_pred[:3]: if model not in BRAND_MODEL_MAP.get(brand, []): continue score = b_conf * l_conf * t_conf merged.append((brand, logo, model, score)) merged.sort(key=lambda x: -x[-1]) return merged[0] if merged else NoneBRAND_LOGO_MAP和BRAND_MODEL_MAP是人工维护的两张先验表,一开始可以只维护主流二三十个品牌,后面遇到新数据再补。分数用乘积而不是平均值,是因为三个维度同时置信的概率远小于单维度置信,乘积能天然惩罚不一致组合。注意最终score不要和任何单模型阈值比较,它只用于排序。
这套先验过滤在样本量越大的时候优势越明显。单独取每个维度top1去拼字符串,单个维度95%正确率,三个维度拼起来最终正确率只有85%左右;加上先验过滤后,品牌和车标的矛盾组合被大量排除,整体准确率能回到93%上下。
5. 避坑指南:环境、权重、小目标与坐标映射的几处现场翻车记录
5.1 ModuleNotFoundError: No module named 'opencv',但pip list里明明有
现象:按python安装教程一步步装完opencv,运行脚本时仍然报No module named 'opencv';或者新开的anaconda prompt里面敲import cv2,同样找不到。
原因:pip把包装进了系统Python的site-packages,而你conda activate后用的是虚拟环境里的Python解释器,两个环境互不相通。另一种常见情况是把opencv装进了base环境,却在另一个环境里跑脚本,等于白装。
解决:激活目标环境后,先执行python -m pip install opencv-python,再用where python和pip --version确认两个命令指向同一解释器。两个路径必须在同一个conda envs目录下才算状态正确。vscode配置python或pycharm配置python环境时,右下角解释器也要选中vehicle环境,终端的conda环境与IDE解释器是两套独立配置。
5.2 权重文件加载失败:YOLOv5的pt和YOLOv8的pt不能混用
现象:model = YOLO("weights/best.pt")直接抛异常,或者能加载但predict后所有结果为空,verbose信息也不打印检测到任何目标。
原因:YOLOv5权重是用老版本PyTorch保存的,Ultralytics YOLOv8加载它时要做兼容处理,但用YOLOv5的Detect代码加载v8权重必然报结构不匹配。还有一批网上流传的pt文件实际是爬虫抓下来的HTML页面,或是下载中断的半截文件,torch.load读进去自然全部乱套。
解决:训练、验证、推理统一用同一个Ultralytics大版本,最好在requirements里锁死版本号。遇到无法加载的权重,先执行torch.load(path, map_location='cpu')打印Key,正常文件会包含model、ema等字段,里面是一堆有序字典和参数张量;如果看到str类型或者报EOF错误,直接删掉重新下载,不要浪费时间找兼容方法。
5.3 车标检测一直空框:小目标不是调阈值能调出来的
现象:车辆框检测得很准,但车标位置永远没有候选框,conf从0.4一路降到0.1,还是空手而归,调低之后反而多出大量整块前脸的误检框。
原因:车标在画面上只有几十像素,经过640输入的特征提取,下采样后只剩几个像素,模型基本没机会学到稳定的车标特征。conf阈值只能过滤候选框,不能凭空造出检测能力。
解决:把检测输入imgsz从640提到960或1280,车标这种小目标对输入分辨率非常敏感。同时车标作为独立类别参与训练,标注时框要贴紧车标边缘,不要包住整个进气格栅。如果条件允许,单独训练一个小目标检测模型,输入分辨率1280,效果通常比强行让一个通用车辆检测模型承担车标检测更稳。imgsz上调后显存占用上升,batch要同步下调。
5.4 白色车识别成银色,夜里深色车全部变成黑色
现象:白天白车输出银色或灰色,傍晚之后深蓝色车全部识别成黑色,整个系统的车色准确率断崖下跌。
原因:HSV的V通道对光照极其敏感,白色和银色在过曝区域都落在“高亮度低饱和”区间,肉眼靠漆面质感和纹理区分,纯像素统计无法区分。夜间光照不足,所有颜色的S和V都被压缩到黑车的区间,模型只能猜个黑色。
解决:统计前先做一个亮度归一化,把V通道拉伸到全范围,再执行颜色区间匹配。另外设置一个最低平均亮度阈值,V均值低于30的图直接标记成“光照不足/夜间”,不输出具体颜色,让后续业务去决定怎么处理。这个“承认自己看不清”的策略,比硬猜一个颜色在工程上更可靠。
def normalize_brightness_in_hsv(hsv): v_channel = hsv[:, :, 2] if v_channel.mean() < 30: return None # 光照不足,交给上层判断 v_channel = cv2.normalize( v_channel, None, 50, 255, cv2.NORM_MINMAX) hsv[:, :, 2] = v_channel return hsv归一化时下限设50而不是0,避免黑色车被强行拉亮成灰色。这个下限值要结合你现场相机的光圈和增益调整,不同摄像头的暗部表现差别很大。
5.5 检测坐标和原图对不上,裁剪出来的全是错位区域
现象:用YOLO返回的boxes.xyxy直接裁剪原图,保存出来的crop要么整体偏移,要么整块黑边,但直接用results.plot()画图位置又是准的。
原因:YOLO推理时默认做letterbox预处理,把图像等比缩放并填充到输入尺寸,输出坐标是在letterbox坐标系下的。直接拿它和原图尺寸对齐,比例和偏移都不匹配,裁剪位置自然全乱。
解决:最省事的方法是predict时传入numpy数组而不是图片路径,数组形式的输入不会触发内置letterbox逻辑,返回坐标就是原图坐标。如果因为性能原因必须传路径或开启half精度,就需要手动把xyxy从letterbox坐标换算回原图坐标,换算公式是减去填充偏移量再除以缩放比例。这个坑在换用不同输入尺寸时会反复出现,建议在utils里封一个坐标还原函数,所有推理脚本统一调用。
6. 实时部署与验证:拉流中断、视频流抓帧和一张置信度热力图
6.1 视频流拉流中断与帧滞后:用抓帧线程解耦采集和推理
opencv python拉流中断是实时视频流部署里最常见的问题,现象是程序跑几分钟后画面卡住,控制台无任何报错,重连后恢复。原因是cap.read()是阻塞调用,网络抖动时读取线程整个卡住,推理线程也跟着停摆;同时解码器内部默认缓冲好几帧,推理速度慢时处理的其实已经是几十秒前的旧画面。
import cv2 import queue import threading import time def grab_loop(url, q, stop_event): cap = cv2.VideoCapture(url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while not stop_event.is_set(): ret, frame = cap.read() if not ret: cap.release() time.sleep(2) cap = cv2.VideoCapture(url) continue if q.qsize() >= 2: try: q.get_nowait() except queue.Empty: pass q.put(frame) cap.release()参数说明:CAP_PROP_BUFFERSIZE设成1,关闭解码器内部的帧缓存,只有这样才能保证推理线程拿到的都是最近帧。队列上限2是允许一帧网络延迟余量,超过就把旧帧丢掉,防止延迟累积。stop_event是threading.Event,用于退出时干净释放cap,grab_loop必须设置为daemon线程,否则键盘中断后进程挂住。这个方案能让长时间运行时的画面延迟稳定在半秒以内。
6.2 一个验证技巧:把检测框按置信度加权画成热力图
部署前我会用置信度热力图快速判断模型在哪个区域容易漏检。把测试集所有检测框按置信度累加,图像中心区域高亮说明训练数据车辆都在中间车厢区域,边缘漏检是数据分布问题而非模型能力问题。热度突然断开的区域,往往对应长期遮挡或采集视角盲区。每次重训后看一眼热力图,比只盯mAP数值直观得多,这种方法是排查夜间场景模型信心崩盘的常见手段。
heat = np.zeros((h, w), dtype=np.float32) for box in results.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) conf = float(box.conf[0]) heat[y1:y2, x1:x2] += conf heat_norm = cv2.normalize(heat, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) heat_color = cv2.applyColorMap(heat_norm, cv2.COLORMAP_JET) cv2.imwrite("heat.jpg", heat_color)我从把单张图片检测跑通,到把颜色、车标、品牌、车型四个维度全部接通,最大的教训是:单个模块都能跑到95%,不代表系统整体能跑到95%。每个模块各自调到了局部最优,组合起来反而会互相放大错误,尤其是颜色和品牌输出矛盾时,用户对系统的信任会迅速崩塌。所以做这套多维识别系统,阶段性地把四个维度输出并排打印出来看一遍,比闷头调其中一个模块的阈值更有效。希望帮到你。
本文还有配套的精品资源,点击获取