资讯动态

基于PyTorch和ResNet的车型识别:从数据准备到模型部署

发布时间:2026/9/15 19:17:43 来源:尧图企业网站定制
简介这是一份基于PyTorch深度学习的车型识别系统毕业设计项目主要面向正在筹备毕业设计的计算机专业学生也适合有基础图像分类项目实战需求的学习者。项目难度适中源码经过本地编译和严格调试确保可运行可同时用于课程设计、毕业设计或深度学习入门阶段的完整项目参考。压缩包共15个文件总体积约1.12MB其中10个Python脚本覆盖MobileNet、ResNet等模型实现、数据集处理、训练启动、日志记录等关键模块2个Markdown文档和1个docx手册用于说明使用流程与项目结构另含requirements.txt与.gitignore便于快速搭建环境和版本管理。已有172人学习下载。通过这份资源读者可以获得一套可独立运行的车型识别程序包括模型定义、训练与评估脚本以及配套的说明文档能够帮助理解深度学习项目的完整开发流程并在此框架上进行算法调整或功能扩展适合作为毕业设计与项目实践的参考底稿。1. 为什么车型识别会成为深度学习入门必做的图像分类项目停车场出入口的自动计费、高速收费站对客车货车的分流、二手车平台对照片资料的自动归档背后都离不开一个基础能力看懂一张车辆图片然后说出它属于哪个车型。这个项目围绕着 PyTorch 深度学习框架把“图片输入–模型推断–车型名称输出”这条链路完整走通是典型的多类别图像分类任务区别只在于最终分类粒度是轿车、SUV、卡车还是细分到宝马3系、奥迪A4这个层级。做这个题目的人通常有两类。一类是第一次独立完成端到端AI项目的学生想用Python毕业设计把理论落到工程上另一类是已经在做业务系统、想快速掌握PyTorch数据流和模型封装方法的工程师。这个项目的难点也恰好在这两个方向上模型结构可以从 torchvision 里直接拿到预训练权重真正花时间的是数据怎么组织、训练参数怎么定、最后如何把训练好的模型变成可被调用的推理接口。下面的内容按照我搭建这类系统时最常用的路线展开每一步都给可复现的代码和参数。2. 车型识别的技术选型PyTorch 与卷积神经网络的基础设计2.1 车型识别的问题边界图像分类和车辆检测不能混为一谈车型识别本质上是细粒度图像分类Fine-Grained Image Classification。输入端是一张已经包含车辆的图片输出端是这个图片所属的类别概率分布比如“大众_帕萨特_2015款”。它与车辆检测有明确区别检测要在画面中画出车辆边界框输出坐标识别则只需要对裁剪好的车辆区域做分类。真实工程场景里通常是两段式pipeline先用 YOLO 这类检测模型找到车辆区域再把裁剪结果送进车型分类模型。毕业设计如果直接拿网上的车辆图片做训练需要先保证训练数据本身已经把车辆放在画面主要位置否则模型学到的是背景特征而不是车型特征。细粒度分类的特殊性在于类别之间差异极小。奥迪A4和A6在照片角度、颜色、光线相似的情况下人眼都要仔细看进气格栅和车灯细节。针对这个问题常见的学术方案是引入注意力机制或部件定位但对一个以落地演示为目标的毕业设计来说用 ImageNet 预训练的 ResNet 做迁移学习配合数据增强和合理训练策略已经能获得不错的基线效果。后面如果还想提高再考虑双分支注意力结构或度量学习。2.2 为什么选择 PyTorch从调试方式到生态覆盖车型识别项目选择 PyTorch 而不选 TensorFlow 或 PaddlePaddle主要理由是动态计算图带来的调试便利。模型前向传播时可以用 print 直接查看中间张量的 shape 和数值断点调试时能看到真实的执行流程这对初学者理解模型行为非常重要。PyTorch 在学术和工业场景的覆盖面也广网上能找到的 pytorch 基础框架教程、pytorch安装gpu教程、anaconda配置pytorch环境教程数量多遇到报错时检索解决方案的成功率更高。torchvision 是另一个关键选型理由。它提供了在 ImageNet 上预训练好的模型权重也提供了标准的数据预处理工具。车型数据集的规模一般不会太大训练时加载预训练权重做迁移学习可以显著降低对数据量的要求。比如用 ResNet34 在约 1 万张车辆图片上微调通常 20-30 个 epoch 就能达到较高准确率如果从随机初始化开始训练同样数据量下效果会差很多。2.3 主干网络选型ResNet 是平衡精度与工程复杂度的首选车型识别项目中常见的主干网络有 AlexNet、VGG、ResNet、EfficientNet 等。AlexNet 结构简单但精度低适合做教学演示VGG 结构规整但参数量大训练和推理都慢EfficientNet 精度高但调参复杂对显存要求也更高。ResNet 通过残差连接解决了深层网络的梯度消失问题是工程上最稳妥的选择。模型参数量ImageNet Top-1 大约准确率单张 CPU 推理耗时适合场景AlexNet约 6100 万约 57%较快教学演示、结构理解VGG16约 1.38 亿约 71%慢精度要求不极端的老式方案ResNet18约 1170 万约 70%较快类别少、嵌入式部署ResNet34约 2180 万约 73%中等毕业设计首选精度与速度均衡EfficientNet-B0约 530 万约 77%中等追求精度且接受调参成本我一般会选 ResNet34 作为默认主干。它比 ResNet18 准确率高又比 ResNet50 参数少训练时显存压力小CPU 也能完成推理演示。2.4 用代码验证模型输入输出维度从 torchvision 加载预训练模型开始训练前先写一小段代码确认模型能接受 224x224 的 RGB 图片并输出指定数量的类别概率。import torch import torch.nn as nn from torchvision import models # 加载 ImageNet 预训练权重ResNet34 的输入是2D图片输出是1000类概率 model models.resnet34(weightsmodels.ResNet34_Weights.IMAGENET1K_V1) num_classes 20 # 假设项目要识别20种车型 # 替换最后的全连接层in_features 是上一层输出的特征数 model.fc nn.Linear(model.fc.in_features, num_classes) # 模拟输入一个 batch4张 224x224 的 RGB 图片 x torch.randn(4, 3, 224, 224) out model(x) print(out.shape) # 期望输出 torch.Size([4, 20])这段代码做了两件事加载预训练权重并替换分类头。model.fc.in_features是 ResNet34 最后一层池化输出特征的维度值为 512替换成nn.Linear(512, 20)后模型输出的每个元素都对应一个车型类别的得分。这里用的是随机输入所以一次前向传播就验证了数据维度是否匹配避免在真实训练时因为维度不匹配报错。weights参数在较新版本的 torchvision 中是推荐写法旧版本也可以用pretrainedTrue但会收到弃用警告。3. 数据准备目录组织、预处理参数与 PyTorch 数据集封装3.1 数据集从哪来开源数据集与自行采集的组织方式车型识别最常用的公开数据集是斯坦福 Cars Dataset包含 196 类、约 1.6 万张图片类别覆盖不同品牌、型号和年份。另一个常见选择是 CompCars 数据集规模更大但标注噪声也更多。如果做的是一个自定义题目比如只识别“轿车、SUV、MPV、卡车”四类用爬虫或现场拍摄自行收集数据时建议每个类别不少于 500 张图片并且要让图片覆盖多种颜色、光照和拍摄角度。训练集和验证集要分开存放类别分布尽量一致避免训练集里白色车居多、验证集里全是黑色车这种分布偏差。收集数据时还要注意一个容易忽略的问题同一辆车在不同角度下差异很大。如果某类车型的训练图片全部来自车头视角验证时放一张车尾图片模型大概率会判断错误。尽量保证每个类别的训练数据涵盖正面、侧面、背面视角这比单纯增加图片数量更有效。3.2 目录结构用 ImageFolder 直接读取训练数据PyTorch 的torchvision.datasets.ImageFolder能从文件夹结构自动生成标签要求每个类别的图片放在以类别名命名的子目录中。常见的目录约定是data/下按train/和val/分开再往下按品牌_车型/建子目录。data/ ├── train/ │ ├── Audi_A4/ │ ├── BMW_3Series/ │ └── ... └── val/ ├── Audi_A4/ ├── BMW_3Series/ └── ...这样组织的直接好处是ImageFolder会按字母序给类别生成索引训练脚本里不需要手工维护类别到数字 ID 的映射表。训练完成后把train_dataset.classes保存下来作为推理阶段的标签字典即可这个细节在部署时非常关键。3.3 数据增强参数哪些操作该用、哪些不该用车辆图片的预处理分为两部分基础缩放归一化和数据增强。基础操作保证输入尺寸与模型一致增强操作提升泛化能力。常见的组合如下操作推荐参数作用与说明Resize256x256先放大到大于模型输入尺寸留出裁剪空间CenterCrop / RandomCrop224x224与 ResNet 输入尺寸对齐RandomCrop 用于训练RandomHorizontalFlip概率 0.5车辆左右对称翻转不会改变车型语义RandomRotation±10 度模拟拍摄角度抖动角度过大会出现无效背景ColorJitterbrightness0.2, contrast0.2, saturation0.2适应不同光线环境NormalizeImageNet 均值 [0.485, 0.456, 0.406]方差 [0.229, 0.224, 0.225]将像素分布对齐到预训练模型的期望分布不能使用 RandomVerticalFlip垂直翻转会让车倒立破坏车辆结构语义。类别数很少时也不建议用 RandomErasing它随机遮挡关键部位反而可能让模型学到错误的特征。训练结束后的验证集和测试集只做 Resize 和 CenterCrop不做随机增强保证评估结果的稳定性。3.4 用 Dataset 和 DataLoader 把数据接入训练循环下面这个代码片段是训练数据流水线的标准封装直接使用 ImageFolder 可以省去自定义 Dataset 的工作。from torch.utils.data import DataLoader from torchvision import transforms, datasets # 训练集包含随机增强 train_transforms transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.RandomRotation(10), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 验证集只做确定性变换 val_transforms transforms.Compose([ transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset datasets.ImageFolder(rootdata/train, transformtrain_transforms) val_dataset datasets.ImageFolder(rootdata/val, transformval_transforms) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue)这里的每个参数都有实际影响。num_workers4用多个子进程加载图片把 CPU 图片解码和 GPU 计算并行起来避免训练时 GPU 空等pin_memoryTrue在显存允许的情况下能略微加快数据从 CPU 到 GPU 的拷贝验证集不设置drop_last因为验证集最后不足一个 batch 的样本也应该参与评估。如果你用的是 Windows 系统且 num_workers 设置过大偶尔会出现内存增长或加载报错可以先降为 2 再观察。这一步也是理解 pytorch 张量基础的好时机图片从 PIL 对象转成张量、按 [batch, channel, height, width] 排列都是在数据加载阶段完成的。4. 基于 ResNet 的训练脚本损失函数、优化器与训练参数调优4.1 加载预训练权重并改造分类层训练核心是迁移学习把 ImageNet 上学会的通用视觉特征迁移到车型识别任务上。代码上只需要加载预训练模型再替换最后的全连接层。import torch import torch.nn as nn import torch.optim as optim from torchvision import models model models.resnet34(weightsmodels.ResNet34_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, len(train_dataset.classes)) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)加载预训练权重后模型最后一层原来输出 1000 类现在输出len(train_dataset.classes)。如果类别数只有 20新的全连接层参数是随机初始化的需要更大的学习率来训练但前面的卷积层已经学到了通用的边缘、纹理和形状特征只需要微调学习率不宜过大。一个常用做法是给分类头单独设置较高的学习率特征提取部分用较小的学习率这个策略在后续参数表里会说明。4.2 完整的训练循环epoch、验证与模型保存训练循环里最容易出错的是模型模式切换。model.train()告诉 BatchNorm 层使用当前 batch 的统计量model.eval()则让 BatchNorm 使用训练阶段累计的 running statistics。如果不切回 eval 模式验证结果会出现明显的波动。from tqdm import tqdm # 损失函数带标签平滑缓解过拟合 loss_fn nn.CrossEntropyLoss(label_smoothing0.1) # 优化器分类头用稍大的学习率特征层用小学习率 optimizer optim.AdamW([ {params: model.layer4.parameters(), lr: 1e-4}, {params: model.fc.parameters(), lr: 1e-3} ], lr1e-4, weight_decay5e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) best_acc 0.0 checkpoint_dir checkpoints for epoch in range(30): model.train() total_loss 0.0 for images, labels in tqdm(train_loader): images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss loss_fn(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() # 一个 epoch 结束后做验证 model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) preds outputs.argmax(dim1) correct (preds labels).sum().item() total labels.size(0) acc correct / total print(fepoch{epoch 1}, loss{total_loss / len(train_loader):.4f}, val_acc{acc:.4f}) if acc best_acc: best_acc acc torch.save({ model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), best_acc: best_acc, epoch: epoch, }, f{checkpoint_dir}/best_model.pt) scheduler.step()验证部分用torch.no_grad()包裹是因为验证阶段不需要计算梯度能显著减少显存占用和推理耗时。optimizer.zero_grad()必须放在loss.backward()之前否则梯度会累加到上一次的数值上。4.3 训练参数表最常用的一组推荐值训练能否快速收敛取决于下面几个参数是否匹配。不同数据集规模下推荐值会有差异但下面的表格适用于大多数万级图片的车型识别项目参数推荐值说明batch_size32 或 64受显存限制ResNet34 224x224 在 8GB 显存下推荐 32特征层学习率1e-4 到 3e-4太大容易破坏预训练特征分类头学习率1e-3新初始化的层需要更大步长weight_decay5e-4抑制过拟合类别多时可调到 1e-3epoch 数20 到 40观察 val_acc 不再上升后提前停止label_smoothing0.1防止模型对训练标签过分自信学习率调度CosineAnnealingLR比固定学习率更平稳关于 epoch 的理解很多人误以为越多越好。实际训练时要注意验证集准确率曲线如果 val_acc 连续 5 个 epoch 不增长就说明模型已经开始过拟合或学习率过小。此时应停止训练恢复之前保存的最好 checkpoint而不是继续跑完所有设定的 epoch。4.4 训练不收敛时先查哪个环节第一个要查的是数据加载是否正常。把训练集的一个 batch 打印出来在没有任何增强的情况下看图片和标签是否对应有些数据放置在爬取时已经损坏PIL 库打开时报错却不会影响 ImageFolder 建立索引。第二个要查的是归一化是否生效忘记用 ImageNet 的均值和方差就会导致模型训练初期 loss 下降极慢。第三个是学习率建议先用一个小 epoch 跑一次loss 如果从 2.0 以上不下降再考虑把学习率调大反之如果 loss 直接变成 nan说明学习率过大。GPU 显存不足导致的 OOM 错误也是常见问题。优先把 batch_size 从 32 降到 16如果还溢出把输入尺寸从 224 降到 192并同步修改模型和预处理中的尺寸参数。这里提醒一点车牌识别和车型识别经常在同一套系统里出现但车牌识别本质上是一个 OCR 任务需要检测字符区域再做文字识别不要试图用车型分类模型去完成两者对图像分辨率和预处理的要求完全不同。5. 推理、评估与一个可演示的技巧把模型导出为 TorchScript训练完成后模型文件是大头保存的 checkpoint里面同时包含模型参数和优化器状态适合断点续训但不适合直接交付。推理阶段通常单独写一个脚本加载 state_dict、重建骨架模型、载入参数然后对单张图片做预测。import torch from torchvision import transforms from PIL import Image device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet34() model.fc torch.nn.Linear(model.fc.in_features, num_classes) checkpoint torch.load(checkpoints/best_model.pt, map_locationcpu) model.load_state_dict(checkpoint[model_state_dict]) model model.to(device) model.eval() transform transforms.Compose([ transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) image Image.open(test_images/Audi_A4_sample.jpg).convert(RGB) x transform(image).unsqueeze(0).to(device) with torch.no_grad(): logits model(x) probs torch.softmax(logits, dim1) top5 probs.topk(5) class_names train_dataset.classes for i in range(5): print(class_names[top5.indices[0][i]], round(top5.values[0][i].item(), 4))预测结果不要只输出argmax前 5 的置信度分布能看出模型在哪些近邻类别之间摇摆比如奥迪A4和奥迪A6。如果一个样本的 top1 置信度低于 0.6说明这张图本身可能存在遮挡、模糊或视角异常实际系统里应该触发人工复核而不是直接采信。比单张图片推理更值得封装的是模型导出。把训练好的模型转成 TorchScript 格式可以脱离原始 Python 类和训练代码独立加载在毕业设计演示时也不用把整棵 model 定义重新跑一遍。导出的常见做法是先用torch.jit.trace对一个样例输入做跟踪再保存为.pt文件# 导出前必须 eval确保 BatchNorm 使用固定的 running statistics scripted_model torch.jit.trace(model, example_inputsx) scripted_model.save(vehicle_type_traced.pt) # 重新加载并推理 loaded_model torch.jit.load(vehicle_type_traced.pt, map_locationcpu) with torch.no_grad(): traced_out loaded_model(x) print(traced_out.shape)torch.jit.trace的问题在于脚本只记录实际执行过的控制流如果模型里有if语句依赖张量数值trace 会固定住一个分支。ResNet 结构没有这类动态控制流所以 trace 是安全的。如果你的模型里为了调试写了依赖 Python 变量的分支它不会体现在 trace 结果里此时需要改用torch.jit.script做源码级编译但它对 Python 语法有更多限制遇到不支持的语法就慢慢替换。推理前记得把输入张量设备与模型设备保持一致CPU 上训练完的模型导出到 GPU 加载时.to(device)的调用顺序会在输出结果的精度上出现细微差别但不会影响类别判断。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价