【快车道线检测项目】项目学习总结

一、项目需求与背景

1. 背景

车道线检测是自动驾驶和高级驾驶辅助系统(ADAS)的核心任务之一,旨在通过摄像头或其他传感器实时识别道路上的车道线标记,帮助车辆保持车道、避免偏离,并为路径规划提供关键信息。传统方法(如边缘检测、霍夫变换或滑动窗口)依赖手工设计特征,在光照变化、遮挡、复杂道路(如弯曲车道或模糊标线)等场景下表现受限。而基于机器学习(尤其是深度学习)的方法通过自动学习鲁棒特征,显著提升了检测精度和泛化能力,成为当前主流解决方案。

技术挑战包括:

  • 环境多样性:不同天气(雨雪、雾霾)、光照(逆光、夜间)和道路条件(磨损、阴影)。

  • 实时性要求:需在嵌入式设备(如车载芯片)上低延迟处理(通常需≥30 FPS)。

  • 复杂场景:车道线弯曲、车辆遮挡、临时施工标线等。


2. 需求

功能性需求

  • 多场景适应:在不同天气、光照及道路类型(高速、城市道路)下稳定检测。

  • 多车道识别:同时检测当前车道及相邻车道线,支持车道保持和变道辅助。

  • 弯曲车道处理:准确拟合抛物线或曲线车道(如环岛、弯道)。

  • 遮挡鲁棒性:在部分遮挡(前车、污渍)时仍能推断车道走向。

非功能性需求

  • 实时性:处理单帧图像时间≤30ms(满足实时视频流需求)。

  • 准确性:在主流数据集(如TuSimple、CULane)上达到≥95%的检测精度。

  • 轻量化:模型需适配边缘计算设备(如Jetson TX2),参数量≤5M。

  • 可扩展性:支持多传感器融合(如激光雷达、雷达)以提升冗余性。


3. 概述

方法框架

  • 数据准备:使用公开数据集(如TuSimple、CULane)或自建数据集,包含多样场景标注(点集或掩模)。

  • 模型选择:采用深度学习模型(如LaneNet、U-Net或SCNN),结合实例分割与曲线拟合。

  • 关键技术

    • 特征提取:通过CNN主干网络(如ResNet、EfficientNet)提取多尺度特征。

    • 后处理:通过多项式拟合或透视变换生成连续车道线,或采用注意力机制增强关键区域。

    • 轻量化优化:使用模型压缩技术(如知识蒸馏、量化)提升推理速度。

流程示例

  1. 输入:车载摄像头捕获RGB图像。

  2. 预处理:图像归一化、ROI裁剪、透视变换(逆透视映射,IPM)。

  3. 推理:模型输出车道线位置的概率图或关键点。

  4. 后处理:聚类、曲线拟合,生成车道线方程或可视化结果。

  5. 输出:车道线坐标及曲率信息,传递给车辆控制系统。

评估指标

  • 准确率:基于交并比(IoU)或点匹配精度。

  • F1分数:平衡误检(FP)与漏检(FN)。

  • 实时性:帧率(FPS)与端到端延迟。


4. 应用与展望

车道线检测不仅是L2级自动驾驶(如特斯拉Autopilot)的基础功能,还可扩展至高精地图构建、车道偏离预警(LDW)等场景。未来趋势包括:

  • 多模态融合:结合雷达、LiDAR提升复杂环境下的鲁棒性。

  • 端到端架构:直接输出控制指令(如方向盘转角),减少模块化冗余。

  • 自监督学习:利用未标注数据降低模型训练成本。

通过结合深度学习与嵌入式优化,车道线检测技术正推动自动驾驶向更安全、更可靠的方向发展。

  


 

5. 快车道线检测算法与传统车道线检测算法的对比

  传统车道线检测基于边缘特征检测车道线,但是只能检测样本原有标注好的车道,且无法识别车道被阻挡或磨损等复杂场景的情况;例如语义分割对图像的每一个像素进行分类,即判断每个像素点是车道还是背景,但可能存在不是车道的地方被识别成车道的错误;并且缺点有计算量大,训练速度慢,且判断的是车道而非车道线;

  而快车道线检测算法不用判断每一个像素,而是判断一个个点,然后把点连起来作为车道线进行检测;

  快车道线模型可以解决之前语义分割+实例分割的两个问题

  • 车道线完全被遮挡下,像素中感受野中上下文都i没有车道线学习,导致无法识别车道线
  • 在端上速度慢,如何实时进行检测

  训练后,网络可以根据是否存在车辆来判断被遮挡住的车道线;

 

二、论文算法

论文总结:《Ultra Fast Structure-aware Deep Lane Detection》


1. 背景与问题

  作者提出了一种新颖的车道线检测方法,旨在解决现有方法在速度和复杂场景下的不足。传统方法依赖于像素级分割,计算量大且难以处理遮挡和极端光照条件。本文提出的方法将车道检测视为基于行的选择问题,利用全局特征和结构损失函数,显著提高了速度和准确性。

  作者将车道检测过程视为使用全局特征的基于行锚点的选择问题。在基于行的选择的帮助下,该公式可以显著降低计算成本。在全局特征上使用大的感受野,也可以处理具有挑战性的场景。此外,基于该公式,作者还提出了一个结构损失来显式模拟车道的结构。

车道线检测是自动驾驶系统的核心任务,但传统像素级分割方法(如SCNN、SAD)存在两大问题:

  • 计算量大:逐像素分类导致高延迟,难以满足实时性需求(如多摄像头输入场景)。

  • 复杂场景鲁棒性差:在遮挡、极端光照或车道线模糊(无视觉线索,no-visual-clue)时表现受限。


2. 算法核心

2.1 图像分割

作者通过预定义的行锚点(row anchors)进行水平位置选择,减少了计算量。此外,结构损失函数考虑了车道的连续性和形状,进一步优化了检测结果。

具体来说,作者提出使用全局特征在图像的预定义行中选择车道的位置,而不是根据局部感受野分割车道的每个像素,这大大降低了计算成本。

 如上图,在左侧和右侧车道上选择的图示。在右侧,详细显示了一行的选择。行锚点是预定义的行位置,我们的公式定义为在每个行锚点上水平选择。在图像的右侧,引入了一个背景网格单元格,以指示此行中没有车道。

 


 

2.2 分类损失函数

车道表示为预定义行(即行锚点)处的一系列水平位置。为了表示位置,第一步是网格化。在每个行锚点上,位置被划分为许多单元格。通过这种方式,泳道的检测可以描述为在预定义的行锚点上选择某些单元格;

 

假设最大车道数为 C,行锚点数为 h,网格化单元数为 w。假设 X 是全局图像特征,f ij 是用于在第 i 个泳道、第 j 行锚点上选择车道位置的分类器。那么 lanes 的预测可以写成:

其中 Pi,j是 (w + 1) 维向量,表示为第 i 条车道、第 j 行锚点选择 (w + 1) 个网格化单元格的概率。

可以看到,作者的方法根据全局特征预测每个行锚点上所有位置的概率分布。因此,可以根据概率分布选择正确的位置。

进一步优化公式:

 其中 LCE 是交叉熵损失。作者使用额外的维度来表示没有车道,因此我们的公式由 (w + 1) 维而不是 w 维分类组成。

 


 

2.3 优势与改进

  如下图所示。可以看出,作者的公式比常用的分割要简单得多。假设图像大小为 H × W 。通常,预定义行锚点的数量和网格大小远小于图像的大小,即 h H 和 w W 。这样,原来的分割公式需要进行 (C + 1) 维的 H × W 分类,而我们的公式只需要解决 (w + 1) 维的 C × h 分类问题。通过这种方式,计算规模可以大大减少,因为我们公式的计算成本是 C ×h×(w+1),而分割的计算成本是 H × W × (C + 1)。例如,使用 CULane 数据集 [22] 的常用设置,我们方法的理想计算成本是 1.7 × 104 次计算,而分割成本为 1.15×106 次计算。计算成本显著降低,我们的公式可以达到极快的速度。

  作者的模型和常规分割如上图示。作者的公式是在行上选择位置(网格),而 segmentation 是对每个像素进行分类。用于分类的维度也不同,标记为红色。所提出的公式显著降低了计算成本。此外,所提出的公式使用全局特征作为输入,其感受野比分割具有更大的感受野,从而解决了无视觉线索问题 


 

  并且为了处理无视觉线索问题,利用来自其他位置的信息很重要,因为无视觉线索意味着目标位置没有信息。例如,一条车道被一辆汽车遮挡,但我们仍然可以根据来自其他车道、道路形状甚至汽车方向的信息来定位该车道。通过这种方式,利用来自其他位置的信息是解决无视觉线索问题的关键。

  从感受野的角度来看,我们的公式有一个整个图像的感受野,这比分割方法大得多。来自图像其他位置的上下文信息和消息可用于解决无视觉线索问题。从学习的角度来看,车道的形状和方向等先验信息也可以使用基于我们公式的结构损失来学习。

  另一个显著的好处是,这种公式以基于行的方式对车道位置进行建模,这使我们有机会显式地建立不同行之间的关系。可以弥合由低级像素建模和高级 lane 长线结构引起的原始语义差距。

 


 

2.4 损失函数

  除了上面的分类损失函数之外,作者还提出了两个损失函数,旨在对车道点的位置关系进行建模。通过这种方式,可以鼓励对结构信息的学习。

  (1)数据源源于车道是连续的,也就是说,相邻行锚点中的车道点应该彼此靠近。在我们的公式中,车道的位置由分类向量表示。因此,连续属性是通过约束分类向量在相邻行锚点上的分布来实现的。这样,相似度损失函数可以是:

 

  其中 Pi,j是对第 j 行锚点的预测,‖·‖1 表示 L1 范数。

  (2)另一个结构损失函数侧重于车道的形状。一般来说,大多数车道都是直的。即使对于曲线车道,由于透视效果,它的大部分仍然是直线的。在这项工作中,我们使用二阶差分方程来约束车道的形状,对于直线情况,该形状为零。

  要考虑形状,需要计算车道在每个行锚点上的位置。直观的思路是通过查找最大响应峰来从分类预测中获取位置。对于任何车道索引 i 和行锚点索引 j,位置 Loci,j 可以表示为:

   其中 k 是表示位置索引的整数。需要注意的是,我们不在后台网格单元格中计数,位置索引 k 的范围只有 1 到 w,而不是 w + 1。

  


 

  但是,argmax 函数是不可微分的,不能与进一步的约束一起使用。此外,在分类公式中,类没有明显的顺序,并且很难在不同行锚点之间建立关系。为了解决这个问题,我们建议使用预测的期望作为位置的近似值。我们使用 softmax 函数来获取不同位置的概率:

   其中 Pi,j,1:w 是 w 维向量,Probi,j,: 表示每个位置的概率。

  出于与方程 4 相同的原因(我们不在后台网格单元格中计数,位置索引 k 的范围只有 1 到 w,而不是 w + 1),不包括背景网格单元,计算范围仅为 1 到 w。

  然后,locations 的期望可以写成:

   其中 Probi,j,k 是第 i 条车道、第 j 行锚点和第 k 个位置的概率。这种定位方法的好处是双重的。第一个是期望函数是可微的。另一个是此作恢复了具有离散随机变量的连续位置。

  又根据上一个方程,二阶差分约束可以写成:

  其中 Loci,j 是第 i 条车道上的位置,即第 j 行锚点。我们使用二阶差分而不是一阶差分的原因是,在大多数情况下,一阶差分不为零。所以网络需要额外的参数来学习车道位置的一阶差值的分布。此外,二阶差值的约束相对较弱于一阶差值的约束,因此当车道不直时,影响较小。

  最后,整体结构损失函数是: 

   其中 λ 是损耗系数。


  上面的损失设计主要关注车道的内部关系。在本节中,我们提出了一种对全局上下文和局部特征执行的辅助特征聚合方法。提出了一种利用多尺度特征的辅助分割任务来模拟局部特征。我们使用交叉熵作为辅助分割损失。这样,我们的方法的整体损失可以写成:

   其中 Lseg 是分割损失,α 和 β 是损失系数。整体架构如图 4 所示。

   总结:

    总的损失函数 = 存在的损失函数+权重*空间的损失函数+权重*辅助分割的损失函数

 


 

2.5 整体架构

  图 4.整体架构。辅助分支显示在上半部分,仅在训练时有效。特征提取器显示在蓝色框中。基于分类的预测和辅助分割任务分别在绿色和橙色框中表示。对每个行锚点进行组分类。

  分为主干和辅助两个分支

  • 辅助分支只在训练时使用
  • 测试时主要走主干分支

  主干分支:使用ResNet网络得到训练结果(卷积、降采样、全连接),映射到原始图像上(哪根线对应哪个格),得到训练后的离散的栅格点,通过聚类或车道线拟合的方式把一个个格拟合成车道线;

  辅助分支:把车道线离散出来,识别出是第几个车道,目的是为了使训练效果更好;

  Resblocks可以选择任意网络,例如ResNet,FCN的计算量有点大;

PS:

  (1)需要注意的是,我们的方法在训练阶段只使用辅助分割任务,在测试阶段会去掉。这样,即使我们添加了额外的切分任务,我们方法的运行速度也不会受到影响。它与没有辅助分割任务的网络相同。

   (2)同时注意最后完成的是分类任务,最后全连接层输出14472个特征,即14472=【201,18,4】;

    •   其中【18,4】与标签对应;
    •   则是分类概率(其中0-199表示位置类别,200表示不存在车道)(即200+1=201);

 


 

2.6 评估指标

  对于 TuSimple 数据集,主要评估指标是准确性。准确性的计算公式为:

   其中 Cclip 是正确预测的车道点数,Sclip 是每个剪辑中真实值总数。


  关于 CULane 的评估指标,每个通道都被视为一条 30 像素宽的线。然后计算 ground truth 和 predictions 之间的交集与并集 (IoU)。IoUs 大于 0.5 的预测被视为真阳性。F1-measure 作为评估指标,其公式如下:

   其中 Precision是准确度,Recall是召回率 ,T P 是真阳性,F P 是假阳性,F N 是假阴性。


 

2.7 图像数据处理

  实现细节:对于TuSimple 和 CULane 两个数据集,我们使用数据集定义的行锚点。具体来说,Tusimple 数据集的行锚点(图像高度为 720)的范围为 160 到 710,步长为 10。CULane 数据集的对应范围从 260 到 530,与 Tusimple 的步骤相同。CULane 数据集的图像高度为 540。网格化单元的数量在 Tusimple 数据集上设置为 100,在 CULane 数据集上设置为 150。

  并且在优化过程中,图像大小调整为 288×800。我们使用 Adam来训练我们的模型,使用用 4e-4 初始化的余弦衰减学习率策略。方程 8 和 9 中的损耗系数 λ、α 和 β 都设置为 1。批量大小设置为 32,TuSimple 数据集的训练时期总数设置为 100,CULane 数据集的训练纪元总数设置为 50。我们之所以选择如此大量的 epoch,是因为我们的结构保持数据增强需要长时间的学习。下面将讨论我们的数据增强方法的详细信息。所有模型都使用 pytorch和 nvidia GTX 1080Ti GPU 进行训练和测试。

  根据模型需要,输出标签为【4,18】矩阵,表示4条车道线在18个位置上的具体点,其中【4,18】可以作为超参数进行调整;

 

  数据增强:由于车道的固有结构,基于分类的网络很容易过度拟合训练集,并在验证集上表现出较差的性能。为了防止这种现象并获得泛化能力,采用了一种由旋转、垂直和水平偏移组成的增强方法。此外,为了保持车道结构,车道延伸到图像的边界。增强的结果图5所示。

  图 5.增强的演示。右侧图像上的车道被延长以保持车道结构,该结构用红色椭圆标记。


 

2.8 结论与总结

 训练结果展示

图 8.Tusimple 和 CULane 数据集上的可视化。前两行是 Tusimple 数据集上的结果,其余行是 CULane 数据集上的结果。从左到右,结果是 image、prediction 和 label。在图像中,预测标记为蓝色,地面实况标记为红色。由于我们的方法仅预测预定义的行锚点,因此图像和标签在垂直方向上的比例并不相同。

 


优势:

  • 提出了一种新颖、简单但有效的车道检测公式,旨在实现极快的速度并解决无视觉线索问题。与深度分割方法相比,我们的方法是选择车道的位置,而不是分割每个像素,并在不同的维度上工作,这是超快的。此外,我们的方法使用全局特征进行预测,其感受野比分割公式大。通过这种方式,也可以解决无视觉线索问题。
  • 基于所提出的公式,我们提出了一个结构性损失,它显式地利用了车道的先验信息
  • 所提出的方法在具有挑战性的 CULane 数据集上实现了最先进的准确性和速度性能。我们方法的轻量级版本甚至可以在相同的分辨率下以相当的性能实现 300+ FPS,这比以前最先进的方法至少快 4 倍

 

作者总结:

  在本文中,作者提出了一种具有结构损失的新型公式,并取得了显着的速度和准确性。所提出的公式将车道检测视为使用全局特征的基于行的选择问题。通过这种方式,可以解决速度和无视觉线索的问题。此外,还提出了用于车道先验信息显式建模的结构损失。我们的配方的有效性和结构损失通过定性和定量实验都得到了充分的证明。特别是,我们使用 Resnet-34 主干的模型可以达到最先进的精度和速度。我们方法的轻量级 Resnet-18 版本甚至可以达到 322.5 FPS,在相同分辨率下具有相当的性能。


 

个人总结:

作者提出一种基于行锚点的全局选择框架,结合结构化损失函数,实现高速与高精度车道检测:

  • 创新框架:将车道检测建模为行锚点选择问题

    • 预定义行锚点(Row Anchors),在每行水平划分网格(Gridding Cells),仅需在行锚点上选择车道位置,而非全图逐像素分类。

    • 计算量显著降低:例如,CULane数据集上,计算量从分割方法的1.15×10⁶降至1.7×10⁴。

    • 全局特征感知:利用整图感受野,结合上下文信息解决遮挡和光照问题。

  • 结构化损失函数

    • 连续性损失(L_sim):约束相邻行锚点的预测分布相似性。

    • 形状损失(L_shp):通过二阶差分约束车道线平滑性。

  • 辅助分割任务:训练时引入辅助分割分支增强局部特征,推理时移除以保持速度。


3. 实验结果

实验部分显示,该方法在两个主流数据集(TuSimple和CULane)上达到了最先进的性能,尤其是轻量级版本实现了超过300 FPS的速度,显著快于之前的方法。

在两个主流数据集上验证方法性能:

  • 速度

    • ResNet-18轻量版:在CULane数据集上达到322.5 FPS(输入分辨率288×800),比SCNN快43倍。

    • ResNet-34版:速度175.4 FPS,精度优于SAD等现有方法。

  • 精度

    • CULane数据集:综合F1分数72.3%,在夜间、弯道等复杂场景表现突出。

    • TuSimple数据集:准确率96.06%,接近最优分割方法(SCNN 96.53%),但速度快22.6倍。


4. 方法优势

  • 高效性:行锚选择框架减少90%以上计算量,适合边缘设备部署。

  • 鲁棒性:全局特征与结构化损失提升遮挡、极端光照下的检测能力。

  • 灵活性:支持多车道检测,可扩展至其他结构化任务(如路标识别)。


5. 未来方向

  • 多模态融合:结合LiDAR或雷达增强三维感知。

  • 端到端优化:直接输出车辆控制指令,减少模块冗余。

  • 自监督学习:利用未标注数据降低标注成本。


