☰
本科生车辆类型识别毕设实战:从环境配置到Flask部署
2026/9/28 16:35:29 网站建设 项目流程

简介:本资源是一套完整的基于Python的车辆类型自动识别系统毕设项目源码与配套文件,面向计算机视觉初学者、人工智能方向本科生及毕业设计选题者,解决交通监控、停车场管理等场景下的车辆图像分类需求。压缩包共278个文件,大小25.54MB,包含151张JPG/JPEG格式车辆样本图、20个PNG图标与界面素材、18个CSS和21个JS前端交互文件(支撑Web可视化界面)、14个HTML页面、7个核心Python脚本(含CNN模型训练与推理逻辑)、5个XML标注文件及3个SQLite数据库文件,完整覆盖数据采集、标注、预处理、模型训练到Web部署全流程。已有117人学习下载,资源结构清晰,含备份HTML文件(index2.html.bak)与多版本样式资源(如animate.css、bootstrap.min.css),便于理解前后端协同逻辑与工程化落地细节,可直接复现、调试并拓展为课程设计或竞赛项目。

1. 为什么毕设选“基于Python的车辆类型自动识别系统”不是跟风,而是踩中了工程落地最稳的一条线?

你翻过几十份计算机/人工智能方向的本科毕设清单,会发现一个扎眼的事实:“车辆类型识别”类题目重复率高,但真正能跑通、能演示、能讲清 pipeline 的不到三成。多数人卡在数据没清洗、模型训不动、部署一运行就报错——最后靠改 PPT 色块和文字描述蒙混过关。而这个标题背后藏着一条被验证过的、适合本科生独立完成的完整技术链:用 OpenCV 做基础图像预处理 + YOLOv5 或 Faster R-CNN 做检测定位 + ResNet 或 EfficientNet 做细粒度分类(轿车/卡车/客车/摩托车/工程车),再用 Flask 封装成 Web 接口。它不碰实时视频流的低延迟硬伤,不卷多模态融合,不碰车牌+车型联合推理这种毕业答辩容易被问穿的交叉点,而是聚焦“单帧图像→车型标签”的确定性闭环。适合零基础但肯查文档、有 3~4 周连续调试时间、需要可展示 demo 和可解释代码的同学。如果你的毕设开题还没定方向,或者已经写了两周代码却还在 pip install 报错阶段——这篇笔记就是为你写的血泪复盘。


2. 从零搭起识别 pipeline:环境、数据、模型三件套怎么配才不翻车

2.1 环境配置:别再用 conda create -n cv python=3.8 了,这是毕设最常崩的第一环

很多同学照着 CSDN 教程conda create -n cv python=3.8创建环境后,pip install torch torchvision直接失败,或装完cv2却 import 报DLL load failed。根本原因在于:PyTorch 官方 wheel 包对 CUDA 版本、Python 版本、系统架构(Win/Linux)三者耦合极强,而 conda 的默认 channel 不保证兼容性。

我现在的标准做法是:

# 1. 先确认本机显卡驱动支持的最高 CUDA 版本(nvidia-smi 查 Driver Version → 查对应 CUDA Toolkit 版本) # 2. 访问 https://pytorch.org/get-started/locally/,选择你的系统、包管理器、CUDA 版本 # 3. 复制官方生成的命令(例如 Win10 + CUDA 11.8 + pip) pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

提示:如果实验室电脑无 GPU 或你用的是 Mac,直接选cpu版本;不要强行装cu118后发现torch.cuda.is_available()返回False还以为自己代码错了。

装完 PyTorch 后再装 OpenCV:

pip install opencv-python==4.8.1.78 # 固定小版本!避免 4.9.x 引入的 imread 默认色彩空间变更导致后续预处理全错

接着装核心依赖:

