首页 / 资讯中心 / 文章详情

基于PyTorch的汽车识别实战:检测、车型与品牌分类全流程

基于PyTorch的汽车识别实战:检测、车型与品牌分类全流程 ★ FEATURED ARTICLE
简介本资源面向深度学习入门与计算机视觉方向的开发者、学生及课程设计者提供一套基于Python实现的汽车识别、车型识别、品牌识别与车辆识别完整源码可用于课程作业、毕业设计或算法练手。压缩包共392个文件约25.6MB以55个py脚本为核心配合mat数据、json配置与xml标注文件另含js、html、css等前端页面资源及apk安装包便于直接运行与效果演示。资源经过本地编译验证可运行难度适中内容经助教老师审定能满足学习与使用需求。目前已有720人学习下载读者可从中获取数据预处理、模型搭建、训练推理到界面展示的完整流程并参考目录结构与排错思路快速复现汽车多任务识别效果。1. 从一张违章抓拍图说起汽车识别到底在识别什么路口电子警察拍下一张图后台要回答的问题其实有三层画面里有没有车、这辆车是什么车型、它挂的是哪个品牌的标。很多人把这三件事混成一个「车辆识别」任务上手就堆一个分类网络结果训练完发现模型在测试集上准确率挺高一上路就翻车。原因很简单检测、车型分类、品牌分类是三个目标不同、数据分布不同的子任务硬塞进一个 head 里梯度会互相打架。这个标题里的「汽车识别车型识别品牌识别车辆识别源码」本质是一套用 Python 和深度学习把这三层任务串起来的工程方案。它适合两类人一类是想找一个能跑通的深度学习实战项目练手的学习者另一类是要做停车场、卡口、车流统计这类落地系统的工程师。前者关心代码能不能复现后者关心精度、速度和部署成本。下面我按自己实际搭过的一套流程把数据、模型、训练、推理和踩过的坑讲清楚你照着能跑出一个可用的基线。2. 三层任务怎么拆检测、车型、品牌的模型选型与数据准备2.1 为什么不能用一个分类网络通吃先明确三个任务的输出空间。汽车检测输出的是边界框坐标加置信度属于回归分类的混合问题车型识别输出的是轿车、SUV、卡车、面包车这类粗粒度类别通常十几到几十类品牌识别输出的是大众、丰田、本田、比亚迪这类细粒度类别动辄上百类而且类间差异极小——很多车标在 64×64 的输入下几乎看不出区别。把这三个任务塞进一个分类网络最直接的问题是标签体系不兼容。检测需要框的坐标分类只需要类别强行合并会让损失函数里回归项和分类项量级失衡。常见做法是两阶段先用检测模型把车抠出来再对每个车框分别跑车型分类和品牌分类两个独立的分类头。这样每个子任务的数据增强策略、输入分辨率、损失函数都能单独调排查问题也方便。我一般会选 YOLO 系列做检测因为它推理快、工程化成熟、Python 接口友好。车型和品牌分类用 ResNet 或 EfficientNet 这类 backbone品牌识别因为细粒度backbone 要深一点输入分辨率也要比车型高。2.2 数据集从哪来、怎么标检测数据需要 VOC 或 COCO 格式的标注每张图标注所有车辆的位置。车型和品牌分类数据则是裁剪后的车框图按类别分文件夹存放。公开数据集里车辆检测可以用 UA-DETRAC、BDD100K 的子集车型分类有 CompCars、Stanford Cars品牌识别 CompCars 也带品牌标注。如果做国内场景建议自己补拍一批因为公开数据里国产车标覆盖不全。标注工具用 LabelImg 或 CVAT 都行导出 VOC 格式。这里有个血泪经验标注时一定要把「车」和「车标」分开标不要指望从整车框里自动裁车标因为车标位置在整车框里占比太小裁剪误差会直接毁掉品牌分类的精度。品牌分类的训练数据最好单独标注车标区域或者至少保证裁剪框以车标为中心。数据划分按 8:1:1 分训练、验证、测试。注意同一个场景、同一辆车不要同时出现在训练集和测试集否则测试准确率会虚高。我见过有人把同一段视频抽帧后随机划分结果测试集里全是训练集的近邻帧准确率 99%实际部署掉到 60%。2.3 目录结构约定为了让后面的脚本能直接跑先约定目录结构dataset/ ├── detection/ │ ├── images/ # 所有原图 │ ├── annotations/ # VOC 格式 xml │ └── ImageSets/ │ └── Main/ # train.txt val.txt test.txt ├── vehicle_type/ │ ├── train/ │ │ ├── sedan/ │ │ ├── suv/ │ │ └── truck/ │ └── val/ └── brand/ ├── train/ │ ├── volkswagen/ │ ├── toyota/ │ └── byd/ └── val/检测数据用 VOC 的 ImageSets 划分分类数据用文件夹名当类别标签。这个结构的好处是分类部分可以直接用torchvision.datasets.ImageFolder不用自己写 Dataset。3. 用 PyTorch 把检测和分类串起来训练脚本与关键参数3.1 检测模型训练的最小命令检测部分我一般直接用 Ultralytics 的 YOLO 实现省去自己写 anchor 匹配的麻烦。先装依赖pip install ultralytics opencv-python torch torchvision然后准备一个数据配置文件vehicle_det.yamlpath: ./dataset/detection train: images/train val: images/val nc: 1 names: [vehicle]训练命令yolo detect train modelyolov8n.pt datavehicle_det.yaml epochs100 imgsz640 batch16 device0这里modelyolov8n.pt是拿预训练权重做迁移学习imgsz640是输入分辨率batch16按显存调显存不够就降到 8。nc1表示只检测「车」这一类如果你还想区分车和车标可以设成 2 类。训练完权重默认存在runs/detect/train/weights/best.pt。参数上最需要盯的是imgsz和batch。分辨率越高小目标检测越好但显存和推理时间成平方增长。卡口场景车牌和车标都小我一般用 640 起步如果车标检测效果差就上 960。epochs不用盲目加大看验证集 mAP 曲线连续 20 轮不涨就可以停。3.2 车型分类训练脚本车型分类用迁移学习backbone 换成 ResNet50。下面是一个可复现的训练脚本import torch import torch.nn as nn from torchvision import datasets, models, transforms from torch.utils.data import DataLoader # 数据增强训练集用随机裁剪和翻转验证集只做 resize 和归一化 train_tf transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(./dataset/vehicle_type/train, transformtrain_tf) val_ds datasets.ImageFolder(./dataset/vehicle_type/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) # 加载预训练 ResNet50替换最后一层全连接 model models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT) num_classes len(train_ds.classes) model.fc nn.Linear(model.fc.in_features, num_classes) model model.cuda() criterion nn.CrossEntropyLoss() # 分类头用大学习率backbone 用小学习率避免破坏预训练特征 optimizer torch.optim.SGD([ {params: model.fc.parameters(), lr: 1e-3}, {params: model.layer4.parameters(), lr: 1e-4}, {params: [p for n, p in model.named_parameters() if not n.startswith(fc) and not n.startswith(layer4)], lr: 1e-5} ], momentum0.9, weight_decay1e-4) for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels imgs.cuda(), labels.cuda() optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() # 每个 epoch 后在验证集上算准确率 model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.cuda(), labels.cuda() pred model(imgs).argmax(1) correct (pred labels).sum().item() total labels.size(0) print(fepoch {epoch}, val acc {correct/total:.4f}) torch.save(model.state_dict(), vehicle_type_resnet50.pth)这段脚本的关键在分层学习率。backbone 的浅层特征边缘、纹理是通用的不该被小数据集带偏所以学习率设得很小layer4和fc是任务相关的学习率放大。如果所有层用同一个学习率小数据集上很容易过拟合验证准确率会在几个 epoch 后掉头向下。batch_size32和num_workers4按机器调num_workers在 Windows 上有时会报错设成 0 就行。RandomCrop(224)配合Resize(256)是标准的 ImageNet 增强流程如果你的车型图片长宽比差异大可以换成RandomResizedCrop。3.3 品牌分类的差异点品牌分类脚本和车型几乎一样但有三处要改。第一输入分辨率提到 288 或 320因为车标细节多第二backbone 换成resnet101或efficientnet_b3第三加 label smoothing因为有些品牌样本少硬标签容易过拟合。criterion nn.CrossEntropyLoss(label_smoothing0.1)label smoothing 把目标从 one-hot 变成 0.9/0.1 的软标签能缓解小样本类别的过自信问题。这个参数在品牌识别上比在车型上收益明显因为品牌类别多、长尾严重。4. 推理管线怎么搭从一张图到三个结果4.1 检测加分类的串联推理训练完两个模型推理时先跑检测再把每个车框裁出来送进两个分类器。下面是一个完整的推理脚本import cv2 import torch import numpy as np from torchvision import transforms, models import torch.nn as nn from ultralytics import YOLO # 加载检测模型 detector YOLO(runs/detect/train/weights/best.pt) # 加载车型分类模型 type_model models.resnet50() type_model.fc nn.Linear(type_model.fc.in_features, 4) # 假设4类车型 type_model.load_state_dict(torch.load(vehicle_type_resnet50.pth)) type_model.cuda().eval() # 加载品牌分类模型结构同理类别数按实际改 brand_model models.resnet101() brand_model.fc nn.Linear(brand_model.fc.in_features, 50) brand_model.load_state_dict(torch.load(brand_resnet101.pth)) brand_model.cuda().eval() type_classes [sedan, suv, truck, van] brand_classes [fbrand_{i} for i in range(50)] # 实际用你的类别名 preprocess transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) 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()) # 框向外扩 5%避免裁掉车标边缘 w, h x2 - x1, y2 - y1 x1 max(0, int(x1 - 0.05 * w)) y1 max(0, int(y1 - 0.05 * h)) x2 min(img.shape[1], int(x2 0.05 * w)) y2 min(img.shape[0], int(y2 0.05 * h)) crop img[y1:y2, x1:x2] if crop.size 0: continue tensor preprocess(crop).unsqueeze(0).cuda() with torch.no_grad(): type_pred type_model(tensor).argmax(1).item() brand_pred brand_model(tensor).argmax(1).item() output.append({ box: [x1, y1, x2, y2], type: type_classes[type_pred], brand: brand_classes[brand_pred] }) return output if __name__ __main__: for item in infer(test.jpg): print(item)这里有两个细节值得说。一是框向外扩 5%因为检测框通常贴着车身车标可能在框边缘被裁掉扩一点能保住车标。二是conf0.4和iou0.5是检测后处理的阈值conf太低会引入大量误检太高会漏掉远处的小车卡口场景我一般用 0.4 到 0.5 之间。4.2 批量推理和速度优化单张推理够用但实际部署要处理视频流。把上面的infer改成接收 numpy 数组然后用cv2.VideoCapture逐帧读或者用多进程把检测和分类拆到不同进程。分类模型可以用torch.jit.trace导出成 TorchScript推理速度能提升 20% 到 30%。# 导出 TorchScript example torch.rand(1, 3, 224, 224).cuda() traced torch.jit.trace(type_model, example) traced.save(vehicle_type_traced.pt)导出后加载用torch.jit.load不再依赖原始模型定义。注意 trace 时模型必须在 eval 模式否则 BN 层和 dropout 的行为会不对。5. 避坑与排查训练不收敛、精度虚高、部署掉点5.1 损失不下降先看标签对不对现象训练几个 epoch 后 loss 一直在 2.3 附近震荡准确率等于随机猜。原因通常是类别标签和文件夹名对不上或者ImageFolder按字母序排的类别索引和你以为的不一致。解决打印train_ds.classes和train_ds.class_to_idx确认索引映射再抽一个 batch 把图片和标签可视化出来看。5.2 验证准确率高但实际用不了现象验证集 98%拿真实场景图一测一半识别错。原因多半是数据泄漏——训练集和验证集里有同一辆车的不同帧或者同一场景的相似图。解决按车辆 ID 或拍摄日期划分数据集不要随机划分。如果数据来自视频按视频段划分整段进训练或整段进验证。5.3 品牌识别在国产车上集体翻车现象测试集里合资品牌识别挺准国产新势力品牌几乎全错。原因公开数据集里国产车标样本太少模型没见过。解决自己补拍国产车标数据每个品牌至少 200 张做类别平衡或者用 focal loss 替代交叉熵让模型关注难样本。class FocalLoss(nn.Module): def __init__(self, gamma2.0, alpha0.25): super().__init__() self.gamma gamma self.alpha alpha def forward(self, logits, targets): ce nn.functional.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce) return (self.alpha * (1 - pt) ** self.gamma * ce).mean()gamma2.0是 focal loss 的常用值alpha0.25用于平衡正负样本。类别极不均衡时这个损失比交叉熵稳。5.4 推理时显存爆了现象训练时 batch 16 没事推理时处理一张 4K 图就 OOM。原因推理时没有torch.no_grad()或者检测模型输入分辨率被自动放大。解决推理代码包在with torch.no_grad():里检测前把图 resize 到 1280 长边以内分类模型用torch.cuda.empty_cache()定期清缓存。5.5 模型部署到边缘设备跑不动现象服务器上 30 FPS放到 Jetson 或 RK3588 上只有 3 FPS。原因模型没做量化或者用了服务器端的 PyTorch 而不是推理引擎。解决分类模型导出 ONNX 后用 TensorRT 或 RKNN 量化成 INT8检测模型用 YOLO 自带的导出功能转成 ONNX 再转 TensorRT。量化后精度一般掉 1 到 2 个点但速度能翻几倍。6. 把精度再往上推一点难样本挖掘和测试时增强基线跑通后如果品牌识别精度卡在 85% 上不去可以试两个技巧。第一个是难样本挖掘每个 epoch 结束后把验证集里预测错的样本挑出来复制到训练集里加权采样。实现上可以给DataLoader传一个WeightedRandomSampler错样本的权重设成正确样本的 3 倍。from torch.utils.data import WeightedRandomSampler # 假设 wrong_indices 是上一轮验证集里预测错的样本索引 weights torch.ones(len(train_ds)) weights[wrong_indices] 3.0 sampler WeightedRandomSampler(weights, num_sampleslen(train_ds), replacementTrue) train_loader DataLoader(train_ds, batch_size32, samplersampler, num_workers4)第二个是测试时增强TTA推理时对同一张图做多种变换原图、水平翻转、多尺度把预测概率平均。品牌识别上 TTA 通常能涨 1 到 2 个点代价是推理时间翻倍。def tta_predict(model, img_tensor): probs [] # 原图 probs.append(torch.softmax(model(img_tensor), dim1)) # 水平翻转 probs.append(torch.softmax(model(torch.flip(img_tensor, dims[3])), dim1)) # 多尺度 scaled torch.nn.functional.interpolate(img_tensor, scale_factor1.15, modebilinear) scaled torch.nn.functional.interpolate(scaled, size(224, 224), modebilinear) probs.append(torch.softmax(model(scaled), dim1)) return torch.stack(probs).mean(0)这两个技巧不要一上来就用先把基线跑稳确认数据没有泄漏、标签没有错再上这些。我自己的习惯是每加一个技巧就在固定测试集上跑一次涨点就留不涨就删避免堆了一堆 trick 最后不知道哪个有用。这套流程从数据准备到部署大概两周能跑通难点不在模型本身而在数据质量和标签一致性。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站