5. 总结

  本文提出了一种基于行锚点选择的车道检测框架,通过全局特征与结构化损失平衡速度与精度,在复杂场景下显著优于传统分割方法。其轻量版300+ FPS的性能为实时自动驾驶系统提供了高效解决方案,是车道检测领域的重要突破。

  常用的语义分割通过对每个像素进行判断,而快车道线检测是把图像分为一个个栅格,对每一个纵格选择一个行格,对栅格进行聚类分类,判断栅格里是否有车道线;即是检测的模型,而非语义分割了;

  例如把1280*256的图像横向分为100个格,纵向分为64个格;栅格是紧挨着的;处理图像缩小,感受野变大,训练时间也变短;

 

 

三、项目框架

1. 项目架构

 

 


 

2. 项目流程

  1. 准备数据集(CULane
  2. 图像预处理(归一化)(栅格化)
  3. 检测处理后的数据
  4. 生成输入的标签数据
  5. 配置文件
  6. 设置超参数
  7. lr选择
  8. DataLoader加载数据
  9. 确定网络结构(加载预训练模型)
  10. 选择优化函数(SGD、Adam)
  11. 分为两个分支(主干和辅助),得到训练后的结果
  12. 计算损失函数(给车道线像素加一个权重,避免背景和车道线的差异)
  13. 整合两个分支损失函数的结果
  14. 进行反向传播和优化
  15. 训练epoch时会保存模型,得到模型的测试指标

四、数据集准备

1. 数据集下载

  本次项目出于学习便捷考虑,只使用CULane的部分数据集进行训练

  CULane地址:https://xingangpan.github.io/projects/CULane.html

  数据集目录如下:


 

2. label数据处理

  label目录下数据标签图是黑色的,但是如果放大看,里面其实不明显的像素值,因此我们只需使用opencv的阈值过滤函数将大于0部分的像素值设置成255(白色)就可以了。  

  修改后如下:

  单张图片修改:

import cv2 as cv 
import numpy as np
import matplotlib.pyplot as plt
import os 

img = cv.imread("F://study//CULane//laneseg_label_w16//laneseg_label_w16//driver_23_30frame//05151646_0421.MP4//01200.png",0)
ret1, thresh1 = cv.threshold(img,0,255,cv.THRESH_BINARY)
cv.imwrite("F://study//CULane//laneseg_label_w16//laneseg_label_w16//driver_23_30frame//05151646_0421.MP4//01200.png",thresh1)

plt.imshow(thresh1)

  批量修改:

# -*- coding:utf8 -*-

import os
import random
import cv2 as cv
import matplotlib.pyplot as plt

path = 'E:/CULane/161/label'
filelist = []
for file in os.listdir(path):
    filelist.append(file)
for i in range(len(filelist)):
    p = os.path.join(path, filelist[i])

    img = cv.imread(p)
    ret1, thresh1 = cv.threshold(img, 0, 255, cv.THRESH_BINARY)
    cv.imwrite(p, thresh1)
    print('完成')

3. frame目录

  frame目录下都是视频的节帧

  显示图片:

 


 

4. list数据标注

   对应标注为:

     图像对应.txt注释文件。

  每两个数为一个像素坐标(x,y),纵向标注,每隔十个像素标注,标注图像的下半部分。

  可视化结果:
  图像对应.txt注释文件。每两个数为一个像素坐标(x,y),纵向标注。

 


 

五、项目配置

1. 数据配置

由于本项目只使用CULAne数据集,因此只需配置culane.py的内容即可

 数据集目录使用相对路径

 


2. 超参数配置

  设置epoch=50,batch_size=32,其它参数保持默认

  • epoch是指模型在训练过程中遍历整个训练数据集一次的过程。(如果训练数据集包含 10,000 个样本,那么一个 Epoch 就是模型在这 10,000 个样本上完成一次 前向传播(forward pass) 和 反向传播(backward pass) 的过程
  • batch_size表示单次传递给程序用以训练的参数个数或数据样本个数;
  • 优化器(optimizer)通过调整模型参数以最小化损失函数,决定了神经网络在给定数据上的学习效率和效果;
  • 权重衰减(weight decay)是最广泛使用的正则化的技术之一, 它通常也被称为L 2正则化。它通过函数与零的距离来衡量函数的复杂度;
  • Momentum(动量)是机器学习中一种常用的优化算法,用于加快模型收敛速度并减少震荡。

  


 

  设置神经网络网格数量=200;

·  backbone:深度神经网络的主要部分,通常用于特征提取。骨干网络通过卷积操作提取图像中的低级和高级特征,为后续的特定任务(如分类、检测、分割等)提供丰富的信息。这些骨干网络一般是预训练的,即在大型数据集(如ImageNet)上进行训练,以便在各种下游任务中实现良好的性能。

 


  设置输出日志的目录 ‘./log’ 

 


 

3. 指定命令行参数

  把配置文件设置到运行命令中,在Pycharm的运行参数中进行设置

--config ./configs/culane.py

 


六、数据处理模块

1. 数据标签

根据所用数据集特点设置行标签

即数据图像中车道线所在位置

 


 

2. 数据转换

本模块自定义函数对样本进行图像设置(缩放、旋转等操作);

 

共分为以下几个类或函数:

  • class Compose2:图像组合类,把transforms中的信息赋值给img,mask,bbx,然后直接返回;

  • class FreeScale:自由缩放类,对img和mask进行缩放;

  • class FreeScaleMask:自由缩放mask类,单独对mask进行缩放;

  • class Scale:尺寸类,读取并计算图像的h、w并返回;

  • class RandomRotate:随机旋转类,生成一个随机角度并旋转;

  • class DeNormalize:非规范化类,链式操作等价于 t = t * s + m;

  • class MaskToTensor:Mask转换为Tensor;

  • def find_start_pos:找到车道线起始位置,使用二分查找法;

  • class RandomLROffsetLABEL:随机LR偏移设置标签(左右);

  • class RandomUDoffsetLABEL:随机UD偏移设置标签(上下);

示例代码:

#尺寸类
class Scale(object):
    # size包括长宽(h, w)
    def __init__(self, size):
        self.size = size

    def __call__(self, img, mask):
        #img和mask不相等则输出各自尺寸
        if img.size != mask.size:
            print(img.size)
            print(mask.size)
        #断言img和mask相等
        assert img.size == mask.size
        #获取w,h
        w, h = img.size
        #若属于正常情况返回img和mask
        if (w <= h and w == self.size) or (h <= w and h == self.size):
            return img, mask
        #当w<h,但w!=self.size
        if w < h:
            #取self.size计算新的w和h
            ow = self.size
            oh = int(self.size * h / w)
            # (w,h),双线性插值算法进行图像缩放
            # (w,h),最近邻插值算法进行图像缩放
            return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST)
        # 当h<w,但h!=self.size
        else:
            # 取self.size计算新的w和h
            oh = self.size
            ow = int(self.size * w / h)
            # (w,h),双线性插值算法进行图像缩放
            # (w,h),最近邻插值算法进行图像缩放
            return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST)

#随机旋转类
class RandomRotate(object):
    """Crops the given PIL.Image at a random location to have a region of
    the given size. size can be a tuple (target_height, target_width)
    or an integer, in which case the target will be of a square shape (size, size)
    """
    """
    裁剪给定的PIL。图像位于随机位置,具有以下区域给定的尺寸。
    size可以是元组(target_height,target_width)
    或者是整数,在这种情况下,目标将是shape (size, size)
    """

    #角度
    def __init__(self, angle):
        self.angle = angle

    def __call__(self, image, label):
        #断言标签是空的 或 图像尺寸=标签尺寸
        assert label is None or image.size == label.size

        #生成一个随机角度 = 随机整数-本身角度
        angle = random.randint(0, self.angle * 2) - self.angle

        #旋转标签和图像
        #label最近邻插值算法进行旋转
        #img双线性插值算法进行旋转
        label = label.rotate(angle, resample=Image.NEAREST)
        image = image.rotate(angle, resample=Image.BILINEAR)

        #返回图像和标签
        return image, label
#找到车道线起始位置
#(行样本,开始车道线)
def find_start_pos(row_sample,start_line):
    # row_sample = row_sample.sort()
    # for i,r in enumerate(row_sample):
    #     if r >= start_line:
    #         return i
    l,r = 0,len(row_sample)-1
    #二分查找row_sample中start_line的位置
    while True:
        mid = int((l+r)/2)
        #已经最后一位
        if r - l == 1:
            return r
        if row_sample[mid] < start_line:
            l = mid
        if row_sample[mid] > start_line:
            r = mid
        if row_sample[mid] == start_line:
            return mid

#随机LR偏移设置标签(左右)
class RandomLROffsetLABEL(object):
    #最大偏移
    def __init__(self,max_offset):
        self.max_offset = max_offset
    def __call__(self,img,label):
        #随机生成(-max_offset,max_offset)范围内整数
        offset = np.random.randint(-self.max_offset,self.max_offset)
        w, h = img.size

        # 将图像对象转换为NumPy数组
        img = np.array(img)
        #正向平移
        #将图像向右平移 offset 像素,左侧空出区域填充黑色(RGB=0),右侧超界部分被截断。
        if offset > 0:
            img[:,offset:,:] = img[:,0:w-offset,:]
            img[:,:offset,:] = 0
        # 反向平移
        # 将图像向左平移 offset 像素,右侧空出区域填充黑色(RGB=0),左侧超界部分被截断。
        if offset < 0:
            real_offset = -offset
            img[:,0:w-real_offset,:] = img[:,real_offset:,:]
            img[:,w-real_offset:,:] = 0

        #标签数据同上
        label = np.array(label)
        if offset > 0:
            label[:,offset:] = label[:,0:w-offset]
            label[:,:offset] = 0
        if offset < 0:
            offset = -offset
            label[:,0:w-offset] = label[:,offset:]
            label[:,w-offset:] = 0
        #将NumPy数组转换为PIL图像,并返回
        return Image.fromarray(img),Image.fromarray(label)

#img[h,w,dim]
#随机UD偏移设置标签(上下)
class RandomUDoffsetLABEL(object):
    # 最大偏移
    def __init__(self,max_offset):
        self.max_offset = max_offset
    def __call__(self,img,label):
        # 随机生成(-max_offset,max_offset)范围内整数
        offset = np.random.randint(-self.max_offset,self.max_offset)
        w, h = img.size

        # 将图像对象转换为NumPy数组
        img = np.array(img)
        # 正向平移
        # 将图像向下平移 offset 像素,上侧空出区域填充黑色(RGB=0),下侧超界部分被截断。
        if offset > 0:
            img[offset:,:,:] = img[0:h-offset,:,:]
            img[:offset,:,:] = 0
        # 反向平移
        # 将图像向上平移 offset 像素,下侧空出区域填充黑色(RGB=0),上侧超界部分被截断。
        if offset < 0:
            real_offset = -offset
            img[0:h-real_offset,:,:] = img[real_offset:,:,:]
            img[h-real_offset:,:,:] = 0

        # 标签数据同上
        label = np.array(label)
        if offset > 0:
            label[offset:,:] = label[0:h-offset,:]
            label[:offset,:] = 0
        if offset < 0:
            offset = -offset
            label[0:h-offset,:] = label[offset:,:]
            label[h-offset:,:] = 0
        # 将NumPy数组转换为PIL图像,并返回
        return Image.fromarray(img),Image.fromarray(label)

 

PS:

  • Mask是一种操作,用于屏蔽或选择特定元素,常用于构建张量的过滤器‌。它相当于在原始张量上覆盖一层掩膜,从而屏蔽或选择一些特定元素;
  • PIL:是用于图像处理的第三方库,提供丰富的图像操作功能;主要用于图像存储、格式转换、像素处理及批量操作‌等。
  • NumPy数组:机器学习模型处理的数据通常是数值型的,而图像本身在计算机中是像素点的集合,每个像素有RGB值。NumPy数组可以高效地存储和操作这些数值数据,这对于处理大量的图像数据非常重要。另外,NumPy的数组操作非常高效,支持向量化运算,能够加速数据处理流程。
  • PIL处理后的图像可以转换为NumPy数组,这样数据就可以无缝地输入到机器学习框架中,比如TensorFlow或PyTorch。这些框架通常接受张量输入,而NumPy数组可以很容易地转换为张量。此外,NumPy数组支持多维数组结构,这对于处理彩色图像(高度、宽度、通道)非常合适。且NumPy底层是用C实现的,处理大规模数据时速度更快,而PIL在处理图像时也进行了优化,两者的结合可以在不损失太多性能的前提下完成复杂的图像处理任务。
  • 张量(Tensor)是一个多维数组,它是向量和矩阵的推广。
    • 一个图像可以表示为一个三维张量,其中两个维度表示图像的宽度和高度,第三个维度表示颜色通道(如RGB)。文本数据可以表示为二维张量,其中一行表示一个单词,列表示单词在文本中的出现次数。  
    • 一个图像识别任务的输入可能是一个四维张量,其中包含批次大小、图像高度、图像宽度和颜色通道。
    • 卷积神经网络(CNN)中,卷积层处理的是二维输入(图像)和二维权重,这些都可以表示为张量。

 

3. 数据处理

本模块自定义函数进行数据图像的预处理任务;

导入上一节的数据转换模块

 


 

3.1 数据处理流程

  1. 加载样本数据;
  2. 对先验样本,行锚点进行排序;
  3. 从列表文件中读取图像和标签路径;
  4. 图像数据预处理;
  5. 延长车道线,并返回车道线标签列表;
  6. 进行网格划分,把图像划分为一个个网格,得到车道线标签位于网格中的坐标,并返回,用于分类任务;
  7. 是否需要额外对图像数据做处理,如不需要则返回处理后的样本和标签数据;

3.2  延长车道线 