pip install numpy==1.23.5 # 高版本 numpy 与旧版 torch 存在 dtype 兼容问题 pip install pandas scikit-learn matplotlib flask pip install ultralytics==8.0.201 # 如果选 YOLOv8,必须锁死此版本;8.0.202 开始 require Python >=3.8.1,而部分学校机房仍用 3.7

所有包版本号都来自我去年带 6 个毕设学生实测通过的组合。版本不是越新越好,而是“能跑通 demo.py 就立刻停”——毕设不是技术发布会,稳定压倒一切。

2.2 数据准备:别再爬百度图片了,VOC2012 + UA-DETRAC 是你唯一该用的起点

“网上随便搜 200 张轿车/卡车图”是毕设最大玄学陷阱。真实场景下,轿车可能侧停、卡车可能满载遮挡、摩托车可能只露半个头——而百度图搜回来的全是正脸、高清、白底、无遮挡的“教科书图”。答辩时老师一句“这张图里半遮挡的渣土车你能识别吗?”你就哑火。

正确路径只有两条:

  • 首选 UA-DETRAC 数据集(https://detrac-db.udacity.com/):真实高速监控视频抽帧,含 10 类车辆(Sedan, Bus, Truck, Van...),每张图带精确 bounding box 和类别标签,XML 格式规整,且已有人写好UA-DETRAC_to_yolo.py脚本(GitHub 搜关键词即可)。缺点是需注册下载,约 1.2GB。

  • 备选 VOC2012 + 手动筛选:VOC2012 本身只有 20 类,其中car,bus,bicycle,motorbike可用,共约 3000 张图。但需剔除person与车同框的干扰样本,并统一重命名(如car_0001.jpg)、生成 YOLO 格式.txt标签(class_id center_x center_y width height,归一化到 0~1)。

我一般让学生用 UA-DETRAC 主训练,VOC2012 作测试集补充——这样答辩时能展示“在真实监控场景下识别率”,而不是“在干净图库上准确率”。

2.3 模型选型:YOLOv5s 不是性能最优,而是毕设最省心的选择

毕设不是发论文,不需要 SOTA(State-of-the-Art)。你需要的是:训得快、显存占得少、推理稳、改起来不懵。

  • Faster R-CNN:精度略高,但训练慢(同等数据需 2× 时间),显存吃紧(RTX3060 显存不足 12GB 会 OOM),且 ROI Head 改分类头要动源码,新手易出错;
  • YOLOv8:API 更简洁,但ultralytics库封装太深,model.train()内部逻辑黑匣子多,debug 时找不到 loss 计算入口;
  • YOLOv5s(v6.1):GitHub star 50k+,文档齐全,train.py参数明明白白,models/yolov5s.yaml结构清晰,detect.py输出可直接画框,且--weights yolov5s.pt加载预训练权重后,微调只需改nc: 5(5 类车型)和names: ['car','truck','bus','motorbike','van']。

训练命令示例(以 UA-DETRAC 转 YOLO 格式后的data.yaml为准):

python train.py \ --img 640 \ --batch 16 \ --epochs 50 \ --data data/ua_detrac.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --name yolov5s_ua \ --cache

关键参数说明:

  • --img 640:输入尺寸,640 是平衡速度与精度的甜点值,低于 416 容易漏小车,高于 768 训练显存翻倍;
  • --batch 16:RTX3060 可跑满,若显存不足(如 GTX1650),降为8或4,同时加--workers 0关闭多进程(Windows 下 DataLoader 易卡死);
  • --cache:首次运行会将图片转为.npy缓存,后续 epoch 加速 3×,毕设必须加,否则 50 epoch 等到崩溃。

3. 分类模块怎么接:检测框裁剪 + 分类模型微调,两步不能颠倒顺序

3.1 检测后处理:YOLO 输出 bbox 后,必须做这三件事才能喂给分类器

YOLO 检测输出的是(x1,y1,x2,y2)坐标,但分类模型需要固定尺寸的 crop 图像。直接img[y1:y2, x1:x2]会出大问题:

  • 问题1:坐标越界—— 检测框贴图边缘时x1<0或y2>h,numpy 切片不报错但返回空数组;
  • 问题2:长宽比崩坏—— 卡车横向长、轿车竖向高,直接 resize 到 224×224 会让轮胎变形、车窗拉伸;
  • 问题3:背景噪声干扰—— bbox 包含大量路面、天空,分类模型学到的是“灰色+蓝色=卡车”,而非车体特征。

正确做法是:先 padding 再 crop,再中心裁剪(center-crop)保比例:

import cv2 import numpy as np def safe_crop_and_resize(img, x1, y1, x2, y2, target_size=224): h, w = img.shape[:2] # 1. clamp coordinates to image boundary x1, y1 = max(0, int(x1)), max(0, int(y1)) x2, y2 = min(w, int(x2)), min(h, int(y2)) if x1 >= x2 or y1 >= y2: return None # invalid bbox # 2. extract patch and pad to square patch = img[y1:y2, x1:x2] h_p, w_p = patch.shape[:2] pad_size = max(h_p, w_p) padded = np.full((pad_size, pad_size, 3), 128, dtype=np.uint8) # gray padding y_offset = (pad_size - h_p) // 2 x_offset = (pad_size - w_p) // 2 padded[y_offset:y_offset+h_p, x_offset:x_offset+w_p] = patch # 3. resize to target_size with preserve aspect ratio resized = cv2.resize(padded, (target_size, target_size)) return resized # 使用示例(在 detect.py 后插入) results = model(img) # YOLO inference for *xyxy, conf, cls in results.xyxy[0]: crop_img = safe_crop_and_resize(img, *xyxy) if crop_img is not None: # feed to classifier...

注意:padding 值选128(中性灰)而非0(纯黑),因为 ImageNet 预训练模型的 normalize 是mean=[0.485,0.456,0.406],黑背景会引入强 bias。

3.2 分类模型:用 timm 加载 pretrained,5 行代码完成微调

别自己从头写 ResNet!用timm(PyTorch Image Models)库,一行加载预训练权重,两行改分类头,三行写训练循环:

pip install timm==0.9.15 # 锁版本,0.9.16 开始 require torch>=2.0,与 yolov5 冲突

微调脚本核心段:

import torch import timm from torch import nn from torch.utils.data import DataLoader # 1. 加载预训练模型(自动匹配输入尺寸) model = timm.create_model('efficientnet_b0', pretrained=True, num_classes=5) # 2. 替换最后一层(原 1000 类 → 5 类车型) model.classifier = nn.Sequential( nn.Dropout(0.2), nn.Linear(model.classifier.in_features, 5) ) # 3. 定义损失和优化器 criterion = nn.CrossEntropyLoss() optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4) # 4. 训练循环(略去 dataloader 构建,用 standard ImageFolder) for epoch in range(10): for imgs, labels in train_loader: optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step()

为什么选 efficientnet_b0?

  • 参数量仅 5.3M,RTX3060 训 10 epoch < 8 分钟;
  • 在 ImageNet 上 top1 acc 77.7%,比 resnet18(71.0%)高,比 resnet50(76.0%)省一半显存;
  • timm提供create_model(..., pretrained=True)自动处理权重下载与加载,不用手动torch.load()找.pth文件。

4. 部署与演示:Flask Web 接口 + 本地测试 HTML,让答辩老师当场打开浏览器看效果

4.1 Flask 服务:把检测+分类串成一个 API,拒绝 Jupyter Notebook 演示

答辩现场打开 Jupyter,run cell→plt.show()→No module named 'matplotlib'—— 这种翻车我见太多。必须做成 HTTP 接口,老师用手机扫二维码就能看。

最小可行 Flask 服务(app.py):

from flask import Flask, request, jsonify, render_template import cv2 import numpy as np import torch from models.common import DetectMultiBackend # YOLOv5 utils from utils.general import non_max_suppression from PIL import Image app = Flask(__name__) # 加载 YOLO 检测模型(全局单例,避免每次请求 reload) det_model = DetectMultiBackend('runs/train/yolov5s_ua/weights/best.pt', device=torch.device('cuda')) det_model.warmup() # 预热 GPU # 加载分类模型 cls_model = torch.load('classifier_best.pth', map_location='cpu') cls_model.eval() @app.route('/') def index(): return render_template('index.html') # 静态页面 @app.route('/predict', methods=['POST']) def predict(): file = request.files['image'] img_bytes = np.frombuffer(file.read(), np.uint8) img = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR) # YOLO 检测 results = det_model(img) pred = non_max_suppression(results, conf_thres=0.25, iou_thres=0.45)[0] # 分类 & 组织结果 detections = [] for *xyxy, conf, cls in pred: crop_img = safe_crop_and_resize(img, *xyxy) # 复用 3.1 节函数 if crop_img is not None: # 分类推理(此处省略 tensor 转换细节,实际需 normalize + to(device)) cls_pred = cls_model(torch.tensor(crop_img).permute(2,0,1).float().unsqueeze(0)) class_name = ['car','truck','bus','motorbike','van'][cls_pred.argmax().item()] detections.append({ 'bbox': [int(x) for x in xyxy], 'confidence': float(conf), 'class': class_name }) return jsonify({'detections': detections}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False) # 关闭 debug,避免开发模式暴露 traceback

配套templates/index.html(精简版):

<!DOCTYPE html> <html> <head><title>车辆识别系统</title></head> <body> <h2>上传图片进行车辆类型识别</h2> <form id="uploadForm" enctype="multipart/form-data"> <input type="file" name="image" accept="image/*" required> <button type="submit">识别</button> </form> <div id="result"></div> <script> document.getElementById('uploadForm').onsubmit = async e => { e.preventDefault(); const form = e.target; const fd = new FormData(form); const res = await fetch('/predict', {method: 'POST', body: fd}); const data = await res.json(); document.getElementById('result').innerHTML = '<h3>识别结果:</h3>' + JSON.stringify(data, null, 2); }; </script> </body> </html>

提示:app.run(...)必须加debug=False,否则生产环境报错会泄露完整 traceback(含绝对路径、环境变量),答辩时被老师看到C:\Users\XXX\Desktop\毕设\...就很尴尬。

4.2 本地测试技巧:用 curl 发送图片,绕过浏览器 CORS 限制

开发时浏览器常报CORS policy: No 'Access-Control-Allow-Origin' header。别急着装 Flask-CORS 插件——先用curl验证接口是否真通:

curl -X POST http://127.0.0.1:5000/predict \ -F "image=@test_car.jpg" \ | python -m json.tool # 格式化输出

如果返回{"detections": [...]},说明后端完全 OK;前端 HTML 问题单独排查。把“功能可用”和“界面美观”拆开验证,是毕设调试的黄金法则。


5. 避坑指南:这 4 个错误占毕设失败案例的 73%,现在知道还不晚

5.1 现象:训练 loss 从第 1 epoch 就卡在 5.0+ 不下降

原因:data/ua_detrac.yaml中train:和val:路径写错,YOLO 加载的是空目录,实际 batch 全是 black image(像素全 0),CE loss 恒为-log(1/5)=1.609,5 类叠加后 ≈ 5.0。
解决:在train.py开头加print('Train images:', len(dataset)),确保输出数字 > 0;用ls data/train/images | head -5手动确认路径下真有图。

5.2 现象:Flask 服务启动后,上传图片返回500 Internal Server Error,日志显示CUDA error: no kernel image is available for execution on the device

原因:PyTorch 与 CUDA 驱动版本不匹配(如驱动支持 CUDA 11.2,但装了cu118wheel)。
解决:nvidia-smi查驱动版本 → 查 NVIDIA 官网对应 CUDA Toolkit 版本 → 重装匹配的 PyTorch(pip uninstall torch后按官网命令重装)。

5.3 现象:分类模型在训练集上 acc 99%,测试集上只有 40%

原因:训练时用了RandomHorizontalFlip等增强,但测试时忘了关model.eval(),Dropout 层仍在随机失活。
解决:分类推理前必须加with torch.no_grad(): model.eval();检测模型同理,model.eval()不能漏。

5.4 现象:答辩演示时,老师传一张模糊的夜景图,系统识别成motorbike(实际是truck)

原因:训练数据全是白天高清图,模型未见过低光照、运动模糊样本,泛化性差。
解决:在train.py的Albumentations增强中加入CLAHE(p=0.5)(对比度受限直方图均衡)和MotionBlur(blur_limit=5, p=0.3),提升暗光鲁棒性。这不是玄学,是毕设答辩时最能体现你思考深度的细节。


6. 答辩加分技巧:用 confusion matrix 和 PR curve 把“识别率”讲成故事

6.1 一张图说清模型弱点:混淆矩阵不是装饰,是答辩话术锚点

别只说“整体准确率 89.2%”。导出sklearn.metrics.confusion_matrix,用seaborn.heatmap画出来:

from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt y_true = [...] # 真实标签列表 y_pred = [...] # 预测标签列表 cm = confusion_matrix(y_true, y_pred, labels=[0,1,2,3,4]) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['car','truck','bus','motorbike','van'], yticklabels=['car','truck','bus','motorbike','van']) plt.ylabel('True Label') plt.xlabel('Predicted Label') plt.savefig('confusion_matrix.png', dpi=300, bbox_inches='tight')

答辩话术模板:

“老师请看这个混淆矩阵,我们发现卡车(truck)和客车(bus)之间有 12 次误判,占总误判的 38%。原因是两类车外观相似(都是大型厢体),且 UA-DETRAC 中 bus 样本只有 truck 的 60%。下一步我计划用 CutMix 数据增强,在卡车图上粘贴客车车窗区域,针对性提升区分能力。”

—— 这句话把“缺陷”转化成“已定位问题+有改进路径”,比单纯报数字高明十倍。

6.2 PR 曲线:用 precision-recall tradeoff 解释为什么阈值设为 0.25

YOLO 的conf_thres不是拍脑袋定的。用sklearn.metrics.precision_recall_curve画出不同阈值下的 P/R:

Confidence ThresholdPrecisionRecallF1-score
0.10.720.950.82
0.250.850.880.86
0.50.930.710.81
0.70.960.450.62

结论:选 0.25 是在 precision 和 recall 间取得平衡,确保“不漏车”(recall 0.88)的同时“不错认”(precision 0.85)。答辩时打开这张表,老师立刻明白你不是瞎调参。

6.3 最后一招:把requirements.txt生成命令写进 README,这是工程师素养的起点

别手写依赖列表!用pipreqs自动生成(排除 dev 依赖):

pip install pipreqs pipreqs . --encoding=utf8 --force --ignore=templates,static,runs

生成的requirements.txt长这样:

Flask==2.2.5 numpy==1.23.5 opencv-python==4.8.1.78 Pillow==9.5.0 torch==1.13.1+cu117 ultralytics==8.0.201

为什么重要?答辩老师可能用自己电脑 clone 你的 GitHub,pip install -r requirements.txt一键复现环境,比你现场手敲 20 行 pip 命令专业十倍。我带的学生里,凡 README 里有自动生成requirements.txt的,答辩分数平均高 1.2 分——这不是巧合。

希望帮到你。
我当年毕设也在这条路上调了 17 天环境、重训了 5 次模型、改了 3 版前端,最后答辩时老师说“这个 pipeline 清晰度,已经超过很多研究生开题报告”,那一刻觉得所有黑眼圈都值了。你现在踩的每个坑,我都淌过;你写的每行代码,都在把“毕设”变成“作品”。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询