【毕设项目】基于CNN+ResNet双模型的10类车辆图像分类识别

📅 发布时间:2026/7/23 17:14:44
【毕设项目】基于CNN+ResNet双模型的10类车辆图像分类识别 基于CNN的10类车辆图像分类识别——从模型训练到Flask Web应用部署全流程解析摘要本文完整记录了从零开始构建一个车辆图像分类识别系统的全过程。我们使用PyTorch训练了两个模型一个是从零搭建的4层卷积神经网络CustomCNN另一个是基于ImageNet预训练的ResNet18迁移学习模型。在此基础上我们基于Flask开发了一个功能完备的Web应用系统支持单图、批量、视频、摄像头四种识别方式并集成了用户认证、历史记录管理、模型切换等企业级功能。本文不仅涵盖算法原理、训练技巧、调参心得还包括完整的工程实现细节、数据库设计、安全措施和部署指南适合作为毕业设计或深度学习入门项目的参考。目录引言为什么做这个项目数据集详解与预处理策略模型设计CustomCNN vs. ResNet183.1 CustomCNN从零搭建的基线模型3.2 ResNet18迁移学习的威力3.3 残差网络核心原理公式推导训练过程全记录4.1 数据加载与增强4.2 训练循环与早停4.3 学习率调度与优化器选择4.4 训练日志与可视化实验结果与深度分析5.1 准确率、混淆矩阵、F1-score5.2 双模型对比雷达图5.3 错例分析与改进思路Web应用系统架构设计6.1 为什么选择Flask SQLite6.2 整体架构与请求流程图6.3 用户认证系统注册/登录/Session6.4 数据库设计ER图、表结构、索引优化核心功能模块实现详解7.1 单张图片识别含结果标注7.2 批量图片识别网格展示7.3 视频识别逐帧推理输出合成7.4 摄像头实时识别WebRTC 轮询7.5 历史记录管理筛选、分页、删除7.6 模型切换动态加载权重前后端交互与API设计8.1 RESTful API列表8.2 前端JavaScript关键逻辑8.3 安全性考量XSS/CSRF/文件上传部署与性能测试9.1 硬件环境与推理速度9.2 压力测试与并发处理9.3 容器化部署建议Docker总结与展望10.1 项目收获10.2 未来改进方向10.3 常见答辩问题FAQ1. 引言为什么做这个项目随着智慧城市和自动驾驶技术的迅猛发展车辆识别已成为计算机视觉领域的研究热点之一。无论是交通流量监测、停车场管理还是违法抓拍精准的车辆分类都是基础性环节。然而现实场景中收集海量标注数据成本高昂如何在小样本条件下训练出高精度模型是工程落地的关键难题。本项目旨在探索两种经典技术路线在小样本车辆分类任务上的表现传统路线从头设计并训练一个卷积神经网络CustomCNN不借助任何外部数据。迁移学习路线利用在ImageNet上预训练的ResNet18仅微调最后一层分类器。通过对比我们希望能为类似场景提供实践参考。同时为了让模型真正“可用”我们构建了一个完整的Web应用让用户通过浏览器就能上传图片、视频甚至连接摄像头进行实时识别大大降低了技术使用门槛。2. 数据集详解与预处理策略2.1 数据集来源与组织本数据集包含10类常见车辆共1600张图片。每类160张按7:1的比例划分为训练集1400张和验证集200张。所有图片均为RGB彩色图像尺寸不等但最长边大多在300~800像素之间。部分图片质量参差不齐存在光照不均、部分遮挡、背景复杂等情况这为模型泛化提出了挑战。2.2 图像预处理与归一化在PyTorch中我们使用transforms.Compose定义预处理流水线fromtorchvisionimporttransforms# 训练集增强train_transformtransforms.Compose([transforms.Resize((224,224)),transforms.RandomHorizontalFlip(p0.5),transforms.RandomVerticalFlip(p0.2),transforms.RandomRotation(15),transforms.RandomResizedCrop(224,scale(0.8,1.0)),transforms.ColorJitter(brightness0.1,contrast0.1,saturation0.1),transforms.ToTensor(),transforms.Normalize(mean[0.485,0.456,0.406],std[0.229,0.224,0.225])])# 验证集仅缩放和归一化val_transformtransforms.Compose([transforms.Resize((224,224)),transforms.ToTensor(),transforms.Normalize(mean[0.485,0.456,0.406],std[0.229,0.224,0.225])])这里有两个关键点归一化参数使用的是ImageNet的均值和标准差这对于迁移学习模型ResNet18至关重要因为预训练权重就是在这组参数下训练的。即使CustomCNN从零训练使用相同的归一化也有助于收敛。数据增强我们引入了多种增强手段包括水平/垂直翻转、旋转、裁剪缩放和颜色抖动。这些操作能有效增加样本多样性提升模型泛化能力。实验证明不使用增强时CustomCNN准确率仅58%使用后提升至66%。2.3 类别映射将文件夹名称映射为数字标签最终得到10个类别索引3. 模型设计3.1 CNN从零搭建的基线模型网络结构详解我们设计了一个4层卷积神经网络每个卷积块包含卷积层 → 批归一化 → ReLU → 最大池化。全连接部分采用三层结构中间加入Dropout防止过拟合。importtorch.nnasnnclassCustomCNN(nn.Module):def__init__(self,num_classes10):super(CustomCNN,self).__init__()self.conv1nn.Sequential(nn.Conv2d(3,64,kernel_size3,padding1),nn.BatchNorm2d(64),nn.ReLU(inplaceTrue),nn.MaxPool2d(2))self.conv2nn.Sequential(nn.Conv2d(64,128,kernel_size3,padding1),nn.BatchNorm2d(128),nn.ReLU(inplaceTrue),nn.MaxPool2d(2))self.conv3nn.Sequential(nn.Conv2d(128,256,kernel_size3,padding1),nn.BatchNorm2d(256),nn.ReLU(inplaceTrue),nn.MaxPool2d(2))self.conv4nn.Sequential(nn.Conv2d(256,512,kernel_size3,padding1),nn.BatchNorm2d(512),nn.ReLU(inplaceTrue),nn.MaxPool2d(2))self.avgpoolnn.AdaptiveAvgPool2d((1,1))self.fc1nn.Sequential(nn.Linear(512,256),nn.ReLU(inplaceTrue),nn.Dropout(0.5))self.fc2nn.Sequential(nn.Linear(256,128),nn.ReLU(inplaceTrue),nn.Dropout(0.3))self.fc3nn.Linear(128,num_classes)defforward(self,x):xself.conv1(x)xself.conv2(x)xself.conv3(x)xself.conv4(x)xself.avgpool(x)xx.view(x.size(0),-1)xself.fc1(x)xself.fc2(x)xself.fc3(x)returnx设计考量为什么是4层我们尝试过更浅2-3层的网络特征提取能力不足准确率徘徊在50%左右更深5-6层的网络在1400张图片上极易过拟合验证集loss不降反升。4层是经验上的平衡点。通道数翻倍策略浅层边缘/纹理用少量通道深层语义用更多通道符合视觉处理的一般规律。使用BatchNorm不仅加速训练还起到轻微正则化作用减少对Dropout的依赖。参数量与计算量总参数量约326万在GPU上单次前向传播约3ms。对于实时应用足够快。3.2 ResNet18迁移学习的威力ResNet18是残差网络家族中最轻量的一员由4个残差块组成每个块包含2个卷积层除了第一个块可能包含下采样总共有18层包括卷积、全连接等。我们直接使用torchvision.models.resnet18加载预训练权重。迁移学习策略importtorchvision.modelsasmodels modelmodels.resnet18(pretrainedTrue)# 冻结所有卷积层参数forparaminmodel.parameters():param.requires_gradFalse# 替换最后的全连接层num_ftrsmodel.fc.in_features model.fcnn.Linear(num_ftrs,10)# 只有fc层的参数可训练forparaminmodel.fc.parameters():param.requires_gradTrue为什么只训练最后一层因为我们的数据集很小每类140张如果解冻所有层模型会过拟合。只训练分类头约51万参数相当于将ImageNet学习到的通用特征边缘、纹理、形状重新组合成10类车辆的判别特征风险低且效率高。训练超参数差异由于只训练一层使用较大的学习率1e-3也不会导致发散且无需学习率衰减。我们使用Adam优化器batch size32早停patience10。3.3 残差网络核心原理公式推导ResNet的突破在于残差连接。传统网络堆叠层数越多越容易出现梯度消失或退化准确率饱和甚至下降。残差块的定义为y F(x, {Wi}) x其中x是输入F是残差映射一般为两个卷积层BNReLUx是恒等映射。在反向传播时损失L对输入x的梯度为∂L/∂x ∂L/∂y * (1 ∂F/∂x)这里的常数项1保证了梯度可以直接流向前层即便∂F/∂x很小也不会阻断梯度。这就是ResNet能训练上百层的关键。在ResNet18中每个残差块由两个3×3卷积组成通道数在块间逐步翻倍64→128→256→512。我们冻结了所有残差块的参数仅利用其强大的特征提取能力。4. 训练过程全记录4.1 数据加载与增强使用torchvision.datasets.ImageFolder加载数据并用DataLoader批量迭代fromtorchvisionimportdatasetsfromtorch.utils.dataimportDataLoader train_datasetdatasets.ImageFolder(rootdataset/train,transformtrain_transform)val_datasetdatasets.ImageFolder(rootdataset/val,transformval_transform)train_loaderDataLoader(train_dataset,batch_size32,shuffleTrue,num_workers4)val_loaderDataLoader(val_dataset,batch_size32,shuffleFalse,num_workers4)4.2 训练循环与早停我们实现了标准的训练循环每个epoch计算训练loss和验证准确率并保存最佳模型。4.3 学习率调度与优化器选择对于CustomCNN我们使用了ReduceLROnPlateau调度器当验证loss连续5个epoch不下降时学习率减半factor0.5防止在局部震荡。schedulertorch.optim.lr_scheduler.ReduceLROnPlateau(optimizer,modemin,factor0.5,patience5)在每个epoch后调用scheduler.step(val_loss)对于ResNet由于只训练一层我们固定学习率1e-3没有使用调度器因为验证准确率持续上升直至早停未出现平台期。4.4 训练日志与可视化我们使用matplotlib绘制训练曲线loss和accuracy并将图像保存到output目录。同时使用sklearn的classification_report和confusion_matrix生成详细指标。CNN的训练曲线训练loss平稳下降验证loss与训练loss接近没有过拟合两者差距小。ResNet的曲线训练第1轮准确率即达64%第3轮超过80%第10轮达88%最终91%且验证loss持续走低无过拟合迹象说明迁移学习非常高效。5. 实验结果与深度分析5.1 准确率、混淆矩阵、F1-score最终ResNet在验证集上的最佳准确率为91%Macro-F1为90.97%。混淆矩阵显示家用轿车family sedan和SUV之间误分类最多各有2-3例因为两者均为轿车形态侧面轮廓相似。出租车taxi被误判为家用轿车的情况也有出现可能是因为部分出租车图片颜色与家用轿车相近。6. Web应用系统架构设计模型训练完成后我们将其封装为一个Web应用让用户通过浏览器即可使用。下面介绍系统架构和设计决策。6.2 整体架构与请求流程图系统采用经典的前后端不分离架构但前后端逻辑清晰分离。前端页面由Jinja2模板渲染动态交互通过JavaScript AJAX完成。请求流程以单张图片识别为例用户选择图片 → JavaScript读取文件并构建FormData → 发送POST请求到 /predict/image → Flask接收文件保存至static/uploads/安全命名 → 调用model_utils.predict_image()执行预处理和推理 → 生成带标注的结果图保存至static/results/ → 返回JSON类别、置信度、Top3、图片路径等 → JavaScript解析JSON更新DOM展示图片、进度条、柱状图 → 异步发送保存请求到 /api/history 存储记录6.3 用户认证系统注册/登录/Session使用Flask-Login扩展管理用户会话。用户密码经过哈希存储绝不存明文。fromflask_loginimportUserMixin,login_user,logout_user,login_requiredclassUser(UserMixin,db.Model):iddb.Column(db.Integer,primary_keyTrue)usernamedb.Column(db.String(80),uniqueTrue,nullableFalse)password_hashdb.Column(db.String(200),nullableFalse)created_atdb.Column(db.DateTime,defaultdatetime.utcnow)defset_password(self,password):self.password_hashgenerate_password_hash(password)defcheck_password(self,password):returncheck_password_hash(self.password_hash,password)注册时校验用户名唯一性和密码长度≥6位。登录成功后设置remembercookie默认31天有效期。6.4 数据库设计ER图、表结构、索引优化数据库包含两张表users和recognition_history。ER图users (1) ────── (N) recognition_history一个用户可以拥有多条识别记录。users表结构字段类型约束说明idINTEGERPRIMARY KEY AUTOINCREMENT用户IDusernameTEXTUNIQUE NOT NULL登录用户名password_hashTEXTNOT NULL哈希密码created_atDATETIMEDEFAULT CURRENT_TIMESTAMP注册时间recognition_history表结构字段类型约束说明idINTEGERPRIMARY KEY AUTOINCREMENT记录IDuser_idINTEGERFOREIGN KEY (users.id) NOT NULL所属用户input_typeTEXTNOT NULL图片/批量/视频/摄像头input_filenameTEXT原始文件名result_summaryTEXTNOT NULL简短摘要如SUV - 92.5%result_detailTEXTJSON格式的完整结果confidenceFLOAT最高置信度model_usedTEXTNOT NULL使用的模型名称processing_timeFLOAT处理耗时秒result_file_pathTEXT结果文件路径标注图/视频created_atDATETIMEDEFAULT CURRENT_TIMESTAMP识别时间索引设计为user_id和created_at建立索引加速按用户查询和时间排序。CREATEINDEXidx_user_idONrecognition_history(user_id);CREATEINDEXidx_created_atONrecognition_history(created_at);为什么单独存储result_summary和confidence因为列表页需要频繁展示这些字段如果每次都从result_detail的JSON中解析会增加性能开销冗余存储提升查询速度。7. 核心功能模块实现详解7.1 单张图片识别含结果标注后端处理函数defpredict_image(file_path,model,device):imageImage.open(file_path).convert(RGB)# 预处理image_tensorval_transform(image).unsqueeze(0).to(device)# 推理model.eval()withtorch.no_grad():outputsmodel(image_tensor)probstorch.softmax(outputs,dim1).cpu().numpy()[0]pred_idxnp.argmax(probs)confidenceprobs[pred_idx]# 获取Top3top3_idxnp.argsort(probs)[-3:][::-1]top3[(class_names[i],probs[i])foriintop3_idx]# 生成标注图result_imgdraw_annotation(image,class_names[pred_idx],confidence)result_pathsave_result_image(result_img)return{class:class_names[pred_idx],confidence:float(confidence),top3:top3,probabilities:probs.tolist(),result_image:result_path}前端展示使用Canvas绘制概率柱状图Top5并用进度条展示置信度绿色≥80%橙色60-80%红色60%。7.2 批量图片识别网格展示前端使用input typefile multiple后端接收request.files.getlist(files)循环处理每张图片返回结果列表。前端用网格卡片展示每张卡片包含缩略图和预测标签。7.3 视频识别逐帧推理输出合成视频处理使用OpenCV。主要步骤读取视频获取总帧数、FPS、尺寸。设定采样间隔例如每3帧处理1帧或按时间间隔每秒处理3帧。循环读取帧跳过非采样帧对采样帧进行推理。在帧上绘制类别和置信度写入输出视频使用cv2.VideoWriter。统计所有处理帧的类别分布和平均置信度。返回输出视频URL和统计信息。注意为了防止视频处理超时我们限制了最大处理时长如30秒超过则截断。同时在前端显示进度条。7.4 摄像头实时识别WebRTC 轮询前端流程navigator.mediaDevices.getUserMedia({ video: true })获取流。渲染到video元素。使用setInterval每300ms从canvas截取一帧canvas.drawImage(video)。将Canvas转为base64 JPEG发送到/predict/camera。后端解码base64执行推理返回结果。前端更新结果展示类别、置信度、概率柱状图并计算FPS。7.5 历史记录管理筛选、分页、删除历史记录页面支持按输入类型、模型、时间范围今天/本周/本月/自定义筛选。关键词搜索在文件名和摘要中匹配。分页每页15条。批量选择删除和清空全部需二次确认。前端使用AJAX请求/api/history获取数据动态渲染表格。7.6 模型切换动态加载权重在app.py中维护一个全局变量current_model_name切换时更新并重新加载模型权重到内存中使用torch.load。app.route(/api/model,methods[PUT])defswitch_model():model_namerequest.json.get(model_name)ifmodel_namein[customcnn,resnet18]:globalcurrent_model,current_model_name current_modelload_model(model_name)current_model_namemodel_namereturnjsonify({success:True})returnjsonify({success:False}),400切换后所有识别接口使用新的模型。8. 前后端交互与API设计8.1 RESTful API列表方法路由功能请求参数返回POST/login用户登录username, password重定向或JSONPOST/register用户注册username, password, confirm重定向或JSONGET/logout登出-重定向POST/predict/image单张图片识别fileJSON结果图片路径POST/predict/images批量识别filesJSON结果列表POST/predict/video视频识别fileJSON视频URL统计POST/predict/camera摄像头帧识别image(base64)JSON类别置信度POST/predict/camera/save保存摄像头会话statsJSONGET/api/history历史列表page, filtersJSON分页数据GET/api/history/stats统计数据-JSONDELETE/api/history/删除单条-JSONPOST/api/history/batch-delete批量删除ids[]JSONDELETE/api/history/clear清空全部-JSONPUT/api/model切换模型model_nameJSON8.2 前端JavaScript关键逻辑主要功能集中在static/js/main.js中uploadImage()上传单张图片处理结果显示。uploadImages()批量上传网格展示。uploadVideo()上传视频轮询进度。startCamera()启动摄像头持续轮询识别。updateHistoryTable()加载历史记录支持筛选和分页。8.3 安全性考量XSS/CSRF/文件上传XSS防护Jinja2模板默认转义所有变量API返回的文本在前端插入时使用textContent而非innerHTML避免执行恶意脚本。CSRF防护Flask-WTF可扩展但本系统由于使用Session Cookie且设置了SameSiteLax已能抵御大部分CSRF攻击。文件上传安全使用werkzeug.utils.secure_filename清洗文件名防止路径穿越。限制允许的文件扩展名图片jpg/png/bmp/gif视频mp4/avi/mov/mkv。限制文件大小图片≤16MB视频≤200MB。密码安全使用pbkdf2:sha256加盐哈希暴力破解成本极高。9. 部署与性能测试9.1 硬件环境与推理速度训练和测试均在一台配备NVIDIA RTX 4060 Laptop GPU8GB显存和Intel i7-13700H处理器的笔记本电脑上完成。GPU推理单张图片含预处理约30~60ms。CPU推理单张图片约200~500ms。视频处理每帧推理约60msGPU采样后一个10秒视频处理约2~3秒。9.2 压力测试与并发处理使用locust进行简单压力测试模拟10个并发用户同时上传图片系统响应时间平均约120msGPUCPU利用率约40%内存占用约2GB加载两个模型时。由于Flask默认是同步阻塞的高并发下可能排队但本系统面向个人使用并发需求低无需引入异步框架。10. 总结与展望10.1 项目收获通过这个项目我完整地经历了深度学习项目的全生命周期数据准备、模型设计、训练调优、Web开发、部署测试。其中最大的收获是迁移学习的威力在数据有限时不要轻易从零训练利用预训练模型能事半功倍。工程化思维模型再精确不能落地也是空谈。构建Web应用需要考虑文件处理、数据库、安全性、用户体验等多方面因素。系统设计能力从ER图到API设计再到前后端分离的交互方式锻炼了全栈开发能力。10.2 未来改进方向虽然已经达到91%的准确率但仍有提升空间模型升级尝试EfficientNet或Vision Transformer可能达到95%以上。数据扩充通过爬虫收集更多图片特别是易混淆的车型。功能增强增加用户自定义类别、模型在线微调、识别结果导出报告等。性能优化引入模型量化INT8加快推理使用WebSocket实现摄像头低延迟。10.3 常见答辩问题FAQQ1: 为什么不用YOLO或SSD这类目标检测模型A: 我们的任务是分类而非检测输入图片已经裁剪为以车辆为主体不需要定位。如果未来需要复杂场景多车辆同时识别会考虑检测模型。Q2: 数据集中各类别图片数量不均会有什么影响A: 本数据集是均衡的每类160张。如果类别不平衡可以采用加权损失或过采样。Q3: Web应用中视频识别为什么不能实时处理A: 因为推理每帧需要数十毫秒视频通常每秒30帧无法达到实时。我们采用抽帧策略每秒处理3帧既保证覆盖不同时刻又控制计算量。若需真正实时需要TensorRT等加速。感谢阅读祝你学习顺利