简介本资源是一套基于Python与深度学习实现的车辆视觉识别项目源码面向计算机视觉初学者、课程设计学生及需要快速搭建车辆识别Demo的开发者可同时完成汽车目标检测、车型分类、品牌识别与车辆识别等多类任务。压缩包共392个文件约25.6MB包含55个py源码文件、39个xml配置、29张jpg与66张png图像素材、14个pyc缓存及mat数据文件另有js、html、css等前端页面资源与apk安装包覆盖模型训练、推理脚本与可视化界面。资源已在本地编译验证可运行难度适中内容经助教老师审定适合直接用于学习与二次开发。目前已有720人学习下载。读者可从中获得完整的深度学习识别流程、可复用的训练与推理代码、配套数据集与界面文件便于快速理解车辆识别项目的目录结构与实现思路并在此基础上进行调参与功能扩展。1. 从一张停车场抓拍图说起汽车识别到底在识别什么很多人第一次接触「汽车识别」这个词脑子里想的是「这是不是一辆车」。但真正落到工程里一张卡口或停车场抓拍图进来业务方要的往往不是一个是非判断而是三件事叠在一起画面里有没有车、这辆车是什么车型轿车/SUV/MPV/皮卡/客车/货车、它挂的是哪个品牌的车标。这就是标题里「汽车识别 车型识别 汽车品牌识别 车辆识别」四层需求的真实来源也是我用 Python 加深度学习做这套东西时反复被追问的点。这套方案适合谁做智慧停车、卡口过车、4S 店客流统计、二手车图片入库的开发者手里有几千到几万张带标注的车辆图想用 Python 快速搭一条能跑通、能迭代的识别流水线。它不追求一步做到工业级精度而是先把「检测框 → 车型分类 → 品牌分类」这条链路打通再谈调优。下面我按自己实际搭过的顺序把选型、代码、参数和踩过的坑讲清楚。2. 车辆识别流水线的技术选型为什么是检测加两级分类2.1 把「车辆识别」拆成检测和分类两个子问题直接用一个多标签分类网络同时输出「有车 车型 品牌」是行不通的因为一张图里可能有 0 辆、1 辆或多辆车分类网络默认输入里已经有一辆居中的车。所以工程上最常见的拆法是两段式第一段做目标检测把每辆车的边界框抠出来第二段对每个框做分类分别预测车型和品牌。检测这一段的候选方案里YOLO 系列是落地最省心的。YOLOv5/v8 在车辆这种大目标上召回率足够推理速度快Python 生态成熟pip install ultralytics就能跑。Faster R-CNN 精度略高但速度慢除非你对小目标和遮挡特别敏感否则没必要。分类这一段车型和品牌是两个独立任务不要硬塞进一个 softmax因为「轿车」和「大众」不在同一个标签空间里。常见做法是训练两个分类头或者干脆训两个独立模型。提示车型和品牌的数据集分布差异很大。车型类别少610 类、样本相对均衡品牌类别多几十到上百类、长尾严重。用同一套训练策略会翻车后面会讲。2.2 数据集怎么准备检测标注和分类标注分开做检测需要的是框标注格式一般是 VOC 的 XML 或 YOLO 的 txt。分类需要的是裁剪后的车辆小图加类别标签。很多人图省事只标了检测框然后想用检测框直接裁图喂给分类器——可以但要注意裁图时留一点边距否则车标经常被裁掉一半品牌识别直接崩。我一般会准备三个目录images/放原图labels/放 YOLO 格式的检测标注crops/放裁好的分类图。裁图脚本要保证检测框和分类标签一一对应文件名带原图 ID 和框序号方便回溯。# 目录结构示例 dataset/ ├── images/ # 原始车辆图 ├── labels/ # YOLO 格式检测标注 txt ├── crops/ # 裁剪后的车辆小图 │ ├── car_type/ # 按车型分文件夹 │ └── car_brand/ # 按品牌分文件夹 └── data.yaml # 检测训练配置data.yaml是 YOLO 训练的核心配置path指向数据集根目录train/val是图片列表或目录nc是类别数names是类别名。车辆检测通常只有一到两类car、bus、truck别把车型当检测类别那是分类的活。2.3 检测模型训练YOLO 的最小可跑命令检测这一段我一般直接用 ultralytics 的 YOLOv8n 起步小模型先跑通再换大的。下面是最小训练命令和对应 Python 调用。# 命令行方式适合快速验证 yolo detect train \ modelyolov8n.pt \ datadataset/data.yaml \ epochs100 \ imgsz640 \ batch16 \ device0# Python 方式方便嵌进自己的流水线 from ultralytics import YOLO model YOLO(yolov8n.pt) # 加载预训练权重 model.train( datadataset/data.yaml, # 数据配置路径 epochs100, # 训练轮数车辆数据 80~150 够用 imgsz640, # 输入尺寸卡口图可上 960 batch16, # 显存不够就降到 8 device0, # 0 表示第一块 GPUCPU 写 cpu patience20 # 20 轮无提升就早停 )逻辑说明modelyolov8n.pt用的是 COCO 预训练权重车辆本身就在 COCO 里迁移学习收敛快。imgsz是关键参数卡口图车辆占比小的时候 640 会漏检调到 960 或 1280 召回明显提升但显存和耗时翻倍。patience是后悔药防止过拟合后白跑几十轮。训练完权重在runs/detect/train/weights/best.pt。2.4 车型与品牌分类迁移学习加长尾处理分类模型我一般用 ResNet18 或 EfficientNet-B0 起步输入 224×224。车型分类样本均衡直接交叉熵就行品牌分类长尾严重得用带权重的损失或者重采样。import torch import torch.nn as nn from torchvision import models, transforms # 车型分类类别少且均衡标准交叉熵 type_model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) type_model.fc nn.Linear(type_model.fc.in_features, num_type_classes) # 品牌分类长尾用类别权重平衡 brand_model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) brand_model.fc nn.Linear(brand_model.fc.in_features, num_brand_classes) # 统计每个品牌样本数反比作为权重 class_counts torch.tensor([...]) # 各品牌训练样本数 weights 1.0 / class_counts.float() weights weights / weights.sum() * len(class_counts) criterion nn.CrossEntropyLoss(weightweights) # 数据增强车辆图翻转要谨慎水平翻转会改变车标方向 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.ColorJitter(0.2, 0.2, 0.2), # 应对不同光照 transforms.ToTensor(), ])参数说明weightsResNet18_Weights.IMAGENET1K_V1是 ImageNet 预训练车辆分类微调时学习率要调小一般1e-4起步太大直接把预训练特征冲掉。品牌分类的weight参数是解决长尾的核心稀有品牌权重高但别调太猛否则常见品牌精度掉得厉害。水平翻转对车型影响不大但品牌车标左右翻转后语义变了建议品牌分类关掉水平翻转。3. 把检测和分类串成一条推理流水线3.1 单图推理从原图到车型品牌输出训练完两个模型推理时先检测再裁图再分类。这里有个细节检测框裁出来要按分类模型的预处理走别直接 resize 成 224要先按比例缩放再 padding否则车辆被拉伸变形品牌识别精度掉得莫名其妙。import cv2 import torch from ultralytics import YOLO from PIL import Image from torchvision import transforms detector YOLO(runs/detect/train/weights/best.pt) type_model.eval() brand_model.eval() def classify_crop(crop_bgr, model, classes): img Image.fromarray(cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2RGB)) # 保持比例缩放 padding避免车辆变形 w, h img.size scale 224 / max(w, h) img img.resize((int(w * scale), int(h * scale))) canvas Image.new(RGB, (224, 224), (114, 114, 114)) canvas.paste(img, ((224 - img.width) // 2, (224 - img.height) // 2)) tensor transforms.ToTensor()(canvas).unsqueeze(0) with torch.no_grad(): logits model(tensor) idx logits.argmax(1).item() return classes[idx], torch.softmax(logits, 1)[0, idx].item() def infer(image_path): img cv2.imread(image_path) results detector(img, conf0.4, iou0.5)[0] output [] for box in results.boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) crop img[y1:y2, x1:x2] if crop.size 0: continue car_type, t_conf classify_crop(crop, type_model, type_classes) car_brand, b_conf classify_crop(crop, brand_model, brand_classes) output.append({ bbox: [x1, y1, x2, y2], type: car_type, type_conf: round(t_conf, 3), brand: car_brand, brand_conf: round(b_conf, 3), }) return output逻辑说明conf0.4是检测置信度阈值卡口场景可以降到 0.3 提召回但误检会增多。iou0.5是 NMS 阈值车辆重叠不多时保持默认。裁图后先按长边缩放到 224 再 padding这一步是品牌识别精度的关键直接 resize 会让车标变形。返回结果里带上置信度方便下游按阈值过滤。3.2 批量推理与性能别让分类拖垮吞吐检测用 GPU 很快但分类如果一张张跑吞吐会被拖死。批量推理时把同一张图里的多个车辆 crop 攒成一个 batch 一起送进分类模型能快好几倍。def infer_batch(image_paths, batch_size32): all_crops, meta [], [] for path in image_paths: img cv2.imread(path) results detector(img, conf0.4, iou0.5)[0] for i, box in enumerate(results.boxes): x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) crop img[y1:y2, x1:x2] if crop.size 0: continue all_crops.append(preprocess(crop)) # 统一预处理成 tensor meta.append((path, [x1, y1, x2, y2])) # 分批送分类模型 for i in range(0, len(all_crops), batch_size): batch torch.stack(all_crops[i:i batch_size]) with torch.no_grad(): type_logits type_model(batch) brand_logits brand_model(batch) # ... 解析结果参数说明batch_size按显存调224 输入下 32 一般没问题。检测和分类可以放不同设备检测用 GPU、分类用 CPU 也能接受但批量后还是 GPU 快。如果做视频流检测可以隔帧跑分类只对新增车辆跑能省大量算力。3.3 用验证集量化每一级的精度流水线的精度是检测和分类相乘的别只看分类准确率。检测漏了分类再准也没用。我一般会分别统计检测的 mAP0.5、车型分类 top-1、品牌分类 top-1再算端到端准确率。指标含义常见达标值检测 mAP0.5车辆框定位精度0.90 以上车型 top-1车型分类准确率0.92 以上品牌 top-1品牌分类准确率0.85 以上端到端检测正确且两级分类都对0.80 以上端到端准确率低的时候先看是检测漏检还是分类错分。把验证集里错的样本按「检测错」「车型错」「品牌错」分类统计定位问题比盲目调参快得多。4. 避坑与排查车辆识别落地时最容易翻车的五件事4.1 车标被裁掉一半品牌识别全错现象车型识别挺准品牌识别准确率死活上不去看错分样本发现车标经常在框边缘甚至框外。 原因检测框是紧贴车身标注的车标在车头正中偏上正常不会丢但如果标注时框偏了或者裁图时没留边距车标就被切了。 解决裁图时按框的宽高各外扩 5%10%再送分类。同时检查检测标注质量框太紧的重新标。4.2 训练集品牌分布长尾稀有品牌永远预测不出来现象常见品牌大众、丰田、本田识别很准稀有品牌全被预测成常见品牌。 原因交叉熵在类别极不均衡时会被多数类主导稀有品牌梯度被淹没。 解决用带类别权重的交叉熵或者对稀有品牌过采样。权重别设太极端1/sqrt(count)比1/count稳。也可以先做品牌粗分类国产/德系/日系再细分。4.3 检测置信度阈值设太高小目标车辆漏检现象近处大车都能检出远处小车全漏。 原因conf默认 0.25 或 0.4小目标本身置信度就低被阈值卡掉。 解决卡口场景把conf降到 0.20.3同时把imgsz提到 960。如果还漏检查训练集里小目标样本够不够不够就补充。4.4 分类模型输入尺寸和训练不一致精度莫名下降现象训练时验证准确率 0.9部署后推理准确率只有 0.7。 原因训练用Resize((224,224))直接拉伸推理用了保持比例 padding两者预处理不一致。 解决训练和推理必须用同一套预处理。我一般统一用保持比例 padding训练时也这么写别图省事直接 resize。4.5 视频流里同一辆车被反复识别结果还不一致现象一段视频里同一辆车前后帧识别出的品牌不一样。 原因每帧独立检测分类光照、角度变化导致单帧结果抖动。 解决加跟踪如 ByteTrack对同一 track ID 的多帧分类结果做投票或取置信度最高的。跟踪还能省算力同一辆车只分类一次。5. 进阶技巧用置信度融合和难例挖掘把端到端精度再抬一截前面把流水线跑通了但端到端准确率卡在 0.8 左右上不去这时候别急着换大模型先把已有模型的输出用起来。我常用的两个技巧是置信度融合和难例挖掘。置信度融合的思路是车型和品牌不是独立的某些组合天然更合理。比如车型预测是「货车」品牌预测是「法拉利」这个组合明显矛盾。可以统计训练集里车型和品牌的共现矩阵推理时把分类 logits 和共现先验做加权矛盾组合的分数被压低。实现上就是在 softmax 后乘一个共现概率向量再重新归一化。# 车型-品牌共现先验融合 cooc torch.tensor(cooc_matrix) # shape: [num_type, num_brand] cooc cooc / cooc.sum(1, keepdimTrue) # 按车型归一化 type_prob torch.softmax(type_logits, 1) # [1, num_type] brand_prob torch.softmax(brand_logits, 1) # [1, num_brand] # 用车型概率加权共现先验再和品牌概率融合 prior type_prob cooc # [1, num_brand] fused brand_prob * (prior ** alpha) # alpha 控制先验强度 fused fused / fused.sum(1, keepdimTrue)alpha是关键参数0 等于不用先验1 等于完全信先验。我一般从 0.3 试起验证集上调。这个技巧在品牌长尾场景下提升明显因为稀有品牌本身分类分数低共现先验能把它拉回来。难例挖掘是另一个提分点。把验证集里端到端错的样本挑出来按错误类型分组人工看一遍。常见的是夜间、逆光、雨雪、车标模糊这几类。把这些难例加入训练集重新微调比盲目加数据有效得多。我一般每轮迭代挑 200500 张难例混进原训练集学习率调小到1e-5微调 10 轮。还有一个容易被忽略的点品牌识别对图像分辨率要求比车型高。车型看轮廓就行品牌要看车标细节。如果检测框裁出来只有 50×50 像素品牌基本没戏。这种情况要么提高检测输入尺寸要么对小车直接跳过品牌识别只输出车型别硬给一个错品牌。最后说个验证方法别只看整体准确率按车型分组看品牌准确率。你会发现轿车品牌识别准货车品牌识别差因为货车车标位置和样式差异大。分组统计能帮你定位到具体哪个车型的品牌识别需要补数据。我自己做这套东西最大的教训是一开始总想用一个模型端到端解决折腾了两周发现检测和分类耦合在一起调检测影响分类调分类影响检测根本没法定位问题。拆成两段后每一级都能单独评估、单独优化迭代速度反而快了一倍。所以如果你正准备动手先把检测跑通再加车型最后加品牌别一上来就搭大而全的架构。希望帮到你。本文还有配套的精品资源点击获取