"""
    从标注图像中提取车道线的坐标,并通过数据增强扩展车道线,以提供更丰富的训练样本。
    主要是考虑到在实际应用中可能遇到不完整或部分遮挡的车道线。
    """
    def _get_index(self, label):
        w, h = label.size
        # 尺寸自适应处理
        # 动态适配不同分辨率标注
        if h != 288:
            # 锚点位置等比缩放
            # 定义函数scale_f:将原始坐标 x 从基准分辨率(如 288 像素高度)按比例缩放到当前目标分辨率 h
            scale_f = lambda x : int((x * 1.0/288) * h)
            #row_anchor 存储车道线在图像中的垂直位置参考点(如预设的行索引)‌
            #将row_anchor缩放为sample_tmp
            sample_tmp = list(map(scale_f,self.row_anchor))

        #初始化all_idx矩阵,结构是(num_lanes, len(sample_tmp), 2)
        all_idx = np.zeros((self.num_lanes,len(sample_tmp),2))
        """
        遍历每个行锚点r,提取对应行的标注数据label_r。
        对于每个车道线编号,寻找该行中车道线的位置,如果存在,则记录其x坐标的平均值;否则标记为-1。
        收集每个车道线在指定行锚点处的横向位置,即x坐标。
        """
        for i,r in enumerate(sample_tmp):
            #r四舍五入转为int
            #将label转换为NumPy数组,并取出第int(round(r))行的数据。这样就能得到该行所有列的车道线标签信息。
            label_r = np.asarray(label)[int(round(r))]
            #遍历该行
            """
            对于每个车道线编号,寻找该行中车道线的位置,
            如果存在,则记录其x坐标的平均值;否则标记为-1。
            收集每个车道线在指定行锚点处的x坐标。
            """
            # 遍历每条车道线(如 self.num_lanes=4 表示最多检测4条车道线)
            for lane_idx in range(1, self.num_lanes + 1):
                #获取当前行描点位置
                #在本行数据列表 label_r 中找到属于当前车道线 lane_idx 的像素位置
                pos = np.where(label_r == lane_idx)[0]
                # 当前行锚位置无车道点
                if len(pos) == 0:
                    # 记录行锚索引 r
                    all_idx[lane_idx - 1, i, 0] = r
                    # 横向坐标标记为无效(-1)
                    all_idx[lane_idx - 1, i, 1] = -1
                    continue
                # 取横向坐标的平均值
                pos = np.mean(pos)
                all_idx[lane_idx - 1, i, 0] = r
                all_idx[lane_idx - 1, i, 1] = pos

        # data augmentation: extend the lane to the boundary of image
        #数据增强:将车道延伸到图像边界
        #复制坐标位置
        all_idx_cp = all_idx.copy()
        #遍历4根车道线
        for i in range(self.num_lanes):
            #如果某个车道线在所有行锚点上的横向坐标都是-1(即无效),则跳过
            if np.all(all_idx_cp[i,:,1] == -1):
                continue
            # if there is no lane
            #获取所有有效车道线索引
            valid = all_idx_cp[i,:,1] != -1
            # get all valid lane points' index
            # 获取所有有效的车道位置
            valid_idx = all_idx_cp[i,valid,:]
            # get all valid lane points

            if valid_idx[-1,0] == all_idx_cp[0,-1,0]:
                # if the last valid lane point's y-coordinate is already the last y-coordinate of all rows
                # this means this lane has reached the bottom boundary of the image
                # so we skip
                # 如果最后一个有效车道点的y坐标已经是所有行的最后一个y坐标
                # 这意味着该车道已到达图像的底部边界
                # 所以跳过
                continue

            # 如果车道太短,无法延伸
            if len(valid_idx) < 6:
                continue
            # if the lane is too short to extend

            """
            使用后半部分的有效点进行线性拟合(np.polyfit,deg=1),得到斜率p。
            然后找到起始位置start_line,即最后一个有效点的行索引,
            接着确定从哪个行锚点开始需要延伸(pos)。
            用拟合的直线方程计算这些行锚点的横向坐标fitted,并将超出图像宽度范围的值设为-1,其余更新到all_idx_cp中。
            """
            valid_idx_half = valid_idx[len(valid_idx) // 2:,:]
            #最小二乘法进行线性拟合直线
            p = np.polyfit(valid_idx_half[:,0], valid_idx_half[:,1],deg = 1)
            #起始行start_line,即最后一个有效点的行索引
            start_line = valid_idx_half[-1,0]
            #找到起始坐标
            pos = find_start_pos(all_idx_cp[i,:,0],start_line) + 1
            #得到代入方程后计算出的值
            fitted = np.polyval(p,all_idx_cp[i,pos:,0])
            #将超出图像宽度范围的值设为-1,其余更新到all_idx_cp中。
            fitted = np.array([-1  if y < 0 or y > w-1 else y for y in fitted])

            #断言该点之后都不存在车道线
            assert np.all(all_idx_cp[i,pos:,1] == -1)
            #设置坐标
            all_idx_cp[i,pos:,1] = fitted

        #如果还是存在-1,则进行调试
        if -1 in all_idx[:, :, 0]:
            pdb.set_trace()
        return all_idx_cp

3.3 网格划分

"""
    进行网格划分
    将连续的车道线位置离散化为网格中的点,用于模型训练。
    特别是多项式拟合的部分,可能是为了扩展车道线到图像边界,增强数据。
    _grid_pts方法的主要任务是将连续的车道线坐标转换为离散的网格分类标签。
    将检测问题转化为分类问题,每个网格代表一个可能的车道线位置。
    """
    #pts是原始的车道线点坐标,来自标注数据。num_cols是横向网格数量(多少列),w是图像的宽度。
    def _grid_pts(self, pts, num_cols, w):
        # pts : numlane,n,2
        # 分割形状:多个车道线,每个车道线有n个点,每个点有2个坐标(x,y)
        num_lane, n, n2 = pts.shape
        #依照定义间隔生成均匀分布的数值序列
        #起始0,结束w-1,w/num_cols为间隔,共num_cols个数据
        #生成一个从0到w-1的等差数列col_sample,数量为num_cols。
        #生成网格采样点‌:使用np.linspace生成横向的网格分界点,均匀分布在图像宽度范围内。
        col_sample = np.linspace(0, w - 1, num_cols)

        assert n2 == 2
        #创建一个形状为(n, num_lane)的零矩阵to_pts
        #to_pts的形状为(n, num_lane),n对应纵向的锚点数量,每个位置存储该车道线在该纵向位置的横向网格索引。
        #例如to_pts = [18*4],代表4条车道线,每条车道线里有18个点
        to_pts = np.zeros((n, num_lane))
        """
        循环处理每个车道线,取每个点的y坐标(pti = pts[i, :, 1]),
        然后根据col_sample的间隔(固定的间隔)将每个y值分配到对应的网格中,如果pt为-1则标记为num_cols,
        最后返回转换为整型的to_pts。
        """
        for i in range(num_lane):
            # 遍历每个车道线‌:对于每个车道线,提取所有点的y坐标(pti)
            # 4条车道线
            pti = pts[i, :, 1]

            #将输入转换为NumPy数组,同时保持数据的原始结构和数据类型
            # 计算网格索引‌:对于每个车道线的y坐标,计算其属于哪个网格。
            # 网格宽度由col_sample的间隔(col_sample[1] - col_sample[0])决定。
            # -1时,表示该位置无车道线,标记为num_cols,作为不存在车道线处理。
            #矩阵中的每一列 = 原来的y坐标 // 网格高度 = 车道线所在的网格位置
            to_pts[:, i] = np.asarray(
                [int(pt // (col_sample[1] - col_sample[0])) if pt != -1 else num_cols for pt in pti])

        #转换为整数类型的矩阵,作为分类标签。
        #即车道线所在网格标签
        return to_pts.astype(int)

 

 


 

4. 数据读取

使用PyTorch框架进行数据处理

张量的作用:PyTorch中的张量(Tensor)是进行深度学习和其他数值计算的核心数据结构。它们类似于NumPy中的数组,但提供了GPU加速的额外功能,使得它们在进行大规模数值计算时更加高效。

对张量进行标准化处理的作用:减少不同特征间的尺度差异‌;加速模型训练速度;加速模型训练速度,收敛速度更快;提高模型性能;增强算法稳定性‌;防止数值溢出;


4.1 创建训练数据加载器:获取训练数据

"""
负责配置数据集的数据预处理、增强,并根据数据集类型和是否分布式训练来创建对应的DataLoader,返回训练数据加载器和每个车道的分类数目。
"""
#创建训练数据加载器:获取训练数据
def get_train_loader(batch_size, data_root, griding_num, dataset, use_aux, distributed):
    #transforms.Compose():将多个图像变换操作组成一个序列,从而简化图像预处理流水线。
    #将分割掩码缩放到288×800像素,并转为张量‌
    target_transform = transforms.Compose([
        mytransforms.FreeScaleMask((288, 800)),
        mytransforms.MaskToTensor(),
    ])
    #生成36×100的小尺寸分割掩码(可能用于辅助损失计算),并转为张量‌
    segment_transform = transforms.Compose([
        mytransforms.FreeScaleMask((36, 100)),
        mytransforms.MaskToTensor(),
    ])
    # 统一图像尺寸为288×800
    # 转换为张量格式
    # 应用ImageNet标准归一化(均值0.485/0.456/0.406,方差0.229/0.224/0.225)‌
    img_transform = transforms.Compose([
        transforms.Resize((288, 800)),
        transforms.ToTensor(),
        transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
    ])
    """
    组合三种数据增强:
    ▪ 随机±6度旋转
    ▪ 垂直方向随机偏移(最大100像素)
    ▪ 水平方向随机偏移(最大200像素)‌
    """
    simu_transform = mytransforms.Compose2([
        mytransforms.RandomRotate(6),
        mytransforms.RandomUDoffsetLABEL(100),
        mytransforms.RandomLROffsetLABEL(200)
    ])
    """
    根据dataset参数创建不同数据集实例:
    CULane数据集:
    ▪ 使用culane_row_anchor定义行锚点
    ▪ 每个车道线划分18个分类区间(cls_num_per_lane=18)
    Tusimple数据集:
    ▪ 使用tusimple_row_anchor
    ▪ 划分56个分类区间(cls_num_per_lane=56)
    异常情况抛出未实现错误‌
    """
    if dataset == 'CULane':
        train_dataset = LaneClsDataset(data_root,
                                           os.path.join(data_root, 'list/train_gt.txt'),
                                           img_transform=img_transform, target_transform=target_transform,
                                           simu_transform = simu_transform,
                                           segment_transform=segment_transform, 
                                           row_anchor = culane_row_anchor,
                                           griding_num=griding_num, use_aux=use_aux)
        #每条车道线18个点
        cls_num_per_lane = 18

    elif dataset == 'Tusimple':
        train_dataset = LaneClsDataset(data_root,
                                           os.path.join(data_root, 'train_gt.txt'),
                                           img_transform=img_transform, target_transform=target_transform,
                                           simu_transform = simu_transform,
                                           griding_num=griding_num, 
                                           row_anchor = tusimple_row_anchor,
                                           segment_transform=segment_transform,use_aux=use_aux)
        # 每条车道线18个点
        cls_num_per_lane = 56
    else:
        raise NotImplementedError

    #根据distributed标志选择采样器
    if distributed:
        #分布式模式使用DistributedSampler,实现多GPU数据划分‌
        sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)
    else:
        # 单机模式使用RandomSampler实现常规随机采样‌
        sampler = torch.utils.data.RandomSampler(train_dataset)
    """
    创建DataLoader时配置:
        batch_size:控制每批数据量
        sampler:使用上一步选择的采样器
        num_workers=4:启用4个子进程加速数据加载‌1
        未显式设置shuffle参数,因采样器已包含随机逻辑
    """
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, sampler = sampler, num_workers=4)

    #train_loader是经过预处理和增强的迭代器,cls_num_per_lane参数供后续模型定义使用(如输出层维度设定)‌
    return train_loader, cls_num_per_lane

4.2 构建测试阶段的数据加载器

"""
构建并返回适用于车道线检测任务测试阶段的数据加载器(DataLoader)
batch_size    控制每次迭代返回的样本数        16, 32
data_root    数据集根目录路径            '/data/culane'
dataset        指定数据集类型                'CULane', 'Tusimple'
distributed    是否启用分布式训练            True, False
"""
def get_test_loader(batch_size, data_root,dataset, distributed):
    """
    使用transforms.Compose定义图像预处理操作:
    Resize((288, 800)):将输入图像统一缩放至指定尺寸(288×800),以适应模型输入要求‌12;
    ToTensor():将图像转换为PyTorch张量格式‌15;
    Normalize:对张量进行标准化处理(基于ImageNet均值和标准差)‌
    """
    img_transforms = transforms.Compose([
        transforms.Resize((288, 800)),
        transforms.ToTensor(),
        transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
    ])
    #根据dataset参数选择不同车道线数据集(CULane或Tusimple),并实例化对应的LaneTestDataset测试集对象‌
    #cls_num_per_lane参数根据不同数据集的标注规则设定(如CULane为18,Tusimple为56)
    if dataset == 'CULane':
        test_dataset = LaneTestDataset(data_root,os.path.join(data_root, 'list/test.txt'),img_transform = img_transforms)
        cls_num_per_lane = 18
    elif dataset == 'Tusimple':
        test_dataset = LaneTestDataset(data_root,os.path.join(data_root, 'test.txt'), img_transform = img_transforms)
        cls_num_per_lane = 56

    if distributed:
        # 若distributed=True,使用SeqDistributedSampler确保多进程分布式训练中数据分片不重叠且顺序固定‌
        sampler = SeqDistributedSampler(test_dataset, shuffle = False)
    else:
        #若distributed=False,采用SequentialSampler按原始顺序加载数据‌
        sampler = torch.utils.data.SequentialSampler(test_dataset)

    #封装数据集、采样器及参数(如batch_size、num_workers=4)生成最终数据加载器,支持批量读取和多线程加速‌
    loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, sampler = sampler, num_workers=4)
    return loader

 

PS:

  •  transforms.Compose():将多个图像变换操作组成一个序列,从而简化图像预处理流水线;
  • 张量:PyTorch中的张量(Tensor)是进行深度学习和其他数值计算的核心数据结构。它们类似于NumPy中的数组,但提供了GPU加速的额外功能,使得它们在进行大规模数值计算时更加高效,并且张量是构建和操作神经网络的基本元素。

 


 

 

七、网络结构模块


 

1. 残差网络(Resnet)

ResNet(Residual Network,残差网络)是深度学习领域中非常重要且具有影响力的一种卷积神经网络(CNN)架构。

 

 


 

 backbone.py文件主要用于构建一个18层的resnet网络作为主干网络;

"""
继承Pytorch中的ResNet网络
重写初始化和前向传播函数
改变网络结构,去除全连接层
主要为了实现:多任务特征提取(如检测、分割)
"""
class resnet(torch.nn.Module):
    #通过 layers 参数动态加载  ResNet 变体
    #使用 pretrained=True 加载 ImageNet 预训练权重,增强特征泛化能力‌
    def __init__(self,layers,pretrained = False):
        super(resnet,self).__init__()
        if layers == '18':
            model = torchvision.models.resnet18(pretrained=pretrained)
        elif layers == '34':
            model = torchvision.models.resnet34(pretrained=pretrained)
        elif layers == '50':
            model = torchvision.models.resnet50(pretrained=pretrained)
        elif layers == '101':
            model = torchvision.models.resnet101(pretrained=pretrained)
        elif layers == '152':
            model = torchvision.models.resnet152(pretrained=pretrained)
        elif layers == '50next':
            model = torchvision.models.resnext50_32x4d(pretrained=pretrained)
        elif layers == '101next':
            model = torchvision.models.resnext101_32x8d(pretrained=pretrained)
        elif layers == '50wide':
            model = torchvision.models.wide_resnet50_2(pretrained=pretrained)
        elif layers == '101wide':
            model = torchvision.models.wide_resnet101_2(pretrained=pretrained)
        else:
            raise NotImplementedError
        
        self.conv1 = model.conv1
        self.bn1 = model.bn1
        self.relu = model.relu
        self.maxpool = model.maxpool
        self.layer1 = model.layer1
        self.layer2 = model.layer2
        self.layer3 = model.layer3
        self.layer4 = model.layer4

    """
    保留原始 ResNet 的 conv1、bn1、relu、maxpool 及前 4 个残差块(layer1~layer4),去除全连接层,仅作为特征提取器‌
    前向传播返回layer2、layer3、layer4的输出(x2、x3、x4),提供多尺度特征图‌
    即输入 x → conv1(卷积) → bn1(归一化) → ReLU(激活函数) → maxpool(最大池化) → layer1 → layer2 → layer3 → layer4 → 输出 (x2, x3, x4)
    """
    def forward(self,x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
        x = self.layer1(x)
        x2 = self.layer2(x)
        x3 = self.layer3(x2)
        x4 = self.layer4(x3)
        return x2,x3,x4

 

 


2. 定义网络结构

 model.py主要定义网络结构

实现了一个用于车道线检测的神经网络模型,结合了分类任务和辅助分割任务。

  1. conv_bn_relu模块

    • 结构:Conv2d + BatchNorm2d + ReLU激活函数

    • 作用:实现了一个标准卷积块,用于特征提取。通过组合卷积、批归一化和激活函数,提升模型训练稳定性和特征表达能力。

  2. parsingNet主网络结构

    • Backbone:使用ResNet作为特征提取主干网络(支持不同深度如18/34/50),输出三个层次的特征(x2, x3, fea)。

    • 输入尺寸:默认288x800,经过ResNet下采样32倍后,最终特征图尺寸为9x25(假设ResNet输出层为1/32下采样)。

    • 输出维度:cls_dim=(37,10,4),对应网格划分数、每个车道的行锚点数和车道数。总维度37104=1480。

  3. 辅助分割分支(use_aux)

    • 结构:处理ResNet中间特征(x2, x3, fea),通过上采样和拼接融合多尺度特征。

    • 输出:通道数为cls_dim[-1]+1(4车道+背景),用于像素级车道分割,辅助主任务训练。

  4. 主分类分支

    • 特征处理:通过1x1卷积降维(2048/512→8通道),展平后输入全连接层。

    • 分类层:线性层将1800维特征映射到总维度(1480),最终reshape为(37,10,4)结构。

  5. 初始化方法

    • 卷积层使用He初始化(kaiming_normal),线性层使用小标准差正态分布,BN层初始化为单位权重。

    • 确保各层参数合理初始化,加速模型收敛。

  6. 前向传播流程

    • 提取ResNet多级特征 → 辅助分支融合多尺度特征 → 主分支降维分类。

    • 输出车道参数预测(group_cls)和可选的分割结果(aux_seg)。

  7. 设计特点

    • 多任务学习:通过辅助分割任务提升特征质量,增强模型鲁棒性。

    • 多尺度融合:利用不同层次特征(空间细节+语义信息)提升检测精度。

    • 参数化输出:将车道检测建模为网格化分类问题,降低回归难度。

 

 

 

 

输出结果分析

  • 201——200个位置+1个不存在车道线
  • 18——18行
  • 4——4条车道线
"""
Conv2d + BatchNorm2d + ReLU激活函数,用作基础卷积单元‌
实现了一个标准卷积块,用于特征提取。通过组合卷积、批归一化和激活函数,提升模型训练稳定性和特征表达能力。
"""
class conv_bn_relu(torch.nn.Module):
    def __init__(self,in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1,bias=False):
        super(conv_bn_relu,self).__init__()
        #输入通道、输出通道、卷积核大小
        #bias=False:由于后续接BN层,可省略卷积偏置参数以提高计算效率‌
        #dilation:支持空洞卷积,可控制卷积核的间隔采样‌
        self.conv = torch.nn.Conv2d(in_channels,out_channels, kernel_size, 
            stride = stride, padding = padding, dilation = dilation,bias = bias)
        self.bn = torch.nn.BatchNorm2d(out_channels)
        self.relu = torch.nn.ReLU()
    #严格执行卷积→归一化→激活的顺序,避免梯度异常‌
    def forward(self,x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x
"""
parsingNet主网络结构:
    Backbone:使用ResNet作为特征提取主干网络(支持不同深度如18/34/50),输出三个层次的特征(x2, x3, fea)。
    输入尺寸:默认288x800,经过ResNet下采样32倍后,最终特征图尺寸为9x25(假设ResNet输出层为1/32下采样)。
    输出维度:cls_dim=(37,10,4),对应网格划分数、每个车道的行锚点数和车道数。总维度37*10*4=1480。
    
主干网络‌:基于ResNet提取多尺度特征(如x2, x3, fea),支持不同深度(如backbone='50')和预训练权重加载‌45。
‌辅助分支‌(use_aux=True时启用):通过多层级特征融合实现分割任务,提升模型鲁棒性‌45。
‌分类分支‌:将主干特征映射为车道线预测结果,输出维度由cls_dim定义‌
"""
class parsingNet(torch.nn.Module):
    """
    size‌;输入图像分辨率(宽×高),用于计算特征图尺寸和上采样比例,如288×800常用于自动驾驶场景‌。
    pretrained:控制是否加载ResNet预训练权重,利用迁移学习加速收敛‌。
    backbone:选择ResNet变体(如'50'、'34'),影响特征图通道数(如backbone='34'时使用较小通道数)‌。
    cls_dim:定义分类维度:(num_gridding, num_cls_per_lane, num_of_lanes),对应车道线预测的网格化编码结构‌。
    use_aux:是否启用辅助分割分支,通过多任务学习提升模型性能‌
    """
    def __init__(self, size=(288, 800), pretrained=True, backbone='50', cls_dim=(37, 10, 4), use_aux=False):
        super(parsingNet, self).__init__()

        self.size = size
        self.w = size[0]
        self.h = size[1]
        self.cls_dim = cls_dim # (num_gridding, num_cls_per_lane, num_of_lanes)
        # num_cls_per_lane is the number of row anchors
        self.use_aux = use_aux
        self.total_dim = np.prod(cls_dim)

        # input : nchw,
        # output: (w+1) * sample_rows * 4
        #主干网络:调用resnet()函数构建ResNet,输出多尺度特征x2, x3, fea,用于辅助分支和分类分支的特征提取‌
        self.model = resnet(backbone, pretrained=pretrained)
        """
        辅助分割分支(use_aux):
            结构:处理ResNet中间特征(x2, x3, fea),通过上采样和拼接融合多尺度特征。
            输出:通道数为cls_dim[-1]+1(4车道+背景),用于像素级车道分割,辅助主任务训练。
        """
        if self.use_aux:
            #层级特征融合‌:通过aux_header2、aux_header3、aux_header4处理不同层级的特征图,并上采样后拼接(torch.cat)‌
            self.aux_header2 = torch.nn.Sequential(
                conv_bn_relu(128, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(512, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            self.aux_header3 = torch.nn.Sequential(
                conv_bn_relu(256, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(1024, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            self.aux_header4 = torch.nn.Sequential(
                conv_bn_relu(512, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(2048, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            #空洞卷积‌:aux_combine使用dilation=2和dilation=4扩大感受野,增强分割精度‌
            self.aux_combine = torch.nn.Sequential(
                conv_bn_relu(384, 256, 3,padding=2,dilation=2),
                conv_bn_relu(256, 128, 3,padding=2,dilation=2),
                conv_bn_relu(128, 128, 3,padding=2,dilation=2),
                conv_bn_relu(128, 128, 3,padding=4,dilation=4),
                #输出通道‌:cls_dim[-1] + 1,对应车道线类别数及背景类‌
                torch.nn.Conv2d(128, cls_dim[-1] + 1,1)
                # output : n, num_of_lanes+1, h, w
            )
            initialize_weights(self.aux_header2,self.aux_header3,self.aux_header4,self.aux_combine)

        """
        分类分支(self.cls)‌
            ‌特征池化‌:self.pool通过1×1卷积压缩通道数(如2048→8),减少计算量‌。
            ‌全连接层‌:将特征扁平化后映射到total_dim,最终输出形状为cls_dim的多维预测结果‌
        """
        self.cls = torch.nn.Sequential(
            torch.nn.Linear(1800, 2048),
            torch.nn.ReLU(),
            torch.nn.Linear(2048, self.total_dim),
        )

        self.pool = torch.nn.Conv2d(512,8,1) if backbone in ['34','18'] else torch.nn.Conv2d(2048,8,1)
        # 1/32,2048 channel
        # 288,800 -> 9,40,2048
        # (w+1) * sample_rows * 4
        # 37 * 10 * 4
        initialize_weights(self.cls)

    def forward(self, x):
        # n c h w - > n 2048 sh sw
        # -> n 2048
        # 主干网络提取特征
        x2,x3,fea = self.model(x)
        ## 辅助分支处理
        if self.use_aux:
            x2 = self.aux_header2(x2)
            x3 = self.aux_header3(x3)
            x3 = torch.nn.functional.interpolate(x3,scale_factor = 2,mode='bilinear')
            x4 = self.aux_header4(fea)
            x4 = torch.nn.functional.interpolate(x4,scale_factor = 4,mode='bilinear')
            aux_seg = torch.cat([x2,x3,x4],dim=1)
            aux_seg = self.aux_combine(aux_seg)
        else:
            aux_seg = None
        ## 主分支分类
        fea = self.pool(fea).view(-1, 1800)

        group_cls = self.cls(fea).view(-1, *self.cls_dim)

        if self.use_aux:
            return group_cls, aux_seg

        return group_cls

 


 

3. 预训练权重(权重初始化模块)

预训练权重,顾名思义,就是预先训练好的模型参数。在深度学习中,模型的参数通常以权重矩阵和偏置向量的形式存在,这些参数是通过反向传播算法从大量的训练数据中学习得到的。预训练权重则是在大规模数据集(如ImageNet、COCO等)上,经过长时间、高强度的训练后,得到的一组最优或接近最优的模型参数。

作用:

  1. 提高模型效果:预训练模型通常在大规模数据集上进行了充分的训练,已经学习到了许多有用的特征和表示。将这些预训练权重作为新任务的初始值,可以帮助模型更快地收敛到更好的性能,从而提升模型的效果。

  2. 缩短训练时间:利用预训练权重可以大大减少新任务训练所需的时间和计算资源。因为预训练模型已经具备了一定的泛化能力,所以在新任务上只需进行微调(Fine-Tuning)即可,无需从头开始训练。

  3. 改善泛化能力:预训练模型通常具有更强的泛化能力,可以更好地迁移到新的任务或数据集上。这意味着,即使在新任务的数据量有限的情况下,预训练权重也能帮助模型获得较好的表现。

  4. 解决数据不足问题:当训练数据有限时,利用预训练权重可以有效弥补数据不足的缺陷。通过在新任务上微调预训练模型,可以充分利用预训练模型学习到的知识和经验,提高模型在小数据集上的表现。

 

权重初始化:

初始化方法选择‌

  • ‌卷积层‌:采用Kaiming初始化,适配ReLU激活函数的非线性特性,缓解梯度消失问题‌12。
  • ‌BN层‌:权重初始化为1,偏置为0,维持输入分布稳定性,避免训练初期数值震荡‌23。
  • ‌全连接层‌:使用小标准差正态分布,平衡参数多样性和梯度传播效率‌23。
def initialize_weights(*models):
    for model in models:
        real_init_weights(model)

"""
nn.Conv2d(卷积层)            kaiming_normal_ + constant_        使用ReLU增益模式,偏置置零‌
nn.Linear(线性层)            normal_(mean=0, std=0.01)        小标准差防止激活值爆炸‌
nn.BatchNorm2d(批量标准化层)    constant_(weight=1, bias=0)        保持初始分布稳定‌
"""
def real_init_weights(m):

    if isinstance(m, list):
        for mini_m in m:
            real_init_weights(mini_m)
    else:
        if isinstance(m, torch.nn.Conv2d):    
            torch.nn.init.kaiming_normal_(m.weight, nonlinearity='relu')
            if m.bias is not None:
                torch.nn.init.constant_(m.bias, 0)
        elif isinstance(m, torch.nn.Linear):
            m.weight.data.normal_(0.0, std=0.01)
        elif isinstance(m, torch.nn.BatchNorm2d):
            torch.nn.init.constant_(m.weight, 1)
            torch.nn.init.constant_(m.bias, 0)
        elif isinstance(m,torch.nn.Module):
            for mini_m in m.children():
                real_init_weights(mini_m)
        else:
            print('unkonwn module', m)

 


 

4. 1*1卷积

通道维度调整(降维/升维)

  • 作用:通过改变输出通道数(即滤波器的数量),1×1卷积可以灵活调整特征图的通道维度。
  • 示例:
    • 降维:若输入为 [H, W, 256],使用64个1×1卷积核,输出变为 [H, W, 64],显著减少后续计算量(如Inception模块)。
    • 升维:增加通道数以提取更复杂的特征组合。
  • 优势:减少参数和计算量,提升模型效率。

跨通道信息交互

  • 作用:1×1卷积将输入的所有通道进行线性组合,学习不同通道间的关联性。
  • 示例:RGB图像(3通道)经1×1卷积生成新通道,每个新通道是原始3通道的加权组合。
  • 意义:增强特征表达能力,促进多通道信息的融合。

引入非线性

  • 操作:1×1卷积后通常接激活函数(如ReLU)。
  • 效果:在保持空间尺寸不变的情况下,增加网络的非线性拟合能力。
  • 对比:若不用1×1卷积,单纯通道线性组合无法引入非线性。

残差连接的通道对齐

  • 问题:在残差网络(ResNet)中,输入和输出的通道数可能不一致,无法直接相加。
  • 解决:使用1×1卷积调整通道数,使残差分支与主分支通道匹配。
  • 示例:输入为64通道,残差块输出128通道,则1×1卷积将64→128通道。

数学视角

  • 输入:尺寸为 [H, W, C_in] 的特征图。
  • 1×1卷积核:每个核的尺寸为 [1, 1, C_in],共 C_out 个核。
  • 输出:尺寸为 [H, W, C_out],每个输出通道是输入通道的线性组合:

 

1×1卷积的核心价值在于高效调整通道维度、融合跨通道信息并引入非线性,同时保持空间结构不变。它是现代CNN中实现轻量化、模块化设计的关键组件之一。

例如,选择2个1x1大小的卷积核,特征图的深度可以从3变成2;如果使用4个1x1的卷积核,特征图的深度可以从3变成4‌。


5. 空洞卷积

空洞卷积与普通卷积的主要区别在于引入了“扩张率”这一参数。扩张率定义了卷积核处理数据时各值之间的间距。当扩张率为1时,空洞卷积退化为普通卷积;当扩张率大于1时,卷积核的元素之间会按照扩张率所指定的间隔进行采样,从而扩大感受野‌。

优势‌:

  • ‌扩大感受野‌:在不增加参数数量的情况下,空洞卷积可以扩大感受野,有助于捕捉更广泛的上下文信息。
  • ‌保持分辨率‌:在语义分割等任务中,空洞卷积可以在保持特征图分辨率的同时增大感受野,减少信息损失。

‌局限性‌:

  • ‌网格效应‌:当扩张率过大时,可能会出现“网格效应”,导致一些像素点被重复采样,影响模型的性能。
  • ‌训练难度‌:空洞卷积的参数设置对模型性能有较大影响,需要仔细调整扩张率以获得最佳效果。

 


八、损失函数模块


 

1.  OhemCELoss(在线难例挖掘交叉熵损失)

功能:筛选训练困难样本,提升模型对难例的学习能力
实现要点

  1. 阈值转换:将概率阈值转换为对数空间(-log(thresh)),与交叉熵的数学形式对齐

  2. 样本筛选

    • 计算逐像素交叉熵损失(reduction='none'

    • 按损失值降序排序,保留前n_min个困难样本

    • 动态阈值过滤:仅保留损失超过阈值的样本

  3. 应用场景:解决类别不平衡问题,特别适用于车道线像素占比小的场景

数学表达式

其中 SS 为筛选后的困难样本集合

class OhemCELoss(nn.Module):
    """
    thresh‌:概率阈值,用于过滤低损失样本(如thresh=0.7表示仅保留预测概率低于30%的样本)‌。
    n_min‌:保证每次至少选择n_min个样本参与反向传播,防止有效样本不足‌。
    ignore_lb‌:忽略特定标签(如255常用于语义分割的掩码背景)‌
    """
    def __init__(self, thresh, n_min, ignore_lb=255, *args, **kwargs):
        super(OhemCELoss, self).__init__()
        self.thresh = -torch.log(torch.tensor(thresh, dtype=torch.float)).cuda()
        self.n_min = n_min
        self.ignore_lb = ignore_lb
        self.criteria = nn.CrossEntropyLoss(ignore_index=ignore_lb, reduction='none')
    """
    损失计算‌:使用CrossEntropyLoss逐像素计算未降维损失,保留每个样本的独立损失值‌。
    ‌样本筛选‌:
        按损失值降序排序,优先选择预测误差大的样本‌。
        动态判断阈值thresh与第n_min个样本损失的关系,决定保留样本范围‌。
        梯度回传‌:仅对筛选后的高损失样本计算梯度均值,忽略简单样本的影响‌
    """
    def forward(self, logits, labels):
        N, C, H, W = logits.size()
        loss = self.criteria(logits, labels).view(-1)
        loss, _ = torch.sort(loss, descending=True)
        if loss[self.n_min] > self.thresh:
            loss = loss[loss>self.thresh]
        else:
            loss = loss[:self.n_min]
        return torch.mean(loss)

 


2、SoftmaxFocalLoss(焦点损失)

FocalLoss:Focal Loss 是一种处理类别不平衡问题的有效方法,通过引入焦点因子和调整样本权重,使得模型对难以分类的样本更加关注,从而提高分类性能。它特别适用于目标检测和其他类别不平衡的任务。

SoftmaxFocalLoss功能:降低易分类样本权重,聚焦困难样本

实现特点

  1. 调制因子:引入 (1−p)γ(1p)γ 动态缩放因子,随置信度增加衰减损失贡献

  2. 计算流程

    • 计算softmax概率 → 生成调制因子 → 调整log概率 → NLLLoss计算

  3. 优势:缓解极端类别不平衡(如背景vs车道像素),增强模型对困难边缘区域的关注

数学形式

 

"""
结合 Softmax 和 Focal Loss 的自定义损失函数类,主要用于处理分类任务中的类别不均衡与难易样本不均衡问题。其核心设计通过调整难样本的权重增强模型对困难样本的学习能力‌
gamma‌:控制难样本的权重,值越大对困难样本的关注度越高(如gamma=2时显著抑制简单样本的梯度贡献)
ignore_lb‌:忽略指定标签(如语义分割中的背景类),避免无效区域干扰训练‌
"""
class SoftmaxFocalLoss(nn.Module):
    def __init__(self, gamma, ignore_lb=255, *args, **kwargs):
        super(SoftmaxFocalLoss, self).__init__()
        self.gamma = gamma
        self.nll = nn.NLLLoss(ignore_index=ignore_lb)

    def forward(self, logits, labels):
        scores = F.softmax(logits, dim=1)   # 计算类别概率分布
        factor = torch.pow(1.-scores, self.gamma)   # 生成调制因子 (1 - p)^gamma
        log_score = F.log_softmax(logits, dim=1)    # 计算对数概率
        log_score = factor * log_score  # 应用Focal Loss调制  
        loss = self.nll(log_score, labels.long())   # 计算NLL损失  
        return loss

 


3、ParsingRelationLoss(解析关系损失)

功能:约束车道线的纵向连续性
实现机制

  1. 相邻行差异:计算特征图相邻行预测结果的L1差异

  2. 平滑约束:使用smooth L1 loss强制相邻行输出相似

  3. 物理意义:确保车道线在垂直方向的连续性,避免断裂

计算公式

 

"""
实现了一种‌空间关系约束损失函数‌,通过强制相邻行(或空间维度)的预测结果保持连续性,优化模型在结构化预测任务(如车道线检测、语义分割)中的输出一致性‌
"""
class ParsingRelationLoss(nn.Module):
    def __init__(self):
        super(ParsingRelationLoss, self).__init__()
    def forward(self,logits):
        n,c,h,w = logits.shape
        loss_all = []
        #遍历高度方向(假设为垂直维度),计算相邻两行(i与i+1)的预测差异‌
        for i in range(0,h-1):
            loss_all.append(logits[:,:,i,:] - logits[:,:,i+1,:])
        #loss0 : n,c,w
        loss = torch.cat(loss_all)
        #使用平滑L1损失函数,强制相邻行预测差异趋近于零,实现空间连续性约束‌
        return torch.nn.functional.smooth_l1_loss(loss,torch.zeros_like(loss))

 


4、ParsingRelationDis(解析关系距离损失)

功能:保持车道线位置变化的平滑性
创新设计

  1. 位置编码:通过softmax期望计算行位置坐标

    • 生成embedding向量表示类别位置

    • pos = sum(softmax(x) * embedding) 实现概率到连续坐标的转换

  2. 差异一致性:计算相邻行位置差,强制相邻差异保持相似

  3. 实现细节

    • 仅处理前50%行(假设车道主要出现在图像下半部分)

    • 使用L1 Loss约束相邻差异的一致性

数学表达

 

"""
实现了一种‌结构化关系判别损失‌,通过约束相邻行(或空间维度)的位置预测差异的平滑性,优化模型在结构化预测任务(如车道线检测、表格结构分析)中的输出稳定性‌
"""
class ParsingRelationDis(nn.Module):
    def __init__(self):
        super(ParsingRelationDis, self).__init__()
        #使用L1Loss(平均绝对误差)计算差异损失,相比MSELoss对异常值更鲁棒‌
        self.l1 = torch.nn.L1Loss()
        # self.l1 = torch.nn.MSELoss()
    def forward(self, x):
        #输入处理与概率归一化(‌Softmax归一化)
        n,dim,num_rows,num_cols = x.shape
        x = torch.nn.functional.softmax(x[:,:dim-1,:,:],dim=1)
        #位置编码与位置预测
        embedding = torch.Tensor(np.arange(dim-1)).float().to(x.device).view(1,-1,1,1)
        pos = torch.sum(x*embedding,dim = 1)
        #相邻行差异计算
        diff_list1 = []
        for i in range(0,num_rows // 2):
            diff_list1.append(pos[:,i,:] - pos[:,i+1,:])
        #平滑性损失计算
        loss = 0
        #通过L1Loss强制相邻差异值趋近,实现二阶平滑性(相邻行差异的变化幅度一致)‌
        for i in range(len(diff_list1)-1):
            loss += self.l1(diff_list1[i],diff_list1[i+1])
        #损失归一化‌:对损失值取平均,确保不同行数的输入具有可比性‌ 
        loss /= len(diff_list1) - 1
        return loss

 


5、组合应用分析

典型训练策略

total_loss = (main_loss + λ1*relation_loss + λ2*distance_loss)
  • 主损失:OhemCELoss 或 SoftmaxFocalLoss

  • 正则项:RelationLoss 约束局部连续性,DistanceLoss 保证全局平滑性

设计优势

  1. 多目标优化:分类精度+空间连续性联合优化

  2. 物理约束:将车道线的先验知识(连续、平滑)编码到损失函数

  3. 难例挖掘:提升对模糊边界、遮挡场景的鲁棒性

整体损失可以写成:

   其中 Lseg 是分割损失,α 和 β 是损失系数。


 

八、模型训练模块(Train)


1、代码结构概览

训练流程:

  1. 主控制流:初始化配置 → 准备数据 → 构建模型 → 训练循环 → 模型保存

  2. 核心模块

    • train():训练流程控制

    • inference():前向计算

    • calc_loss():多任务损失计算

    • resolve_val_data():结果后处理


2、关键模块解析

2.1. 分布式训练初始化

if distributed:
    torch.cuda.set_device(args.local_rank)
    torch.distributed.init_process_group(...)
net = DistributedDataParallel(net)  # 多卡训练封装
  • 技术要点

    • 使用NCCL后端初始化进程组

    • 通过local_rank指定设备索引

    • 采用数据并行策略

2.2. 数据加载与模型构建

train_loader = get_train_loader(...)  # 定制数据加载
net = parsingNet(...)  # 构建车道线解析网络

 

  • 实现特点

    • 支持多种backbone(ResNet系列)

    • 动态调整输入尺寸(cfg.griding_num)

    • 可选辅助分割分支(use_aux)

2.3. 训练循环控制

for epoch in range(...):
    train(...)  # 单epoch训练
    save_model(...)  # 模型保存

 

  • 迭代控制

    • 典型epoch式训练

    • 支持断点续训(resume机制)

    • 学习率动态调整(scheduler)


3、训练流程分解


 

3.1. 训练模式初始化

net.train()  # 设置模型为训练模式
progress_bar = dist_tqdm(train_loader)  # 分布式进度条
t_data_0 = time.time()  # 数据加载计时起点

关键点

  • dist_tqdm支持多GPU训练时的同步进度显示

  • 计时变量用于性能监控


 

3.2. 批次训练循环

for b_idx, data_label in enumerate(progress_bar):
    # 数据加载时间计算
    t_data_1 = time.time()
    reset_metrics(metric_dict)  # 重置指标计算器
    global_step = epoch * len(data_loader) + b_idx  # 全局迭代次数

设计特点

  • global_step的跨epoch累计便于学习率调度

  • 指标重置确保各批次独立统计


3.3. 前向推理阶段

t_net_0 = time.time()
results = inference(net, data_label, use_aux)
  • 数据流

    graph LR
      A[原始图像] --> B[Backbone特征提取]
      B --> C[主分类头]
      B --> D{使用辅助分支?}
      D -- Yes --> E[分割头]
      C --> F[cls_out]
      E --> G[seg_out]

 

3.4. 损失计算阶段

loss = calc_loss(loss_dict, results, ...)
  • 多任务损失组合

    损失类型权重系数计算方式
    分类损失(OhemCE) λ1 在线难例挖掘
    分割损失(Focal) λ2 焦点损失
    关系损失 λ3 相邻行连续性约束
    距离损失 λ4 位置平滑性约束

 

3.5. 反向传播优化

  optimizer.zero_grad()
  loss.backward() # 反向传播
  optimizer.step() # 参数更新
  scheduler.step(global_step) # 学习率调整

  • 优化策略

    • 标准PyTorch梯度更新流程
    • 按迭代次数调整学习率(需确认scheduler是否支持)

 


 

3.6. 指标计算与日志记录

    results = resolve_val_data(results, use_aux)  # 结果解析
    update_metrics(metric_dict, results)  # 更新指标
    
    if global_step % 20 == 0:  # 稀疏日志记录
        for me_name, me_op in zip(...):
            logger.add_scalar('metric/'+me_name, me_op.get())
    logger.add_scalar('meta/lr', ...)  # 学习率记录

日志监控项

日志类别记录内容记录频率
loss/* 各损失项数值 每20 step
metric/* 评估指标(acc/IoU等) 每20 step
meta/lr 当前学习率 每个step

3.7. 进度条更新

    progress_bar.set_postfix(
        loss='%.3f'%float(loss),
        data_time='%.3f'%float(t_data_1 - t_data_0),  # 数据加载耗时
        net_time='%.3f'%float(t_net_1 - t_net_0),     # 网络计算耗时
        **kwargs  # 各指标当前值
    )
    t_data_0 = time.time()  # 重置计时器

性能监控要素

  • 数据时间:反映数据加载效率(I/O瓶颈)

  • 网络时间:反映模型计算效率(GPU利用率)

  • 指标实时显示:帮助快速判断收敛情况


 九、模型测试模块(Test)


 

1. 模块功能概览

  1. 核心流程:环境配置 → 模型加载 → 分布式初始化 → 执行评估

  2. 设计目标

    • 支持多GPU分布式推理

    • 兼容不同数据集(CULane/TuSimple)

    • 处理训练与部署的模型兼容性问题

 


 

2、关键代码解析

 

2.1. 运行环境配置与分布式初始化

 

 

torch.backends.cudnn.benchmark = True  # 启用cuDNN自动优化
args, cfg = merge_config()  # 合并命令行参数与配置文件
distributed = check_world_size()  # 检测分布式环境

  if distributed:
    torch.cuda.set_device(args.local_rank) # 绑定GPU设备
    torch.distributed.init_process_group(
    backend='nccl', # NVIDIA集体通信库
    init_method='env://' # 从环境变量获取地址
  )

 


2.2 模型构建与加载

 

 

net = parsingNet(
    pretrained=False,  # 测试时不加载预训练权重
    backbone=cfg.backbone,
    cls_dim=(cfg.griding_num+1, cls_num_per_lane, 4),
    use_aux=False  # 禁用辅助分支
).cuda()

# 处理训练保存的权重格式
state_dict = torch.load(cfg.test_model, map_location='cpu')['model']
compatible_state_dict = strip_module_prefix(state_dict)  # 移除"module."前缀
net.load_state_dict(compatible_state_dict, strict=False)

 


2.3 评估执行

eval_lane(net, cfg.dataset, cfg.data_root, 
         cfg.test_work_dir, cfg.griding_num, 
         False, distributed)

根据数据集选择不同,使用不同评估方法

  • CULane:基于F1分数的车道存在性评估

  • Tusimple:基于准确率和召回率的车道连续性评估
def eval_lane(net, dataset, data_root, work_dir, griding_num, use_aux, distributed):
    net.eval()
    if dataset == 'CULane':
        run_test(net,data_root, 'culane_eval_tmp', work_dir, griding_num, use_aux, distributed)
        synchronize()   # wait for all results
        if is_main_process():
            res = call_culane_eval(data_root, 'culane_eval_tmp', work_dir)
            TP,FP,FN = 0,0,0
            for k, v in res.items():
                val = float(v['Fmeasure']) if 'nan' not in v['Fmeasure'] else 0
                val_tp,val_fp,val_fn = int(v['tp']),int(v['fp']),int(v['fn'])
                TP += val_tp
                FP += val_fp
                FN += val_fn
                dist_print(k,val)
            P = TP * 1.0/(TP + FP)
            R = TP * 1.0/(TP + FN)
            F = 2*P*R/(P + R)
            dist_print(F)
        synchronize()

3.流程总结

  1. 加载测试数据集

  2. 运行已有模型

  3. 计算评估指标(如F1-score、准确率)

  4. 生成可视化结果

  5. 保存预测结果


 十、项目部署与应用


 1. 项目性能评估

 该模块用于‌评估车道线检测模型 parsingNet 的推理性能‌,通过测量模型在固定输入尺寸下的推理速度(时间与FPS),验证其部署可行性‌,适用于部署前性能验证‌。

核心流程包括:

  • 模型初始化
  • 预热推理
  • 计时测试
  • 结果分析
"""
用于‌评估车道线检测模型 parsingNet 的推理性能‌,通过测量模型在固定输入尺寸下的推理速度(时间与FPS),验证其部署可行性‌。
核心流程包括模型初始化、预热推理、计时测试与结果分析,适用于结构化预测任务(如车道线检测)的部署前性能验证‌。
"""
torch.backends.cudnn.benchmark = True   # 启用CUDA加速优化(输入尺寸固定时生效)‌:ml-citation{ref="7" data="citationList"}
net = parsingNet(pretrained = False, backbone='18',cls_dim = (100+1,56,4),use_aux=False).cuda()
# net = parsingNet(pretrained = False, backbone='18',cls_dim = (200+1,18,4),use_aux=False).cuda()

# 切换为评估模式(禁用Dropout等训练层)‌:ml-citation{ref="7" data="citationList"}
net.eval()

# 构造全1输入张量(模拟归一化后的图像)
x = torch.zeros((1,3,288,800)).cuda() + 1
for i in range(10):
    y = net(x)   # 预热10次,初始化CUDA内核并避免首次推理延迟‌:ml-citation{ref="7" data="citationList"}

t_all = []
#推理性能测试
for i in range(100):
    t1 = time.time()
    y = net(x)
    t2 = time.time()
    t_all.append(t2 - t1)   # 记录100次推理时间

print('average time:', np.mean(t_all) / 1)  # 平均速度
print('average fps:',1 / np.mean(t_all))    # 平均帧率

print('fastest time:', min(t_all) / 1)  # 最快速度
print('fastest fps:',1 / min(t_all))    # 最高性能(最小延迟)

print('slowest time:', max(t_all) / 1)  # 最慢速度
print('slowest fps:',1 / max(t_all))    # 最低性能(最大延迟)

 


2. 模拟运行

 核心流程:配置加载 → 模型初始化 → 数据准备 → 逐帧推理 → 结果可视化 → 视频生成

坐标转换公式

  • x = pred_loc * 网格宽度 * 原图宽度 / 800

  • y = row_anchor位置 * 原图高度 / 288

关键代码:

#数据预处理流水线
img_transforms = transforms.Compose([
    transforms.Resize((288, 800)),  # 固定输入尺寸
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],  # ImageNet标准归一化
                      std=[0.229, 0.224, 0.225])
])

# CULane场景划分
splits = [
    'test0_normal.txt',  # 正常场景
    'test1_crowd.txt',   # 拥挤场景
    'test2_hlight.txt',  # 强光干扰
    'test3_shadow.txt',  # 阴影干扰
    'test4_noline.txt',  # 无标线道路
    'test5_arrow.txt',   # 箭头标识
    'test6_curve.txt',   # 弯道
    'test7_cross.txt',   # 交叉路口
    'test8_night.txt'    # 夜间场景
]

#关键后处理逻辑
col_sample = np.linspace(0, 800-1, cfg.griding_num)  # 横向采样点(0~799)
col_sample_w = col_sample[1] - col_sample[0]        # 网格宽度(约4像素)

prob = scipy.special.softmax(out_j[:-1, :, :], axis=0)  # 类别概率
loc = np.sum(prob * np.arange(1, griding_num+1), axis=0)  # 期望位置计算

# 坐标映射回原图
x_coord = loc[k, i] * col_sample_w * img_w / 800  # 横向坐标
y_coord = img_h * (row_anchor[cls_num_per_lane-1-k]/288)  # 纵向坐标

# 视频生成配置
fourcc = cv2.VideoWriter_fourcc(*'MJPG')  # M-JPEG编码
vout = cv2.VideoWriter(
    filename=split[:-3]+'avi', 
    fourcc=fourcc,
    fps=30.0,  # 帧率
    frameSize=(img_w, img_h)  # 分辨率(CULane:1640x590)
)

#车道点绘制策略
if np.sum(out_j[:, i] != 0) > 2:  # 至少3个有效点才视为有效车道
    for k in range(out_j.shape[0]):
        if out_j[k, i] > 0:
            cv2.circle(vis, ppp, 5, (0,255,0), -1)  # 绘制绿色实心圆点

 PS:

  •  在运行模块,无需进入辅助分支,走主干分支进行识别即可;
  •  在视频或图像画图时,使用openCV实现;

 

十一、项目总结


1、项目背景与需求

项目背景

车道线检测是自动驾驶和高级驾驶辅助系统的核心任务之一,旨在通过摄像头或其他传感器实时识别道路上的车道线标记,帮助车辆保持车道、避免偏离,并为路径规划提供关键信息。

功能性需求

  • ‌多场景适应‌:在不同天气、光照及道路类型(高速、城市道路)下稳定检测。
  • ‌多车道识别‌:同时检测当前车道及相邻车道线,支持车道保持和变道辅助。
  • ‌弯曲车道处理‌:准确拟合抛物线或曲线车道(如环岛、弯道)。
  • ‌遮挡鲁棒性‌:在部分遮挡(前车、污渍)时仍能推断车道走向。

非功能性需求

  • ‌实时性‌:处理单帧图像时间≤30ms。
  • ‌准确性‌:在主流数据集上达到≥95%的检测精度。
  • ‌轻量化‌:模型需适配边缘计算设备,参数量≤5M。

2、方法框架

数据准备

  • ‌使用公开数据集‌:CULane,包含多样场景标注(点集或掩模)。

网络单元选择

  • ‌深度学习模型‌:如ResNet,结合实例分割与分类任务。

关键技术

  • 网格划分:在预处理阶段,对图像数据进行网格划分,减小计算量,增大感受野,把分割问题转换为分类问题
  • 神经网络优化:主干分支是分类算法,辅助分支是检测算法,通过多层级特征融合,优化训练结果。
  • 损失函数优化:利用车道线的先验知识,车道线是笔直的,额外增加一个空间上的损失函数,用来检验图像中车道线的y轴分布情况,作为新的损失函数。

主干分支:使用ResNet网络得到训练结果(卷积、降采样、全连接),映射到原始图像上(哪根线对应哪个格),得到训练后的离散的栅格点,通过聚类或车道线拟合的方式把一个个格拟合成车道线;

辅助分支:把车道线离散出来,识别出是第几个车道,并扩大特征,目的是为了使训练效果更好;

3、代码实现流程

1. 数据集准备

  • 下载并解压CULane数据集。
  • 对数据集进行预处理,包括图像归一化、ROI裁剪、透视变换等。
  • 生成输入标签数据,对应车道线位置。

2. 项目配置

  • 设置配置文件(如culane.py),包括数据集目录、训练轮次(epoch)、批次大小(batch_size)、优化器参数等。
  • 确定网络结构,加载预训练模型(如ResNet-18)。

3. 数据处理模块

  • 自定义函数进行数据图像的预处理任务,如图像裁剪、归一化等。
  • 使用PyTorch框架进行数据处理,设置行标签等。

4. 网络结构模块

  • 自定义网络结构,分为主干的分类分支,辅助的检测分割分支;
  • 基本单元使用ResNet网络结构
  • 主干网络‌:基于ResNet提取多尺度特征(如x2, x3, fea),支持不同深度(如backbone='50')和预训练权重加载‌。
  • 辅助分支‌(use_aux=True时启用):通过多层级特征融合实现分割任务,提升模型鲁棒性‌。‌
  • 分类分支‌:将主干特征映射为车道线预测结果,输出维度由cls_dim定义‌

5. 损失函数模块

  • 根据需求自定义三个损失函数;
  • SoftmaxFocalLoss:分类损失函数,判断当前网格是否属于车道线,可以降低易分类样本权重,聚焦困难样本;

  • ParsingRelationLoss:约束车道线的纵向连续性,确保车道线在垂直方向的连续性,避免断裂,可以延申车道线;

  • ParsingRelationDis:保持车道线位置变化的平滑性,车道是连续的,也就是说,相邻行锚点中的车道点应该彼此靠近。

6. 模型训练

  • 初始化模型,切换为训练模式。
  • 使用DataLoader加载数据,
  • 使用定义好的网络模型,调用GPU进行迭代训练。
  • 计算损失函数,包括分类损失和结构损失。
  • 进行反向传播和优化,更新模型参数。
  • 保存模型权重和训练日志。

7. 模型推理与评估

  • 初始化模型,切换为评估模式。
  • 构造输入张量,进行预热推理(多次前向传播以稳定性能)。
  • 计时测试,记录多次推理时间,计算平均FPS、最快FPS和最慢FPS。
  • 输出评估结果,包括平均推理时间、平均FPS等。

8. 性能优化

  • 根据评估结果,调整输入尺寸、模型结构等。

4、评估指标

  • ‌准确率‌和召回率。
  • ‌F1分数‌:平衡误检(FP)与漏检(FN)。
  • ‌实时性‌:帧率(FPS)与端到端延迟。

通过以上流程,可以完成车道线检测模型的训练、评估与优化,为自动驾驶系统提供高性能的车道线检测功能。

5、与传统车道线检测算法的对比

  传统车道线检测基于边缘特征检测车道线,但是只能检测样本原有标注好的车道,且无法识别车道被阻挡或磨损等复杂场景的情况;例如语义分割对图像的每一个像素进行分类,即判断每个像素点是车道还是背景,但可能存在不是车道的地方被识别成车道的错误;并且缺点有计算量大,训练速度慢,且判断的是车道而非车道线;

  而快车道线检测算法不用判断每一个像素,而是判断一个个点,然后把点连起来作为车道线进行检测;

  快车道线模型可以解决之前语义分割+实例分割的两个问题

  • 车道线完全被遮挡下,像素中感受野中上下文都i没有车道线学习,导致无法识别车道线
  • 在端上速度慢,如何实时进行检测

  训练后,网络可以根据是否存在车辆来判断被遮挡住的车道线;


十二、常见问题

 


 1、使用到的技术?

  • numpy进行矩阵计算;
  • pytorch作为深度学习框架,进行训练、搭建、前向传播、反向传播、损失函数计算、性能评估等一系列操作;
  • openCV进行画图,输出训练和测试结果;

2、ResNet是什么?为什么要使用ResNet网络?

ResNet(Residual Network,残差网络)是一种在深度学习领域中非常重要的卷积神经网络(CNN)架构。

ResNet的核心思想是残差学习,通过学习输入与输出的残差,简化优化任务。其关键结构是残差块,通过短连接(Skip Connection)直接跳过部分非线性层,将输入直接传递至输出,有效缓解梯度消失问题‌

ResNet(先降维再升维)

 

 

(1)深层特征提取能力‌

ResNet通过残差结构有效缓解了深层网络训练中的梯度消失问题,能够提取更丰富的多尺度特征:

  • ‌低层特征‌(浅层网络):捕捉车道线边缘、颜色梯度等细节信息,增强局部定位精度‌
  • ‌高层特征‌(深层网络):学习车道线整体走向、上下文关系等语义信息,提升对遮挡或极端光照的鲁棒性‌

(2)预训练模型迁移优势‌

  • ResNet在ImageNet等大型数据集上的预训练权重,提升了模型在车道线检测任务中的泛化能力,减少训练数据不足的影响‌78;
  • 预训练特征对复杂道路场景(如阴影、反光)具有更强的适应性‌

 


3、分割算法的作用?

图像分割算法通过像素级解析为计算机视觉任务提供基础数据,其作用贯穿于感知、分析与决策的各个环节。

深度学习中的图像分割算法通过像素级分类实现对图像的精细化分析与理解,其核心作用包括以下方面:

  • 精准识别物体边界‌:分割算法可为图像中的每个像素分配类别标签,精确划分不同对象的边界,例如区分医学影像中的病变组织与正常组织‌
  • 提取结构化信息‌:通过分割结果生成掩膜(Mask),为后续任务(如目标检测、三维重建)提供结构化数据支持‌

 

4、项目算法介绍:网格划分

传统的语义分割要判断每一个像素点,该算法把图像分割成一个个的小网格;

例如原图是1280*720像素的图像,通过分割成一个个网格,把判断像素->判断小网格,把识别到的小网格连接起来,从而大大减小计算量,易于部署到移动端,且准确度高;

从而把语义分割问题->分类检测问题;

处理图像缩小,感受野变大,训练时间也变短;

 


5、项目算法介绍:网络结构

 

 辅助分支显示在上半部分,仅在训练时有效。特征提取器显示在蓝色框中。基于分类的预测和辅助分割任务分别在绿色和橙色框中表示。对每个行锚点进行组分类。

  • 主干分支:使用ResNet网络得到训练结果(卷积、降采样、全连接),映射到原始图像上(哪根线对应哪个格),得到训练后的离散的栅格点,通过聚类或车道线拟合的方式把一个个格拟合成车道线;
  • 辅助分支:分割任务强制模型关注车道线的连续几何形态,与主分支的锚点分类形成互补;学习车道线的局部细节特征,弥补主分支对细粒度信息捕捉的不足提升复杂场景(如遮挡、模糊)下的检测鲁棒性‌

PS:

   (1)需要注意的是,我们的方法在训练阶段只使用辅助分割任务,在测试阶段会去掉。这样,即使我们添加了额外的切分任务,我们方法的运行速度也不会受到影响。它与没有辅助分割任务的网络相同。

   (2)同时注意最后完成的是分类任务,最后全连接层输出14472个特征,即14472=【201,18,4】;

  • 其中【18,4】与标签对应;
  • 201则是分类概率(其中0-199表示位置类别,200表示不存在车道)(即200+1=201);

   (3)Resblocks可以选择任意网络,例如ResNet,FCN的计算量有点大;


6、项目算法介绍:损失函数

 

  • L_cls:监督行/列锚点的分类结果(交叉熵损失)‌,主干分类分支的损失函数,判断当前网格是否是车道线;使用交叉熵损失实现。作者使用额外的维度来表示没有车道,因此我们的公式由 (w + 1) 维而不是 w 维分类组成。
  • L_str:约束相邻行车道线位置的连续性(如L1/L2损失)‌,空间的损失函数,判断当前网格的与y轴上方的车道线网格,y轴位置应该相近,大部分车道线是直线,使用二阶差分方程来约束车道的形状
  • L_seg:通过二阶差分约束车道线曲率的光滑性(如多项式拟合误差)‌,辅助分割分支的损失函数,判断;使用多尺度特征的辅助分割任务来模拟局部特征。使用交叉熵作为辅助分割损失。

关于L_str:

  • L1loss:torch.nn.functional.smooth_l1_loss(loss,torch.zeros_like(loss)) 实现
  • 对于二阶平滑性:通过L1Loss强制相邻差异值趋近,实现二阶平滑性(相邻行差异的变化幅度一致)‌,即遍历所有输出数据,计算相邻两个元素的L1损失,累加起来,然后求平均即可;

在L_str是结构化损失函数:

  • 连续性损失(L_sim):约束相邻行锚点的预测分布相似性。
  • 形状损失(L_shp):通过二阶差分约束车道线平滑性。

交叉熵的数学表达:

 

设计优势

  1. 多目标优化:分类精度+空间连续性联合优化

  2. 物理约束:将车道线的先验知识(连续、平滑)编码到损失函数

  3. 难例挖掘:提升对模糊边界、遮挡场景的鲁棒性


7、项目算法介绍:评估指标

本项目使用IOU和F1分数进行评估;

F1分数:

其中 Precision是准确度,Recall是召回率 ,T P 是真阳性,F P 是假阳性,F N 是假阴性。

 

IOU:

每个通道都被视为一条 30 像素宽的线。然后计算 ground truth 和 predictions 之间的交集与并集 (IoU)。IoUs 大于 0.5 的预测被视为真阳性。

 

 


8、深度学习中 def forward(self, x)的作用?

在深度学习中,def forward(self, x) 是 PyTorch 框架中定义神经网络前向传播(forward pass)的核心方法。它的作用可以用以下几点概括:

定义数据流的计算路径‌

  • ‌输入到输出的映射‌:forward 方法定义了输入数据 x 如何通过网络中的各层(如全连接层、卷积层等)逐步转换为最终的输出。
  • 灵活的动态计算‌:可以在 forward 中实现复杂的逻辑(如条件分支、循环),而不仅仅是固定层顺序。
与 __call__ 方法的关联‌
  • ‌隐式调用‌:通常不直接调用 forward(),而是通过模型实例调用(如 output = model(x))。这是因为 PyTorch 的 nn.Module 基类重写了 __call__ 方法,会在调用时自动执行 forward,并处理钩子(hooks)和缓存等机制。
  • ‌避免手动调用‌:直接调用 model.forward(x) 会绕过 __call__ 中的逻辑(如自动梯度跟踪),可能导致错误。

与自动求导(Autograd)的集成‌

  • ‌计算图构建‌:在 forward 执行过程中,PyTorch 会动态记录所有张量操作,构建计算图。这使得反向传播时可以通过链式法则自动计算梯度。
  • ‌梯度追踪‌:只有通过 forward 方法定义的操作才会被梯度跟踪(如果 requires_grad=True)。
示例代码 :
def forward(self, x):
    x = self.conv1(x)     # 第一层卷积
    x = F.relu(x)         # 激活函数
    x = self.fc(x)        # 全连接层
    return x
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(10, 5)  # 定义全连接层

    def forward(self, x):
        # 定义输入 x 如何转换为输出
        return self.fc(x)

# 使用模型
model = MyModel()
input = torch.randn(3, 10)  # 3个样本,每个样本10维
output = model(input)       # 调用 __call__,自动执行 forward

总结

  • ‌核心作用‌:forward 是定义模型逻辑的入口,将输入逐步映射到输出。
  • ‌调用方式‌:通过 model(x) 触发,而非直接调用 forward
  • ‌框架协作‌:与 PyTorch 的自动求导和模块化设计深度集成,确保计算图和梯度正确生成。

如果忘记实现 forward,PyTorch 会抛出 NotImplementedError 错误。

 


9、epoch是什么意思?

 在深度学习中,‌Epoch(轮次/周期)‌ 指模型完整遍历一次整个训练数据集的过程。它是训练过程中的基本单位,直接影响模型的学习效果和收敛速度。

‌核心概念‌

    1. ‌定义‌:

      • 1个 Epoch = 模型用训练集中的‌所有样本‌完成一次前向传播(预测)和反向传播(参数更新)。
      • 例如:训练集有 10,000 张图片,当模型遍历完这 10,000 张图片时,即完成 1 个 Epoch。
    2. ‌与 Batch(批次)和 Iteration(迭代)的关系‌:

      • ‌Batch‌:单次输入模型的样本子集(如每次训练输入 32 张图片)。
      • ‌Iteration‌:完成一个 Batch 训练所需的计算步骤。
      • 公式:
        Iterations per Epoch=训练集样本总数Batch SizeIterations per Epoch=Batch Size训练集样本总数
        例如:训练集 10,000 张图,Batch Size=32 → 1 Epoch ≈ 313 次 Iterations。

‌Epoch 的作用‌

  1. ‌控制训练进度‌:

    • 通过设置 Epoch 数量(如 100 轮),决定模型遍历数据集的次数。
    • 较少的 Epoch 可能导致欠拟合,较多的 Epoch 可能引发过拟合。
  2. ‌优化模型收敛‌:

    • 多轮训练使模型逐步调整参数,学习数据中的复杂模式。
    • 每一轮结束后,数据通常会被‌随机打乱‌(Shuffle),增强泛化能力。
  3. ‌监控训练状态‌:

    • 每完成一个 Epoch,可记录训练集和验证集的损失(Loss)和准确率(Accuracy),分析模型性能变化。

10、优化函数的作用?

 优化函数通过参数更新策略、学习率动态调整和正则化机制,实现损失函数的高效最小化,并平衡模型的训练速度与泛化性能‌。

本项目中使用Adam进行模型训练;

Adam‌:结合动量(Momentum)和自适应学习率,在多数场景下表现稳定且高效‌

Adam(Adaptive Moment Estimation)‌ 是一种广泛使用的自适应优化算法,结合了‌动量(Momentum)‌和‌自适应学习率‌的优点,旨在高效解决非凸优化问题(如神经网络的训练)。

 应用场景‌

  • ‌深度学习主流选择‌:用于训练 CNN(图像分类)、RNN(序列建模)、Transformer(NLP)等复杂模型。
  • ‌非凸优化问题‌:如神经网络的损失函数存在大量局部极小值时,Adam 能有效跳出局部最优。
  • ‌稀疏梯度数据‌:在推荐系统、文本分类中,Adam 对稀疏特征(如 one-hot 编码)的优化效果显著。

‌优化器对比与选择‌

‌优化器‌‌优势‌‌适用场景‌
‌SGD‌ 简单、易调参,适合凸优化问题 基础模型、理论验证
‌Adam‌ 自适应学习率,收敛快,鲁棒性强 大多数深度学习任务
‌RMSProp‌ 处理非平稳目标函数,适合循环神经网络(RNN) 序列建模、时间序列预测
‌AdaGrad‌ 自动分配参数学习率,适合稀疏特征数据 自然语言处理(NLP)任务

 


11、项目的训练流程?

  1. 准备数据集
  2. 图像预处理(归一化)
  3. 检测处理后的数据
  4. 生成输入的标签数据
  5. 配置文件
  6. 设置超参数
  7. lr选择
  8. DataLoader加载数据
  9. 确定网络结构(加载预训练模型)
  10. 选择优化函数(SGD、Adam)
  11. 分为两个分支(语义分割和实例分割),得到像素的结果
  12. 计算损失函数(给车道线像素加一个权重,避免背景和车道线的差异)(语义分割使用交叉熵,实例分割使用判别式类间和类内的计算方式)
  13. 整合两个分支损失函数的结果
  14. 进行反向传播和优化
  15. 训练epoch时会保存模型,得到模型的测试指标

 


12、端到端训练是什么意思?

在机器学习中,‌端到端训练(End-to-End Training)‌ 是一种直接从‌原始输入数据‌到‌最终输出结果‌的模型训练方式,无需依赖人工设计的中间步骤或手动特征工程。它通过单一模型架构实现整个流程的自动化学习和优化,是深度学习革命的核心推动力之一。

自动驾驶‌

  • ‌传统流程‌:
    摄像头图像 → 车道线检测 → 障碍物识别 → 路径规划 → 控制指令
  • ‌端到端模型‌:
    NVIDIA 的 PilotNet 直接输入车载摄像头图像,输出方向盘转向角度和油门控制信号。

核心价值‌在于:

  • ‌降低领域知识门槛‌:使非专家也能构建高性能模型。
  • ‌释放数据潜力‌:通过端到端优化充分挖掘数据中的隐含规律。
  • ‌推动技术融合‌:为多模态、跨任务学习提供统一框架(如通用AI的探索)。

13、使用行锚点划分后,为什么可以扩大感受野?

 使用基于分类的方式来学习具有锚驱动表示的车道坐标。使用基于分类的方式能够确保使用了整个全局特征,能够确保感受野与整个输入一样大。它使网络能够更好地捕获全局和远程信息进行车道检测,并有效缓解无视觉线索的问题。

    1. 多尺度特征融合
      行锚点常结合多层级特征(如特征金字塔网络,FPN)。低层特征保留细节但感受野小,高层特征感受野大但较粗糙。通过行锚点对不同层级特征的融合,每个锚点的预测既具备局部精度,又捕获了全局上下文,间接扩大感受野。

    2. 注意力机制的引入
      行锚点结构常与注意力机制结合(如自注意力或通道注意力),使网络在处理特定行时能动态关注图像其他区域。例如,某行的车道线预测可能依赖远处车辆的移动趋势,这种全局依赖显著扩展了感受野。

    3. 网络结构的适应性设计
      为适配行锚点,主干网络可能采用空洞卷积或更深的层直接增大卷积核的有效覆盖范围。例如,深层卷积堆叠自然积累更大感受野,而空洞卷积可在不增加参数量的情况下扩大感受野

 


14、主干分支和辅助分支的作用?

分为主干和辅助两个分支

  • 辅助分支只在训练时使用
  • 测试时主要走主干分支

作用:

  • 主干分支(分类):区分该格是否是车道线,使用ResNet网络得到训练结果(卷积、降采样、全连接),映射到原始图像上(哪根线对应哪个格),得到训练后的离散的栅格点,通过聚类或车道线拟合的方式把一个个格拟合成车道线;
  • 辅助分支(分割):分割任务强制模型关注车道线的连续几何形态,与主分支的锚点分类形成互补;学习车道线的局部细节特征,弥补主分支对细粒度信息捕捉的不足提升复杂场景(如遮挡、模糊)下的检测鲁棒性‌

Resblocks可以选择任意网络,例如ResNet,FCN的计算量有点大;

 


15、模型怎么压缩?

  • 知识蒸馏:teacher-student,使用大的网络来指导小的网络进行学习,在已经有一个大模型的基础上,可以用大模型的结果(残差块)指导小模型,以达到用小模型代替大模型的效果;;
  • 模型量化:float->int,量化主要是对模型的参数和激活值的存储方式,进行位数上的更改(例如float->int);作用是减少计算量,加快推理速度;量化可以全部参数都量化,也可以指定某一层进行量化;可以使用pytorch里的包进行量化操作;

16、CNN主干网络是怎么提取多尺度特征的?

一个图像矩阵经过一个卷积核的卷积操作后,得到了另一个矩阵,这个矩阵叫做特征映射(feature map)。每一个卷积核都可以提取特定的特征,不同的卷积核提取不同的特征,举个例子,现在我们输入一张人脸的图像,使用某一卷积核提取到眼睛的特征,用另一个卷积核提取嘴巴的特征等等。而特征映射就是某张图像经过卷积运算得到的特征值矩阵。

  1. 浅层网络(底层)‌

    • ‌功能‌:像“放大镜”看细节,捕捉小尺度特征(如车道线的边缘、短线段)。
    • ‌例子‌:前几层卷积提取车道线的局部纹理(如白色虚线边缘)。
  2. ‌深层网络(高层)‌

    • ‌功能‌:像“望远镜”看整体,捕捉大尺度特征(如车道线的走向、消失点)。
    • ‌例子‌:深层特征能感知整条车道的弯曲趋势。
  3. ‌特征融合‌

    • ‌操作‌:将浅层的细节和深层的全局信息“拼合”在一起。
    • ‌效果‌:既能看清局部(精确位置),又能理解全局(车道连续性)。
    • ‌类比‌:拼图时先看局部碎片,再看整体轮廓,最后结合两者完成拼图。

 

 


17、注意力机制增强关键区域是什么?

 注意力机制通过‌空间权重分配‌、‌通道特征筛选‌及‌时空联合建模‌,使深度学习模型能够“主动选择”关键区域,其本质是模拟人类视觉系统的注意力聚焦特性‌。这种机制在复杂场景(如自动驾驶、遥感分析)中尤为重要,已成为提升模型性能的核心技术之一‌

空间域注意力:定位图像中的关键区域‌

  • ‌原理‌:通过生成空间权重图,突出图像中对任务敏感的区域(如车道线边缘、目标物体轮廓)‌
  • ‌实现方式‌:
    • 使用‌空间注意力模块‌(如SENet的变体),对特征图的每个空间位置分配权重‌
    • ‌示例‌:在车道线检测中,模型可能对道路中央区域(车道线高概率出现的位置)赋予更高权重‌
  • ‌效果‌:抑制背景干扰(如树木、车辆),增强目标区域的特征响应‌

‌关键区域的增强意义‌

  1. ‌提升效率‌:减少对冗余区域的计算,例如在车道线检测中仅关注路面区域而非整个图像‌
  2. ‌增强鲁棒性‌:在遮挡、光照变化等场景下,通过注意力权重补偿信息缺失(如依赖未遮挡区域推断车道线走向)‌
  3. ‌多任务适配‌:根据不同任务动态调整关注区域(如车道线检测关注纵向连续性,目标检测关注物体边界框)‌

18、快车道线检测算法与传统车道线检测算法的对比

  传统车道线检测基于边缘特征检测车道线,但是只能检测样本原有标注好的车道,且无法识别车道被阻挡或磨损等复杂场景的情况;例如语义分割对图像的每一个像素进行分类,即判断每个像素点是车道还是背景,但可能存在不是车道的地方被识别成车道的错误;并且缺点有计算量大,训练速度慢,且判断的是车道而非车道线;

  而快车道线检测算法不用判断每一个像素,而是判断一个个点,然后把点连起来作为车道线进行检测;

  快车道线模型可以解决之前语义分割+实例分割的两个问题

  • 车道线完全被遮挡下,像素中感受野中上下文都i没有车道线学习,导致无法识别车道线
  • 在端上速度慢,如何实时进行检测

  训练后,网络可以根据是否存在车辆来判断被遮挡住的车道线;


 19、项目怎么缓解过拟合现象?

 数据增强:由于车道的固有结构,基于分类的网络很容易过度拟合训练集,并在验证集上表现出较差的性能。为了防止这种现象并获得泛化能力,采用了一种由旋转、垂直和水平偏移组成的增强方法。此外,为了保持车道结构,车道延伸到图像的边界。增强的结果下图所示。

 

 

 

Resblocks可以选择任意网络,例如ResNet,FCN的计算量有点大;

 


20、数据处理成mask的作用?

Mask是一种操作,用于屏蔽或选择特定元素,常用于构建张量的过滤器‌。它相当于在原始张量上覆盖一层掩膜,从而屏蔽或选择一些特定元素;

 Mask(掩码)通过对数据的特定区域进行选择性屏蔽或加权,优化模型对关键信息的处理效率与精度

区域屏蔽与特征选择‌

  • ‌功能‌:在图像/视频任务中,通过Mask过滤噪声区域或强化目标区域‌
    • ‌兴趣区提取‌:如车道线检测中,屏蔽非路面区域以降低干扰‌
    • ‌实例分割‌:生成像素级掩膜区分目标与背景‌
  • ‌实现‌:通过二值矩阵或权重矩阵对特征图进行逐元素乘法操作‌

21、Adam优化器是什么?

Adam‌:结合动量(Momentum)和自适应学习率,在多数场景下表现稳定且高效‌

Adam(Adaptive Moment Estimation)‌ 是一种广泛使用的自适应优化算法,结合了‌动量(Momentum)‌和‌自适应学习率‌的优点,旨在高效解决非凸优化问题(如神经网络的训练)。

 应用场景‌

  • ‌深度学习主流选择‌:用于训练 CNN(图像分类)、RNN(序列建模)、Transformer(NLP)等复杂模型。
  • ‌非凸优化问题‌:如神经网络的损失函数存在大量局部极小值时,Adam 能有效跳出局部最优。
  • ‌稀疏梯度数据‌:在推荐系统、文本分类中,Adam 对稀疏特征(如 one-hot 编码)的优化效果显著。

22、epoch是什么?

  • epoch是指模型在训练过程中遍历整个训练数据集一次的过程。(如果训练数据集包含 10,000 个样本,那么一个 Epoch 就是模型在这 10,000 个样本上完成一次 前向传播(forward pass) 和 反向传播(backward pass) 的过程)
  • batch_size表示单次传递给程序用以训练的参数个数或数据样本个数;

23、骨干网络是什么?

 backbone:深度神经网络的主要部分,通常用于特征提取。骨干网络通过卷积操作提取图像中的低级和高级特征,为后续的特定任务(如分类、检测、分割等)提供丰富的信息。这些骨干网络一般是预训练的,即在大型数据集(如ImageNet)上进行训练,以便在各种下游任务中实现良好的性能。

 

23、插值算法的作用?
双线性插值算法
最近邻线性插值算法
 插值算法在深度学习中主要用于‌数据维度的动态调整‌与‌特征信息的连续化重建‌。
作用:

上采样(Upsampling)与分辨率提升‌

  • ‌功能‌:在生成高分辨率图像或特征图时,通过插值填补低维数据到高维空间的像素缺失‌

 数据增强与小样本扩展‌

  • ‌功能‌:通过插值生成虚拟数据点,扩充训练样本规模,缓解数据不足问题‌、

特征对齐与尺寸匹配‌

  • ‌功能‌:在网络多尺度特征融合阶段,对不同层级的特征图进行尺寸调整以实现对齐‌

24、NumPy和PIL的作用?
  • PIL:是用于图像处理的第三方库,提供丰富的图像操作功能;主要用于图像存储、格式转换、像素处理及批量操作‌等。
  • NumPy数组:机器学习模型处理的数据通常是数值型的,而图像本身在计算机中是像素点的集合,每个像素有RGB值。NumPy数组可以高效地存储和操作这些数值数据,这对于处理大量的图像数据非常重要。另外,NumPy底层是用C实现的,NumPy的数组操作非常高效,支持向量化运算,能够加速数据处理流程。
  • PIL处理后的图像可以转换为NumPy数组,这样数据就可以无缝地输入到机器学习框架中,比如TensorFlow或PyTorch。这些框架通常接受张量输入,而NumPy数组可以很容易地转换为张量。此外,NumPy数组支持多维数组结构,这对于处理彩色图像(高度、宽度、通道)非常合适。且NumPy底层是用C实现的,处理大规模数据时速度更快,而PIL在处理图像时也进行了优化,两者的结合可以在不损失太多性能的前提下完成复杂的图像处理任务。

25、张量的作用?

张量的作用:PyTorch中的张量(Tensor)是进行深度学习和其他数值计算的核心数据结构。它们类似于NumPy中的数组,但提供了GPU加速的额外功能,使得它们在进行大规模数值计算时更加高效,并且张量是构建和操作神经网络的基本元素。

对张量进行标准化处理的作用:减少不同特征间的尺度差异‌;加速模型训练速度;加速模型训练速度,收敛速度更快;提高模型性能;增强算法稳定性‌;防止数值溢出;

  • 张量(Tensor)是一个多维数组,它是向量和矩阵的推广。
    • 一个图像可以表示为一个三维张量,其中两个维度表示图像的宽度和高度,第三个维度表示颜色通道(如RGB)。文本数据可以表示为二维张量,其中一行表示一个单词,列表示单词在文本中的出现次数。  
    • 一个图像识别任务的输入可能是一个四维张量,其中包含批次大小、图像高度、图像宽度和颜色通道。
    • 卷积神经网络(CNN)中,卷积层处理的是二维输入(图像)和二维权重,这些都可以表示为张量。

26、项目是如何进行网格划分的?

将检测问题转化为分类问题,每个网格代表一个可能的车道线位置。
流程:
  1. ‌横向网格分界点生成‌
    使用 np.linspace(0, w-1, num_cols) 生成横向均匀分布的网格分界点。例如,若图像宽度 w=640num_cols=10,则分界点为 [0, 71.11, 142.22, ..., 639],共10个点,将图像横向划分为9个等宽网格(每个宽度为71.11)。

  2. ‌计算网格宽度:图像宽度 / 网格数

     
  3. ‌遍历每个车道线及其像素点‌

  4. ‌网格索引计算‌
    对每个点的横向坐标 x 进行离散化:

    • 计算其所属网格索引:index = x // grid_width
    • 若 x=-1(无效点),标记为 num_cols(表示该位置无车道线)。
  5. ‌构建分类标签矩阵‌
    将结果存储到 to_pts 矩阵中,形状为 (n, num_lane)。其中:

    • n 是纵向位置数量(如18个预定义锚点)。
    • num_lane 是车道线数量。
      每个元素表示对应纵向位置的车道线横向网格索引。
  6. ‌返回整型标签‌
    最终将 to_pts 转换为整数类型,作为模型训练的分类标签。

示例演示:

假设 w=640num_cols=10,则 grid_width=71.11

  • 若某点 x=150,则 150 // 71.11 ≈ 2,属于第2个网格(索引从0开始)。
  • 无效点 x=-1 标记为10,超出有效索引范围,表示无车道线。

通过这种方式,连续的车道线检测问题被转化为对每个纵向锚点的横向网格分类任务,简化模型设计。

 


27、项目是如何延申车道线的?

  1. x轴遍历,找到车道线网格;
  2. y轴遍历,找到该条车道线底部边界;
  3. 用最小二乘法进行线性拟合直线,使用后半部分的有效点进行线性拟合,得到斜率p;
  4. 找到起始位置(即最后一个有效点的索引),用拟合的线性方程计算延申点的坐标,并更新到数据列表中;

27、DataLoader的作用?

transforms.Compose():将多个图像变换操作组成一个序列,从而简化图像预处理流水线;

 DataLoader是深度学习框架(如PyTorch)中用于‌数据加载与管理的核心工具‌,其作用围绕数据的高效处理、训练优化及流程标准化展开。

作用‌:

  • 将大规模数据集划分为小批次(mini-batch),避免一次性加载全部数据导致内存溢出,支持高效处理超大规模数据‌;
  • 将数据预处理(如归一化、裁剪)和数据增强(如随机旋转、翻转)嵌入数据加载流程,提升训练效率‌;
  • 利用多进程/多线程预加载数据,减少模型等待数据的时间,最大化GPU利用率‌;

28、残差网络(ResNet)前向传播的步骤

ResNet 的 layer1 至 layer4 通过逐步下采样和通道扩展,从局部到全局提取图像特征:

  • ‌浅层(layer1/layer2)‌:聚焦细节与局部语义‌
  • ‌深层(layer3/layer4)‌:捕获全局抽象信息‌
  • ‌残差块差异‌:浅层网络用 BasicBlock 降低计算成本,深层网络用 Bottleneck 平衡参数量与性能‌
 """
    保留原始 ResNet 的 conv1、bn1、relu、maxpool 及前 4 个残差块(layer1~layer4),去除全连接层,仅作为特征提取器‌
    前向传播返回layer2、layer3、layer4的输出(x2、x3、x4),提供多尺度特征图‌
    即输入 x → conv1(卷积) → bn1(归一化) → ReLU(激活函数) → maxpool(最大池化) → layer1 → layer2 → layer3 → layer4 → 输出 (x2, x3, x4)
    """
    def forward(self,x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
        x = self.layer1(x)
        x2 = self.layer2(x)
        x3 = self.layer3(x2)
        x4 = self.layer4(x3)
        return x2,x3,x4

 


29、辅助分割分支是怎么帮助主干分类分支进行训练的?

 

(1)特征增强学习‌
分割分支通过像素级标注(如二值掩码)监督模型学习车道线的局部细节特征,弥补主分支(基于行或列锚点分类)对细粒度信息捕捉的不足提升复杂场景(如遮挡、模糊)下的检测鲁棒性‌

 

 

‌(2)结构化约束引导‌
分割任务强制模型关注车道线的连续几何形态,与主分支的锚点分类形成互补:

 

  • ‌主分支‌:通过行/列锚点预测车道线位置,侧重全局结构‌
  • ‌分割分支‌:通过像素级分类细化局部特征,抑制断线或误检‌

 

‌(3)训练阶段的协同优化‌
分割分支仅在训练时启用,通过多任务学习提升主分支的特征表达能力,而推理阶段仅保留主分支,保证实时性‌

 

关于多层级特征融合:

   通过多层级特征融合,通过整合神经网络不同层次输出的特征图,提升模型对车道线结构感知能力。

其核心是通过融合底层细节特征和高层语义特征,增强模型对复杂场景(如光照变化、遮挡等)的适应能力。

特征层级划分‌

  • ‌底层特征‌(分类):捕捉图像细节(如边缘、颜色梯度等),对应浅层网络输出的高分辨率特征图,适合定位车道线边界‌
  • ‌高层特征‌(检测):提取抽象语义信息(如车道线整体走向、上下文关系),对应深层网络输出的低分辨率特征图,适合识别复杂场景下的车道线连续性‌

融合方法‌

  • ‌多尺度特征拼接‌:将不同层级的特征图通过上采样或下采样调整至相同分辨率后拼接。

 


30、预训练权重是什么?

预训练权重,顾名思义,就是预先训练好的模型参数。在深度学习中,模型的参数通常以权重矩阵和偏置向量的形式存在,这些参数是通过反向传播算法从大量的训练数据中学习得到的。预训练权重则是在大规模数据集(如ImageNet、COCO等)上,经过长时间、高强度的训练后,得到的一组最优或接近最优的模型参数。

作用:

  1. 提高模型效果:预训练模型通常在大规模数据集上进行了充分的训练,已经学习到了许多有用的特征和表示。将这些预训练权重作为新任务的初始值,可以帮助模型更快地收敛到更好的性能,从而提升模型的效果。

  2. 缩短训练时间:利用预训练权重可以大大减少新任务训练所需的时间和计算资源。因为预训练模型已经具备了一定的泛化能力,所以在新任务上只需进行微调(Fine-Tuning)即可,无需从头开始训练。

  3. 改善泛化能力:预训练模型通常具有更强的泛化能力,可以更好地迁移到新的任务或数据集上。这意味着,即使在新任务的数据量有限的情况下,预训练权重也能帮助模型获得较好的表现。

  4. 解决数据不足问题:当训练数据有限时,利用预训练权重可以有效弥补数据不足的缺陷。通过在新任务上微调预训练模型,可以充分利用预训练模型学习到的知识和经验,提高模型在小数据集上的表现。

 

 


31、1*1卷积的作用?

1×1卷积的核心价值在于高效调整通道维度、融合跨通道信息并引入非线性,同时保持空间结构不变。它是现代CNN中实现轻量化、模块化设计的关键组件之一。

例如,选择2个1x1大小的卷积核,特征图的深度可以从3变成2;如果使用4个1x1的卷积核,特征图的深度可以从3变成4‌。

数学视角

  • 输入:尺寸为 [H, W, C_in] 的特征图。
  • 1×1卷积核:每个核的尺寸为 [1, 1, C_in],共 C_out 个核。
  • 输出:尺寸为 [H, W, C_out],每个输出通道是输入通道的线性组合:

通道维度调整(降维/升维)

  • 作用:通过改变输出通道数(即滤波器的数量),1×1卷积可以灵活调整特征图的通道维度。
  • 示例:
    • 降维:若输入为 [H, W, 256],使用64个1×1卷积核,输出变为 [H, W, 64],显著减少后续计算量(如Inception模块)。
    • 升维:增加通道数以提取更复杂的特征组合。
  • 优势:减少参数和计算量,提升模型效率。

残差连接的通道对齐

  • 问题:在残差网络(ResNet)中,输入和输出的通道数可能不一致,无法直接相加。
  • 解决:使用1×1卷积调整通道数,使残差分支与主分支通道匹配。
  • 示例:输入为64通道,残差块输出128通道,则1×1卷积将64→128通道。

 


32、空洞卷积是什么?

空洞卷积与普通卷积的主要区别在于引入了“扩张率”这一参数。扩张率定义了卷积核处理数据时各值之间的间距。当扩张率为1时,空洞卷积退化为普通卷积;当扩张率大于1时,卷积核的元素之间会按照扩张率所指定的间隔进行采样,从而扩大感受野‌。

优势‌:

  • ‌扩大感受野‌:在不增加参数数量的情况下,空洞卷积可以扩大感受野,有助于捕捉更广泛的上下文信息。
  • ‌保持分辨率‌:在语义分割等任务中,空洞卷积可以在保持特征图分辨率的同时增大感受野,减少信息损失。

‌局限性‌:

  • ‌网格效应‌:当扩张率过大时,可能会出现“网格效应”,导致一些像素点被重复采样,影响模型的性能。
  • ‌训练难度‌:空洞卷积的参数设置对模型性能有较大影响,需要仔细调整扩张率以获得最佳效果。

 


33、什么是分布式训练?

 分布式训练‌指利用‌多台设备(GPU/TPU)或多台机器‌协同训练同一模型,通过并行计算加速训练并处理超大规模数据与模型,其核心目标是‌提升训练效率‌与‌突破单机资源限制‌。

 ‌模型并行‌:将模型拆分到不同设备,各设备处理‌同一批数据的不同部分‌(如将Transformer层分配到多个GPU)‌


34、怎么保存模型?

PyTorch 模型保存‌

‌1. 保存完整模型(结构 + 参数)‌

  • ‌方法‌:使用 torch.save() 直接保存模型对象。
torch.save(model, 'model.pth')  
# 加载时直接还原模型  
model = torch.load('model.pth')  
  • 适用场景‌:快速部署或测试,但需确保加载环境与保存时的类定义一致。
  • ‌缺点‌:模型定义依赖代码,跨项目加载易失败‌13。

2.仅保存模型参数(推荐)‌

  • ‌方法‌:保存 state_dict,加载时需先实例化模型结构。
torch.save(model.state_dict(), 'model_weights.pth')  
# 加载时重建模型结构  
model = MyModel()  
model.load_state_dict(torch.load('model_weights.pth'))  
  • 优点‌:轻量且兼容性强,适合跨环境迁移‌

35、训练流程是什么?

  1. 训练模式初始化:设置训练模式和数据加载

  2. 批次训练循环:for遍历数据列表,迭代进行训练

  3. 前向推理阶段:在每个迭代中进行前向推理

  4. 损失计算阶段:根据定义,计算损失函数

  5. 反向传播优化:反向传播和参数更新

  6. 指标计算与日志记录

  7. 进度条更新 

 


36、监控什么性能指标?

性能监控要素

  • 数据时间:反映数据加载效率(I/O瓶颈)

  • 网络时间:反映模型计算效率(GPU利用率)

  • 指标实时显示:帮助快速判断收敛情况

 


37、模型测试流程?

环境配置与分布式初始化 → 模型构建与加载 → 执行评估→ 保存预测结果

流程总结

  1. 加载测试数据集

  2. 运行已有模型

  3. 计算评估指标(如F1-score、准确率)

  4. 生成可视化结果

  5. 保存预测结果

 


38、模拟运行流程?

核心流程:配置加载 → 模型初始化 → 数据准备 → 逐帧推理 → 结果可视化 → 视频生成

坐标转换公式

  • x = pred_loc * 网格宽度 * 原图宽度 / 800

  • y = row_anchor位置 * 原图高度 / 288


39、如何绘制点?

遍历所有有效车道点,通过cv2.circle绘制绿色圆点;

 PS:

  •  在运行模块,无需进入辅助分支,走主干分支进行识别即可;
  •  在视频或图像画图时,使用openCV实现;

40、如何绘制性能和收敛曲线?

 绘制训练过程中的‌损失曲线(Loss Curve)‌、‌准确率曲线(Accuracy Curve)‌等是分析模型性能与收敛性的关键步骤。

可以用matplotilb进行绘画

import pandas as pd
import matplotlib.pyplot as plt

data = pd.read_csv('training_log.csv')

plt.figure(figsize=(10, 4))

# 绘制损失曲线
plt.subplot(1, 2, 1)
plt.plot(data['Epoch'], data['Train Loss'], label='Train Loss')
plt.plot(data['Epoch'], data['Val Loss'], label='Val Loss')
plt.xlabel('Epoch'), plt.ylabel('Loss'), plt.legend()

# 绘制准确率曲线
plt.subplot(1, 2, 2)
plt.plot(data['Epoch'], data['Train Acc'], label='Train Acc')
plt.plot(data['Epoch'], data['Val Acc'], label='Val Acc')
plt.xlabel('Epoch'), plt.ylabel('Accuracy'), plt.legend()

plt.tight_layout()
plt.savefig('training_curves.png', dpi=300)
plt.show()

 


41、如何输出视频?

通过 cv2.VideoWriter() 函数,可以选择输出的格式编码、帧率、分辨率等。

 


42、弯曲车道是怎么通过变换到直线车道的?

通过多项式拟合或透视变换生成连续车道线,或采用注意力机制增强关键区域。

坐标变换(逆透视变换/IPM)‌

将原始前视图像‌投影到鸟瞰图空间‌,使弯曲车道在俯视视角下近似直线:

  • ‌实现方式‌:通过标定相机内外参数,建立透视变换矩阵,将图像从车辆坐标系映射到俯视平面坐标系‌
  • ‌效果‌:弯曲车道线在鸟瞰图中呈现平行或近似直线形态,降低后续检测复杂度‌

43、损失函数的连续性和光滑性有什么区别?

1. ‌连续性约束‌

  • ‌定义‌:确保车道线在空间分布上的连贯性,避免相邻预测点之间出现断裂或突变
  • ‌数学表达‌:通常通过约束相邻位置的预测结果差异实现。例如,在基于行(Row Anchor)的方法中,通过计算相邻行上车道线位置的相似性损失,强制同一车道线的垂直方向分布平滑过渡‌
  • ‌应用场景‌:
    • 垂直方向(图像中的行方向):约束相邻行上车道线预测的偏移量差异,防止车道线在纵向出现跳跃‌
    • 水平方向(图像中的列方向):约束同一车道的横向位置变化幅度,减少因遮挡或光照导致的预测抖动‌

2. ‌光滑性约束‌

  • ‌定义‌:保证车道线几何形状的平滑性,抑制曲率突变或非物理的弯折
  • ‌数学表达‌:常通过高阶导数或几何先验建模实现。例如,利用车道线曲率变化的一致性约束,或通过多项式拟合强制曲线平滑‌
  • ‌应用场景‌:
    • 弯曲车道:通过约束相邻点的曲率差异,减少急转弯场景下的异常弯折‌
    • 复杂道路拓扑:结合道路的物理先验(如车道线曲率连续性),提升三维车道检测的几何合理性‌

3. ‌实际应用中的协同作用‌

  • 二者常联合使用以实现互补:连续性约束确保车道线‌物理存在‌的合理性,而光滑性约束优化其‌几何形态‌的合理性‌
  • 例如,在弯道检测中,连续性损失可避免车道线断裂光滑性损失则抑制曲率突变,从而提升复杂场景的检测鲁棒性‌

通过上述约束,模型能更好地利用车道线的结构化先验,显著提升检测结果的准确性和物理合理性‌。

 


44、本项目中如何进行数据增强?

数据增强:由于车道的固有结构,基于分类的网络很容易过度拟合训练集,并在验证集上表现出较差的性能。为了防止这种现象并获得泛化能力,采用了一种由旋转、垂直和水平偏移组成的增强方法。此外,为了保持车道结构,车道延伸到图像的边界

  • 旋转
  • 水平、垂直偏移
  • 车道线延申

45、后处理阶段干了什么?

后处理:曲线拟合,生成车道线方程和可视化结果。


46、曲线拟合是如何在项目中具体实现的?

曲线拟合‌通过将离散的车道线像素点映射为连续数学曲线,实现几何特征的精确建模。

# 使用NumPy进行多项式拟合
coefficients = np.polyfit(x_points, y_points, deg=2)

 


47、训练模型时采用了哪些超参数?这些参数是如何选择的?

学习率(Learning Rate)‌

  • 初始值设置为 10−3103 至 10−4104,通过验证集观察损失收敛情况动态调整‌

设置epoch=50,batch_size=32,其它参数保持默认

  • epoch是指模型在训练过程中遍历整个训练数据集一次的过程。(如果训练数据集包含 10,000 个样本,那么一个 Epoch 就是模型在这 10,000 个样本上完成一次 前向传播(forward pass) 和 反向传播(backward pass) 的过程)
  • batch_size表示单次传递给程序用以训练的参数个数或数据样本个数;
  • 优化器(optimizer)通过调整模型参数以最小化损失函数,决定了神经网络在给定数据上的学习效率和效果;
  • 权重衰减(weight decay)是最广泛使用的正则化的技术之一, 它通常也被称为L 2正则化。它通过函数与零的距离来衡量函数的复杂度;
  • Momentum(动量)是机器学习中一种常用的优化算法,用于加快模型收敛速度并减少震荡。
  • 设置神经网络网格数量=200;
  • 设置输出日志的目录 ‘./log’ 

 


48、在项目实施过程中,你遇到了哪些技术挑战?是如何解决的?

(1)部分遮挡与模糊场景处理

‌挑战‌:前车遮挡、路面污渍等导致车道线断裂,传统方法无法恢复完整轨迹‌14。
‌解决方案‌:

  • 设计 ‌双分支网络架构‌:主干网络提取全局特征,辅助分支聚焦局部细节,通过特征融合补偿遮挡区域信息‌

(2)实时性要求与计算资源限制

  • 设计行锚点选择机制‌:仅在垂直方向预设的行锚点上进行预测,计算量降低
  • 实现多线程并行处理‌:将图像预处理、模型推理、后处理三个阶段部署在不同计算单元(CPU/GPU/DSP)‌

49、你认为车道线检测技术未来的发展方向是什么?

  • 端到端深度学习架构的持续优化
  • 实时性与精度平衡
  • 高精地图实时更新‌:将检测结果与高精地图动态匹配,实现车道线拓扑结构的在线修正
  • 智能驾驶

50、对于多模态融合和端到端架构等未来趋势,你有什么看法或计划?

  • ‌隐式控制优化‌:通过强化学习训练端到端控制策略,在仿真环境中实现方向盘转角预测误差≤1.5°‌
  • ‌在线蒸馏机制‌:部署教师-学生网络架构,实时压缩多模态模型至车载芯片可运行规格
  • 高精地图动态融合‌:开发在线地图更新算法,实现检测结果与高精地图的亚米级实时匹配

51、在准备这个项目的过程中,你阅读了哪些相关文献?有哪些文献对你的研究产生了重要影响?

‌关键文献‌‌核心贡献‌‌项目应用‌
Ultra Fast Lane Detection‌
提出行锚点预测机制,实现98.7%的检测精度与230FPS处理速度 模型架构优化的核心参考,参数量减少68%
LaneNet‌
首创基于实例分割的车道线检测框架,解决多车道实例分离问题 多车道语义分割模块设计基础
SCNN‌
引入空间卷积模块,增强车道线空间连续性建模 遮挡场景下的特征补偿机制原型

 

 


52、辅助分支和主干分支分别使用什么技术进行实现?CNN吗?

主干分支:基于ResNet提取多尺度特征,将主干特征映射为车道线预测结果

即输入 x → conv1(卷积) → bn1(归一化) → ReLU(激活函数) → maxpool(最大池化) → layer1 → layer2 → layer3 → layer4 → 输出 (x2, x3, x4)

辅助分支:使用空洞卷积‌扩大感受野,增强分割精度‌,主要学习车道线的局部细节特征,并通过上采样和拼接融合多尺度特征

"""
继承Pytorch中的ResNet网络
重写初始化和前向传播函数
改变网络结构,去除全连接层
主要为了实现:多任务特征提取(如检测、分割)
"""
class resnet(torch.nn.Module):
    #通过 layers 参数动态加载  ResNet 变体
    #使用 pretrained=True 加载 ImageNet 预训练权重,增强特征泛化能力‌
    def __init__(self,layers,pretrained = False):
        super(resnet,self).__init__()
        if layers == '18':
            model = torchvision.models.resnet18(pretrained=pretrained)
        elif layers == '34':
            model = torchvision.models.resnet34(pretrained=pretrained)
        elif layers == '50':
            model = torchvision.models.resnet50(pretrained=pretrained)
        elif layers == '101':
            model = torchvision.models.resnet101(pretrained=pretrained)
        elif layers == '152':
            model = torchvision.models.resnet152(pretrained=pretrained)
        elif layers == '50next':
            model = torchvision.models.resnext50_32x4d(pretrained=pretrained)
        elif layers == '101next':
            model = torchvision.models.resnext101_32x8d(pretrained=pretrained)
        elif layers == '50wide':
            model = torchvision.models.wide_resnet50_2(pretrained=pretrained)
        elif layers == '101wide':
            model = torchvision.models.wide_resnet101_2(pretrained=pretrained)
        else:
            raise NotImplementedError
        
        self.conv1 = model.conv1
        self.bn1 = model.bn1
        self.relu = model.relu
        self.maxpool = model.maxpool
        self.layer1 = model.layer1
        self.layer2 = model.layer2
        self.layer3 = model.layer3
        self.layer4 = model.layer4

    """
    保留原始 ResNet 的 conv1、bn1、relu、maxpool 及前 4 个残差块(layer1~layer4),去除全连接层,仅作为特征提取器‌
    前向传播返回layer2、layer3、layer4的输出(x2、x3、x4),提供多尺度特征图‌
    即输入 x → conv1(卷积) → bn1(归一化) → ReLU(激活函数) → maxpool(最大池化) → layer1 → layer2 → layer3 → layer4 → 输出 (x2, x3, x4)
    """
    def forward(self,x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
        x = self.layer1(x)
        x2 = self.layer2(x)
        x3 = self.layer3(x2)
        x4 = self.layer4(x3)
        return x2,x3,x4
"""
Conv2d + BatchNorm2d + ReLU激活函数,用作基础卷积单元‌
实现了一个标准卷积块,用于特征提取。通过组合卷积、批归一化和激活函数,提升模型训练稳定性和特征表达能力。
"""
class conv_bn_relu(torch.nn.Module):
    def __init__(self,in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1,bias=False):
        super(conv_bn_relu,self).__init__()
        #输入通道、输出通道、卷积核大小
        #bias=False:由于后续接BN层,可省略卷积偏置参数以提高计算效率‌
        #dilation:支持空洞卷积,可控制卷积核的间隔采样‌
        self.conv = torch.nn.Conv2d(in_channels,out_channels, kernel_size, 
            stride = stride, padding = padding, dilation = dilation,bias = bias)
        self.bn = torch.nn.BatchNorm2d(out_channels)
        self.relu = torch.nn.ReLU()
    #严格执行卷积→归一化→激活的顺序,避免梯度异常‌
    def forward(self,x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

"""
parsingNet主网络结构:
    Backbone:使用ResNet作为特征提取主干网络(支持不同深度如18/34/50),输出三个层次的特征(x2, x3, fea)。
    输入尺寸:默认288x800,经过ResNet下采样32倍后,最终特征图尺寸为9x25(假设ResNet输出层为1/32下采样)。
    输出维度:cls_dim=(37,10,4),对应网格划分数、每个车道的行锚点数和车道数。总维度37*10*4=1480。
    
主干网络‌:基于ResNet提取多尺度特征(如x2, x3, fea),支持不同深度(如backbone='50')和预训练权重加载‌。
‌辅助分支‌(use_aux=True时启用):通过多层级特征融合实现分割任务,提升模型鲁棒性‌。
‌分类分支‌:将主干特征映射为车道线预测结果,输出维度由cls_dim定义‌
"""
class parsingNet(torch.nn.Module):
    """
    size‌;输入图像分辨率(宽×高),用于计算特征图尺寸和上采样比例,如288×800常用于自动驾驶场景‌。
    pretrained:控制是否加载ResNet预训练权重,利用迁移学习加速收敛‌。
    backbone:选择ResNet变体(如'50'、'34'),影响特征图通道数(如backbone='34'时使用较小通道数)‌。
    cls_dim:定义分类维度:(num_gridding, num_cls_per_lane, num_of_lanes),对应车道线预测的网格化编码结构‌。
    use_aux:是否启用辅助分割分支,通过多任务学习提升模型性能‌
    """
    def __init__(self, size=(288, 800), pretrained=True, backbone='50', cls_dim=(37, 10, 4), use_aux=False):
        super(parsingNet, self).__init__()

        self.size = size
        self.w = size[0]
        self.h = size[1]
        self.cls_dim = cls_dim # (num_gridding, num_cls_per_lane, num_of_lanes)
        # num_cls_per_lane is the number of row anchors
        self.use_aux = use_aux
        self.total_dim = np.prod(cls_dim)

        # input : nchw,
        # output: (w+1) * sample_rows * 4
        #主干网络:调用resnet()函数构建ResNet,输出多尺度特征x2, x3, fea,用于辅助分支和分类分支的特征提取‌
        self.model = resnet(backbone, pretrained=pretrained)
        """
        辅助分割分支(use_aux):
            结构:处理ResNet中间特征(x2, x3, fea),通过上采样和拼接融合多尺度特征。
            输出:通道数为cls_dim[-1]+1(4车道+背景),用于像素级车道分割,辅助主任务训练。
        """
        if self.use_aux:
            #层级特征融合‌:通过aux_header2、aux_header3、aux_header4处理不同层级的特征图,并上采样后拼接(torch.cat)‌
            self.aux_header2 = torch.nn.Sequential(
                conv_bn_relu(128, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(512, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            self.aux_header3 = torch.nn.Sequential(
                conv_bn_relu(256, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(1024, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            self.aux_header4 = torch.nn.Sequential(
                conv_bn_relu(512, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(2048, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            #空洞卷积‌:aux_combine使用dilation=2和dilation=4扩大感受野,增强分割精度‌
            self.aux_combine = torch.nn.Sequential(
                conv_bn_relu(384, 256, 3,padding=2,dilation=2),
                conv_bn_relu(256, 128, 3,padding=2,dilation=2),
                conv_bn_relu(128, 128, 3,padding=2,dilation=2),
                conv_bn_relu(128, 128, 3,padding=4,dilation=4),
                #输出通道‌:cls_dim[-1] + 1,对应车道线类别数及背景类‌
                torch.nn.Conv2d(128, cls_dim[-1] + 1,1)
                # output : n, num_of_lanes+1, h, w
            )
            initialize_weights(self.aux_header2,self.aux_header3,self.aux_header4,self.aux_combine)

        """
        分类分支(self.cls)‌
            ‌特征池化‌:self.pool通过1×1卷积压缩通道数(如2048→8),减少计算量‌。
            ‌全连接层‌:将特征扁平化后映射到total_dim,最终输出形状为cls_dim的多维预测结果‌
        """
        self.cls = torch.nn.Sequential(
            torch.nn.Linear(1800, 2048),
            torch.nn.ReLU(),
            torch.nn.Linear(2048, self.total_dim),
        )

        self.pool = torch.nn.Conv2d(512,8,1) if backbone in ['34','18'] else torch.nn.Conv2d(2048,8,1)
        # 1/32,2048 channel
        # 288,800 -> 9,40,2048
        # (w+1) * sample_rows * 4
        # 37 * 10 * 4
        initialize_weights(self.cls)

    def forward(self, x):
        # n c h w - > n 2048 sh sw
        # -> n 2048
        # 主干网络提取特征
        x2,x3,fea = self.model(x)
        ## 辅助分支处理
        if self.use_aux:
            x2 = self.aux_header2(x2)
            x3 = self.aux_header3(x3)
            x3 = torch.nn.functional.interpolate(x3,scale_factor = 2,mode='bilinear')
            x4 = self.aux_header4(fea)
            x4 = torch.nn.functional.interpolate(x4,scale_factor = 4,mode='bilinear')
            aux_seg = torch.cat([x2,x3,x4],dim=1)
            aux_seg = self.aux_combine(aux_seg)
        else:
            aux_seg = None
        ## 主分支分类
        fea = self.pool(fea).view(-1, 1800)

        group_cls = self.cls(fea).view(-1, *self.cls_dim)

        if self.use_aux:
            return group_cls, aux_seg

        return group_cls

 


53、辅助分支如何使用空洞卷积来扩大感受野,提升分割精度?空洞卷积是怎么实现的?

(1)空洞卷积原理

 空洞卷积(Dilated Convolution),也称为扩张卷积或膨胀卷积,是一种通过引入“间隔”来扩大感受野的卷积操作,常用于深度学习中的计算机视觉和序列建模任务。

基本原理‌

  • ‌普通卷积‌:卷积核在输入数据上连续滑动,每个位置的参数紧密相邻。
  • ‌空洞卷积‌:在卷积核的元素之间插入“空洞”(间隔),从而覆盖更大的区域,同时保持参数数量不变。
    • ‌膨胀率(Dilation Rate)‌:控制间隔大小的参数。例如,膨胀率为2时,3×3的卷积核实际覆盖的区域相当于5×5的普通卷积(如下图)。
    • ‌感受野(Receptive Field)‌:空洞卷积显著扩大感受野,捕捉更广范围的上下文信息。

核心优点‌

  • ‌扩大感受野‌:不增加参数或计算量,即可覆盖更大的输入区域。
  • ‌保持分辨率‌:无需下采样(如池化操作),保留高分辨率特征图,适合密集预测任务(如语义分割)。
  • ‌参数高效‌:参数数量与普通卷积相同,但能捕获更全局的信息。

潜在问题与解决方案‌

  • ‌网格效应(Gridding)‌:当膨胀率过大时,卷积核过于稀疏,可能丢失局部细节。
    • ‌解决方案‌:组合不同膨胀率的卷积层(如混合膨胀率或渐进式膨胀率)。
  • ‌边缘信息丢失‌:大膨胀率可能忽略局部特征。
    • ‌解决方案‌:结合普通卷积或跳跃连接(Skip Connection)补充细节。

(2)项目中空洞卷积的使用

空洞卷积的核心作用

    1. ‌保持分辨率扩大感受野‌
      空洞卷积通过控制‌膨胀率参数‌,在卷积核元素间插入空格,可在不降低特征图分辨率的前提下扩大感受野‌36。相比传统池化层(需先缩小再放大特征图),避免了因下采样造成的细节丢失问题‌3。

    2. ‌多尺度特征融合机制‌
      辅助分支中常采用‌多分支结构‌,通过组合不同膨胀率的空洞卷积层(如1×1、3×3、5×5),捕获车道线局部细节和全局上下文信息‌25。例如,RFB模块通过多分支池化和空洞卷积实现跨尺度特征增强,尤其提升小目标(如远距离车道线)的检测精度‌

具体实现方案

  1. ‌空洞卷积网络结构设计‌

    • ‌复合卷积层‌:将标准卷积与不同膨胀率的空洞卷积结合(如1×1标准卷积+3×3膨胀率2的空洞卷积),弥补空洞卷积可能导致的局部信息丢失‌5。
    • ‌级联结构‌:在辅助分支中堆叠多个空洞卷积层,逐级扩大感受野,例如使用膨胀率2→4→8的分层结构,覆盖更广的车道线连续性特征‌36。
  2. ‌特征融合与监督机制‌

    • ‌双路径融合‌:将辅助分支输出的多尺度特征与主干网络特征进行通道拼接或加权融合,增强对车道线弯曲、遮挡等复杂场景的适应性‌58。
    • ‌结构化损失约束‌:通过辅助分支输出车道线实例分割结果,结合加权交叉熵损失函数,强化车道线拓扑结构的一致性监督‌

实际效果验证

在CULane等公开数据集测试中,采用上述方法的模型整体F1值提升至74.9%,尤其在‌极端光照‌和‌严重遮挡‌场景下性能显著优于传统方法‌5。空洞卷积的引入使模型能更精准捕获车道线全局分布规律,同时降低计算成本(相比全卷积网络减少约30%参数量)‌

局部细节的捕获能力‌
在语义分割等任务中,空洞卷积通过以下方式增强局部细节学习:

    • ‌多尺度特征融合‌:不同空洞率的卷积层可并行提取多尺度特征(如ASPP结构),同时捕捉细粒度局部特征和大范围上下文信息‌38。
    • ‌高分辨率特征保留‌:空洞卷积不减少特征图尺寸,使得像素级预测任务(如边缘、纹理)能够直接利用高分辨率信息进行精细分类‌

(3)代码实现

"""
Conv2d + BatchNorm2d + ReLU激活函数,用作基础卷积单元‌
实现了一个标准卷积块,用于特征提取。通过组合卷积、批归一化和激活函数,提升模型训练稳定性和特征表达能力。
"""
class conv_bn_relu(torch.nn.Module):
    def __init__(self,in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1,bias=False):
        super(conv_bn_relu,self).__init__()
        #输入通道、输出通道、卷积核大小
        #bias=False:由于后续接BN层,可省略卷积偏置参数以提高计算效率‌
        #dilation:支持空洞卷积,可控制卷积核的间隔采样‌
        self.conv = torch.nn.Conv2d(in_channels,out_channels, kernel_size, 
            stride = stride, padding = padding, dilation = dilation,bias = bias)
        self.bn = torch.nn.BatchNorm2d(out_channels)
        self.relu = torch.nn.ReLU()
    #严格执行卷积→归一化→激活的顺序,避免梯度异常‌
    def forward(self,x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

 


54、pytorch比起openCV、 transformer等其他框架有什么好处?

  •  自动微分与优化器‌:PyTorch内置自动求导功能(autograd),简化了反向传播流程,同时提供多种优化器(如Adam、SGD)‌36。OpenCV虽包含传统机器学习算法,但缺乏深度学习专用的训练工具。
  • GPU加速与分布式训练‌:支持多GPU并行计算和分布式训练,适用于大规模数据集和复杂模型(如大型Transformer)的训练加速‌;
  • 动态图适配复杂架构‌:Transformer依赖自注意力机制和可变长序列处理,PyTorch的动态图特性使其更易实现此类模型的动态调整(如修改多头注意力层)‌
  • 丰富的预训练资源‌:提供Transformer相关的高质量实现(如Hugging Face库),以及针对NLP任务的工具链(如TorchText),加速模型开发‌
  • 应用场景不同‌:OpenCV专注于图像处理(如图像滤波、特征提取),而PyTorch用于构建和训练深度学习模型。两者常结合使用(如用OpenCV预处理数据,PyTorch训练模型)。

 


55、主干分类分支和辅助检测分支是如何实现的?

主干分支:基于ResNet提取多尺度特征,将主干特征映射为车道线预测结果

即输入 x → conv1(卷积) → bn1(归一化) → ReLU(激活函数) → maxpool(最大池化) → layer1 → layer2 → layer3 → layer4 → 输出 (x2, x3, x4)

辅助分支:

  • 使用空洞卷积‌扩大感受野,获取更多的全局上下文信息;
  • 并将标准卷积与不同膨胀率的空洞卷积结合(如1×1标准卷积+3×3膨胀率2的空洞卷积),弥补空洞卷积可能导致的局部信息丢失‌,学习车道线的局部细节特征;
  • 最后与主分支的输出特征,通过上采样和拼接融合多尺度特征

56、图像数据的旋转、偏移、延申是怎么实现的?

使用NumPy,把图像数据构建为矩阵后,调用库里的函数进行计算;


57、项目中还有什么可以继续改进?

数据集多样性

  • ‌扩展数据集‌:目前项目主要使用CULane数据集进行训练和验证,虽然该数据集涵盖了多种道路场景,但进一步扩展数据集,包括更多天气条件(如雨雪、雾霾)、不同国家/地区的道路样式,以及更复杂的遮挡情况,将有助于提高模型的泛化能力。

模型优化

  • ‌轻量化设计‌:虽然目前的方法已经相对轻量化,但可以考虑进一步使用网络剪枝、量化等技术,以减少模型参数量和计算量,使其更适合在嵌入式设备上运行。
  • ‌注意力机制‌:引入注意力机制,使模型能够更关注图像中的重要区域,特别是在复杂场景下,有助于提高检测鲁棒性。

自监督学习

  • ‌利用未标注数据‌:考虑使用自监督学习方法,利用大量未标注的数据进行模型预训练,以降低对标注数据的依赖,并进一步提高模型的泛化能力。

 


58、感受野是什么?

 感受野(Receptive Field)是机器学习和计算机视觉中一个核心概念,尤其在卷积神经网络(CNN)中至关重要。它描述了网络中某一层神经元能“看到”的输入图像区域的大小,即‌每个特征点对应原始输入的空间范围‌

感受野的定义‌

    • ‌直观理解‌:
      假设你有一个5层卷积神经网络,最后一层的某个神经元可能对应输入图像中一个较大的区域(如100x100像素),而第一层的神经元可能只对应3x3像素的区域。这个“能看到的区域”就是感受野。
    • ‌数学定义‌:
      感受野是输入图像中对某一层特征图(Feature Map)中某个位置有影响的区域

感受野的计算‌

感受野的计算与卷积核大小、步长(Stride)、填充(Padding)和网络深度相关。以下是递推公式:

 

  • Fl:第ll层的感受野大小
  • klkl:第ll层的卷积核大小
  • sisi:第ii层的步长(Stride)
  • ‌初始条件‌:输入层的感受野RF0=1RF0=1

‌示例:简单3层卷积网络‌

层数卷积核大小步长感受野计算
输入 - - 1x1
第1层 3x3 1 1+(3−1)×1=31+(31)×1=3
第2层 3x3 2 3+(3−1)×1=53+(31)×1=5
第3层 3x3 1 5+(3−1)×(1×2)=95+(31)×(1×2)=9

最终,第三层神经元的感受野是‌9x9像素‌。

感受野的重要性‌

    • ‌特征抽象能力‌:
      浅层网络的感受野小,适合捕捉局部特征(如边缘、纹理);深层网络的感受野大,适合捕捉全局语义(如物体形状、整体结构)。
    • ‌模型设计依据‌:
      在设计目标检测、分割网络时,需确保感受野足够覆盖目标物体的大小。例如,检测大型物体需要更大的感受野。
    • ‌空洞卷积(Dilated Convolution)‌:
      通过扩大卷积核的间隔(空洞率),可以在不增加参数量的情况下扩大感受野。

 


59、主分支的分布分类算法,x = self.bn1(x),,归一化的含义是什么?
批量归一化(BN)的目的是通过对输入数据进行标准化处理,使得网络中间层的输入分布保持稳定,从而‌加速训练收敛‌、‌缓解梯度消失/爆炸‌,并‌提升模型泛化能力‌。

具体操作‌

假设输入特征图为 x,BN 的公式为:

 

其中:

  • μμ 和 σ2σ2:当前批次数据的均值和方差。
  • γγ 和 ββ:可学习的缩放和平移参数(保持模型表达能力)。
  • ϵϵ:防止除零的小常数(如1e-5)。
与激活函数的顺序‌:
  • 通常为 Conv -> BN -> ReLU,BN 的归一化操作应在非线性激活前完成。
在车道线检测模型中,self.bn1(x) 的归一化操作通过稳定特征分布、加速训练和提升泛化能力,间接提高了车道线分类的精度和鲁棒性。它是现代深度学习中不可或缺的“默认组件”,尤其在复杂多变的真实道路场景中,BN 的作用更加显著。
 

60、为什么使用交叉熵计算分类算法和分割算法的损失函数,用smoothL1计算车道线位置的损失函数?

1. 交叉熵损失(Cross-Entropy Loss)‌

‌适用场景‌:分类任务(如分割算法)

在车道线检测中,分割算法需要对每个像素进行分类(是车道线 or 不是车道线),本质上是‌二分类或多分类问题‌。交叉熵损失通过衡量预测概率分布与真实标签的差异,优化分类性能。

 

2. Smooth L1 损失(Smooth L1 Loss)‌

‌适用场景‌:回归任务(如车道线位置预测)

当需要预测车道线的具体位置(如横向偏移量、曲线参数或坐标点)时,这是一个‌连续值的回归问题‌。Smooth L1 损失通过最小化预测值与真实值的绝对差异,优化定位精度。

 

总结:

  • 交叉熵损失‌用于分类/分割任务,因为它直接优化概率分布匹配,适合离散标签的预测。
  • ‌Smooth L1 损失‌用于位置回归任务,因为它对数值误差的惩罚更鲁棒,且梯度稳定。
 
 
 
 
 
 
 
 
 
posted @ 2025-02-22 22:20  逍遥划水真君  阅读(547)  评论(0)    收藏  举报