
简介这是一份基于深度学习的车道线检测系统毕业设计项目面向需要完成毕设、课程设计或工程实训的初学者与进阶开发者。系统采用Python 3.8、Django框架与MySQL 5.7构建以YOLOv5为核心算法通过卷积神经网络对车道线图像进行特征提取与识别实现首页、图片检测、图片管理、视频检测、个人信息、用户管理等完整功能模块。资源包共2000个文件整体约978MB内容主要包括Python源码、YOLOv5模型配置、图像训练与标注数据、Shell脚本、SQL数据库文件以及开题报告和设计文档等目录结构清晰便于直接运行与二次开发。目前已有89人学习下载。项目附带可运行源码、SQL文件、开题报告、任务书和设计文档材料完整能帮助读者快速理解系统架构与实现思路是一套适合用于毕业设计答辩和实际项目起步的完整参考方案。1. 拿到这个“车道线检测系统”压缩包先别急着跑训练做自动驾驶和ADAS相关毕设、课题的人大概率都下过类似“基于深度学习的车道线检测系统”的压缩包。解压之后通常是一个Django工程、一个训练脚本、一堆权重文件。很多人第一反应是把它当目标检测任务拿YOLO直接去画框跑出来的效果惨不忍睹。这不是模型不行而是任务定错了。车道线检测的本质是像素级分割框住一条线是没意义的关键是把每个像素准确地分类成“车道线”或“背景”再在分割结果基础上拟合出车道线方程。这套系统的核心价值就在这条pipeline上用深度学习模型做分割用Django做Web封装用户上传一张车道图片页面直接返回画好线的结果。它适合两类人一类是深度学习入门者想做完整的图像分割落地项目另一类是Django开发者想了解怎么把一个PyTorch模型接到Web后端里。我基于标题和行业里最常见的工程实践把这个系统的设计思路、关键代码、参数配置和踩坑记录完整拆一遍你可以直接照着复现到自己的项目里。2. 模型选型与训练车道线检测为什么选分割网络而不是目标检测2.1 分割网络对比U-Net、SegNet还是轻量自定义模型车道线的视觉特征很特殊细长、连续、颜色单一、背景复杂。拿检测框去框一条细线框的宽高比极端回归框参数很容易抖动。所以主流方案里车道线检测基本都用分割网络。U-Net是这类任务里最稳的选择编码器下采样提取语义特征解码器上采样恢复空间分辨率加上skip connection把底层细节传回来对小目标、细长结构的还原能力好显存占用也不高。SegNet也行但它保留的是池化索引细节恢复能力比skip connection弱一点车道线这种细结构容易断。常见做法是在U-Net基础上做轻量化改造。编码器用预训练的ResNet34或者MobileNetV3解码器保持U-Net结构但通道数减半。模型输出层用1x1卷积把通道压成1经过sigmoid得到每个像素属于车道线的概率。用ResNet34做backbone输入分辨率512x288车道图片一般是宽幅训练显存占用在8GB左右推理一张大约30ms这个量级放在Django后端完全够用。训练数据一般用TuSimple数据集它有3626张训练图片和2782张测试图片标注是车道线坐标点。处理方式是先按标注点把线画成掩膜生成对应的分割标签再交给模型做二分类训练。自己采集数据的话标注用LabelMe或CVAT画多边形比逐点标注高效很多。数据量建议至少2000张原始图每张再做平移、旋转、亮度扰动增强。2.2 损失函数与训练参数Dice Loss和BCE结合学习率用余弦退火训练分割模型时正负样本极度不平衡——车道线像素占整张图比例不到5%。只用BCEBinary Cross Entropy会让模型倾向把所有像素预测成背景因为这样loss已经很低了。解决方案是把Dice Loss和BCE按比例叠加。Dice Loss直接优化分割结果的区域重叠度对小目标非常友好但单独用容易震荡和BCE混着用刚好互补。我自己实验下来权重配比Dice:BCE 1:1效果最稳类别极不平衡时可以调到2:1。训练脚本核心参数如下# train_seg.py import torch from torch.utils.data import DataLoader from torch.optim.lr_scheduler import CosineAnnealingLR model LaneSegModel(backboneresnet34) # 编码器用ResNet34解码器自建 dataset LaneDataset(img_dirtu_simple/train, mask_dirtu_simple/annotations, img_size(512, 288)) # 宽512高288保留车道线的横向长条特征 dataloader DataLoader(dataset, batch_size8, # batch size按显存8GB量级开的 shuffleTrue, num_workers4) optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max60) # 60个epoch内余弦退火到最低点 def total_loss(pred, target): bce torch.nn.functional.binary_cross_entropy_with_logits(pred, target) dice dice_loss(torch.sigmoid(pred), target) # dice_loss需要自己实现或引用现成库 return bce dice # 默认1:1不平衡可调成2:1 for epoch in range(60): model.train() for imgs, masks in dataloader: pred model(imgs) loss total_loss(pred, masks) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step()这段代码里有几个关键设计。第一输入尺寸设成512x288而不是正方形是因为车道图片都是横向宽幅的强行resize成方形会让车道线被压扁细节丢失。第二优化器选了AdamW而不是SGDAdamW学习率不用精调收敛快适合快速验证模型有效性的阶段如果追求极致精度再换SGD加动量。第三余弦退火调度器把学习率从1e-3平滑降到接近0比固定学习率在分割任务上能多提升2到3个点mIoU。训练结束后只保存state_dict不要整个模型对象一起存这样模型文件小加载也快后面接Django时也方便。torch.save(model.state_dict(), lane_seg_resnet34.pth)推理时加载也要保持同样的键名结构。2.3 置信度阈值预测结果里最容易乱调的那个参数分割模型的输出会经过sigmoid函数压缩到0到1之间每个像素的值代表该像素属于车道线的概率。要把它转成二值掩膜需要设一个阈值。这个阈值非常敏感设高了线会断成虚线设低了会把路面裂缝、轮胎印也误判成车道线。我一般设在0.4到0.5之间具体要看模型训练的收敛程度。如果训练充分、验证集mIoU在85以上阈值0.4就够了。如果模型欠拟合把阈值提到0.6反而能过滤掉很多低置信度的误检。3. Django后端集成把PyTorch模型接到Web端的完整流程3.1 Django项目初始化创建app和设计的目录结构Django后端接模型不是直接把推理代码扔进views.py就完事了。模型加载一次要几百毫秒到几秒每次请求都重新加载模型Web服务直接卡死。正确做法是在Django启动时加载模型到内存请求来了只做forward传播整个过程要处理好模型常驻、图片上传、结果返回三条链路。先按标准流程创建项目和应用django-admin startproject lane_detection_project cd lane_detection_project python manage.py startapp detector目录规划上有两个点要注意。第一模型权重文件放到detector/weights/目录下用settings配置路径不要硬编码在代码里。第二前端模板里用的静态资源走Django的static机制上传的图片放到media/目录。这样的结构在本地开发和部署到服务器时都不会乱。3.2 推理模块封装模型加载、预处理、后处理三段式推理逻辑要独立成模块不要直接写在视图函数里方便单元测试也方便以后换ONNX或其他推理引擎时只改一个文件。# detector/inference.py import torch import numpy as np import cv2 class LaneDetector: def __init__(self, weights_path, devicecuda if torch.cuda.is_available() else cpu): self.device device self.model LaneSegModel(backboneresnet34) self.model.load_state_dict(torch.load(weights_path, map_locationdevice)) self.model.to(device) self.model.eval() def preprocess(self, image): # opencv读进来是BGR模型训练用的是RGB这一步漏了颜色全乱 rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) resized cv2.resize(rgb, (512, 288), interpolationcv2.INTER_LINEAR) # 归一化到0-1区间和训练时的数据预处理保持一致 normalized resized.astype(np.float32) / 255.0 # HWC转CHW加batch维度 tensor torch.from_numpy(normalized).permute(2, 0, 1).unsqueeze(0) return tensor.to(self.device) def postprocess(self, output_tensor, original_shape, threshold0.4): # 把模型输出经过sigmoid和二值化 prob torch.sigmoid(output_tensor).squeeze().cpu().numpy() mask (prob threshold).astype(np.uint8) * 255 # 把mask从288x512还原回原图尺寸 mask_resized cv2.resize(mask, (original_shape[1], original_shape[0]), interpolationcv2.INTER_NEAREST) # 膨胀操作把断裂的车道线接起来kernel大小根据分辨率调整 kernel np.ones((5, 5), np.uint8) mask_dilated cv2.dilate(mask_resized, kernel, iterations1) return mask_dilated def detect(self, image): original_shape image.shape tensor self.preprocess(image) with torch.no_grad(): output self.model(tensor) mask self.postprocess(output, original_shape) return mask这个封装有三个关键细节。第一图像颜色空间转换必须写OpenCV默认读成BGR直接丢给PyTorch模型模型输入和训练分布不一致推理效果会断崖式下降这是新手最容易踩的坑。第二后处理里mask还原尺寸用INTER_NEAREST不要用双线性插值。二值图用线性插值会产生灰色过渡值后续处理都要再设一遍阈值纯属自找麻烦。第三膨胀操作只在推理端做训练数据不要膨胀。训练时膨胀会让车道线变粗模型学到的就是粗线推理时再膨胀一次会过拟合到错误形态。3.3 视图函数与路由同步请求和异步任务的选择视图层的设计要区分单张图片和批量图片两种场景。单张图片是同步请求上传后直接返回结果批量场景应该用Celery异步任务把请求先扔进队列前端轮询任务状态。标题里的系统一般指的是单张图同步处理的轻量方案代码也按这个写。# detector/views.py import cv2 from django.shortcuts import render from django.http import JsonResponse from django.conf import settings from .inference import LaneDetector # 模块加载时实例化一次整个进程复用同一个对象 detector LaneDetector(weights_pathsettings.LANE_WEIGHTS_PATH) def index(request): return render(request, detector/index.html) def detect_lane(request): if request.method ! POST: return JsonResponse({code: 400, msg: only POST allowed}) image_file request.FILES.get(image) if not image_file: return JsonResponse({code: 400, msg: image field required}) # Django的File对象转成opencv能读的numpy数组 file_bytes np.frombuffer(image_file.read(), np.uint8) image cv2.imdecode(file_bytes, cv2.IMREAD_COLOR) if image is None: return JsonResponse({code: 400, msg: invalid image}) mask detector.detect(image) # 把mask叠加到原图上生成可视化结果 overlay image.copy() overlay[mask 0] (0, 0, 255) # BGR顺序纯红色车道线 result_path save_overlay_result(overlay) # 保存到media目录返回相对路径 return JsonResponse({code: 200, data: {result_url: result_path}})对应的路由# detector/urls.py from django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), path(detect/, views.detect_lane, namedetect_lane), ]这其中的关键点是Django接收到的图片文件是一个Django的UploadedFile对象不能直接喂给OpenCV。要先读成bytes再用cv2.imdecode解码成numpy数组。反过来OpenCV处理完的结果也不能直接写回数据库要保存成文件后用URL暴露给前端。日常做法是保存在media/detected/下文件名加个时间戳避免缓存冲突。input标签里写img标签加载这个URL时Django的static配置要确保media路由正确否则img标签显示不了——这个问题在网上一搜一大片。4. 关键参数调优车道线检测系统的阈值、膨胀核与输入尺寸4.1 置信度阈值的选择二分搜索每张图的最优值再取均值置信度阈值是整个系统里最受玄学影响的一个参数。有人直接定0.5有人试几个数看效果好就放着不管了这样线上跑起来遇到阴影、雨雪天就翻车。靠谱做法是拿一张验证集图片做实验在一个范围内遍历阈值每跑一次在后处理后的mask上计算一次车道线像素的准确率和召回率找到F1分数最高的值。一次遍历70张左右的图片阈值从0.1到0.8步长0.05取每张最优阈值的均值作为系统的默认值。这样定出来的阈值比拍脑袋定的稳定得多。缺点是耗时但这是离线操作不影响线上推理速度。4.2 膨胀核大小影响通车线连续性的细节参数膨胀操作是把车道线断裂处填补的关键手段核大小直接决定填补力度。核太小时断裂处补不上线看起来像虚线核太大时会把相邻的并行车道线融合成一块后续做车道线方程拟合时数据就废了。按512x288的输入分辨率5x5的核迭代一次是稳妥起点。如果输入分辨率提高到640x360核跟着放大到7x7。判断标准是看输出结果中车道线上下连通的情况统计每条车道线在垂直方向的最大断裂间隙间隙小于2个像素说明膨胀量够了大于5个像素就需要加大核或增加迭代次数。注意这个判断要在二值化之后做。4.3 输入尺寸与推理速度的权衡模型的输入尺寸直接把推理速度和精度绑在一起。512x288是平衡点分辨率降到320x180推理速度提升约60%但车道线消耗的像素太少断裂严重。分辨率提到640x360mIoU能涨2个点但Django后端并发处理能力直线下降。如果Web应用要支撑几十个人同时测试512x288是唯一能扛住的选择。推理瓶颈不在Django而在GPU显存和带宽一张图推理30ms但所有用户共享同一个GPU并发上来之后排队时间会明显变长。限流或队列是这类系统的刚需。5. 部署与运行避坑Django静态文件、模型路径、CPU与GPU环境不一致5.1 踩坑记录static目录下img标签显示不了现象Django模板里写了img src{% static images/result.jpg %}本地能显示部署到服务器上就裂开了。原因Django默认的static文件处理在开发模式是自动找的生产模式需要collectstatic把所有app的静态文件收集到一个目录再由Nginx托管。解决项目settings.py里加一行STATIC_ROOT os.path.join(BASE_DIR, staticfiles)部署前手动执行python manage.py collectstatic --noinputNginx端的配置把/static/路径指向这个目录保证静态文件由Nginx直接返回不经过Django。5.2 踩坑记录模型权重路径写死在views.py里换环境就跑不了现象代码在Windows上跑通了提交到Linux服务器后报FileNotFoundError。原因有人把权重路径写死成D:/project/lane_seg.pth换到服务器上路径不存在。解决权重路径配置进settings.py用相对BASE_DIR的方式拼接并且服务器和开发机的目录结构保持一致。# settings.py LANE_WEIGHTS_PATH os.path.join(BASE_DIR, detector, weights, lane_seg_resnet34.pth)5.3 踩坑记录训练用GPU部署用CPU推理结果对不上现象GPU上验证效果不错部署到只有CPU的服务器上同样的图片结果变差很多车道线断断续续。原因两个因素叠加。一是CPU推理时数据精度和GPU有差异PyTorch在CPU上某些算子有精度损失。二是训练和推理的数据预处理不一致比如训练时归一化到0到1推理时忘了归一化。解决先检查预处理代码是否完全对齐。对齐后再看算子精度问题尝试把模型转成ONNX格式用ONNX Runtime跑CPU推理它做了大量算子融合优化CPU上精度和速度通常比PyTorch原生CPU模式更好。5.4 踩坑记录Django开发服务器跑推理请求超时现象开发模式下runserver跑推理接口大图片处理时间超过30秒前端直接报超时。原因runserver是单进程串行模型推理阻塞期间其他请求全部排队图片越大越明显。解决生产环境换Gunicorn加多worker部署每个worker独立加载一份模型并发能力线性提升。显存不够就限制worker数量或者把模型放进独立推理服务Django通过HTTP或gRPC调用。标题项目的体量放Gunicorn2个worker足够显存占用约6GB。6. 进阶技巧把推理结果转成车道线方程并接入实时视频流前面所有步骤讲的是单张图的完整链路。落地到实际场景时还需要把分割mask进一步拟合回车道线方程以及处理连续帧视频。这个阶段我一般会把mask按列扫描找每行像素的横向峰值位置把这些峰值点聚合成若干条线再用RANSAC拟合二次多项式曲线。每帧的拟合参数不直接用而是平滑后再输出。视频流处理和单张图处理最大的差别在状态管理。Django的请求是无状态的但视频流检测天然需要跨帧信息。每次只处理当前帧、不做跨帧关联车道线位置会抖动拟合方程在直道转弯道时有一帧明显的滞后。常见的做法是用一个全局字典保存最近几帧的拟合曲线参数下一帧到来时先按参数预测位置再和当前帧检测结果加权融合。插值权重一般0.6对0.4历史帧权重太大转弯时反应迟钝太小滤波效果等于零。更进一步的工程优化是把检测模型导出成ONNXimport torch # 导出ONNX格式CPU和GPU都能跑部署不强制装PyTorch torch.onnx.export(model, dummy_input, lane_seg.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}})导出时dynamic_axes保证batch维度可变部署端可以用onnxruntime-gpu跑推理速度比PyTorch原版快20%左右。这个过程要注意的是导出前模型一定要切到eval模式否则batch norm的统计量不对导出的ONNX在部署端效果和训练时差一截这个坑我吃过亏后来每次导出都先检查model.eval()。希望帮到你。本文还有配套的精品资源点击获取