☰
语义分割车道线检测:Python源码与实战解析
2026/10/5 3:57:22 网站建设 项目流程

简介:基于语义分割的车道线检测项目,面向计算机视觉、深度学习方向的学生、研究人员与工程师,用于自动驾驶场景下的车道线精准识别与分割。项目围绕Cascade结构、GCN与ERFNet等模型展开,包含完整的Python训练、测试与预处理代码,以及详细的README说明,可帮助读者快速掌握从数据准备、模型训练到效果评估的完整流程。压缩包共32个文件,以py源码、meta与data-00000-of-00001模型权重文件为主,另有png结果对比图、pyc编译缓存与bat构建脚本,约1.35MB,结构紧凑、便于下载实践。目前已有65人学习下载。通过实际运行训练脚本并查看不同阈值下的分割效果,读者可直观了解语义分割在车道线检测中的应用,并学习GCN、ERFNet等网络的设计思路与调优方法,为后续研究与工程落地打下基础。

1. 语义分割车道线检测:像素级分类为什么能治传统方法的顽疾

在ADAS和自动驾驶的感知模块里,车道线检测是基础但远未解决的问题:夜间行车、雨雪天气、车道线磨损,都会让传统视觉方案翻车。基于语义分割方法的车道线检测python源码+项目说明.zip 这个项目,核心是用语义分割算法把图像里每个像素都分类为“车道线”或“背景”,把检测问题转成密集预测任务。它和基于边缘检测、霍夫变换的老套路本质不同——不依赖线条连续性和直线假设,所以对弯道、破损线、强光阴影都有更好的鲁棒性。适合正在做ADAS感知的工程师、毕设或课程项目的学生,以及想用Python完整跑一遍语义分割落地流程的读者。

2. 为什么选语义分割做车道线:和传统方法、检测方法的本质差异

2.1 传统方法的局限:边缘检测和霍夫变换在弯道和阴影前失效

早年做车道线检测,最常见的一条技术路径是 Canny 边缘检测 + ROI 区域截取 + 霍夫变换直线拟合。这套组合在高速公路的白天场景里效果不错:车道线是一条连续的白色边缘,霍夫变换能稳定地找到对应直线,然后按斜率筛出左线右线,输出车道线方程。

但落地时问题会接连出现。第一,霍夫变换拟合的是直线模型,遇到弯道就直接失效——弯道需要分段拟合或者曲线模型,而曲线模型的参数空间更大、更容易受到噪声干扰。第二,边缘检测对光照极其敏感,路面阴影、树影、隧道出入口的强烈明暗交替,都会产生大量伪边缘,车道线本身反而被淹没。第三是路面磨损和裂缝:当车道线褪色、断裂时,边缘检测的结果是一段一段的,霍夫变换很难把它们拼成一条完整线。

这类传统方法本质上是“低层特征 + 几何假设”,它没有语义理解能力,不知道什么是车道线、什么是轮胎印。所以在实际测试里,白天高速公路可能做到可用,但一到夜间、雨雪或者逆光路段,误检率和漏检率会同时飙升,这是传统方案的结构性缺陷,调阈值救不回来。

2.2 语义分割模型选型:从FCN、UNet到轻量级ERFNet

语义分割的思路完全不同。它不依赖底层边缘,而是用深度网络学习“什么东西是车道线”——把每个像素都打上一个类别标签,这个类别标签是模型从大量标注数据里学出来的。经典的起点是FCN(Fully Convolutional Network),它把分类网络的全连接层换成卷积层,输出一个密集的像素分类概率图,那就是语义分割。

FCN语义分割解决了“像素级分类怎么做”的问题,但它的上采样方法(转置卷积)倍数固定,结果比较粗糙,容易出现棋盘效应。后来的UNet引入编码器-解码器结构和跳跃连接,把浅层的空间细节和深层的语义特征拼在一起,让分割边缘更干净,而且在小数据集上表现非常稳。DeepLab系列的贡献则是空洞卷积,在不大幅增加计算量的前提下扩大感受野。

选型建议上,如果项目是跑通流程、做验证和毕设,我一般先用UNet作为baseline,因为它结构直观、收敛快、显存占用不高,在TuSimple这种规模适中的数据集合上很容易得到看得过去的结果。如果追求在车载嵌入式平台上实时跑,那要换轻量级方案,比如ERFNet或者带MobileNet骨干的DeepLabV3+。ERFNet是为语义分割专门设计的残差结构,参数量小,推理速度快,在CULane这类大规模道路数据集上也有不错的表现。

2.3 标签设计:二分类还是多分类,通道数怎么定

语义分割车道线检测的标签设计有两种常见做法,决定了模型的输出通道数和服务场景。

第一种是二分类。标签是一张单通道掩码(mask),像素值为1代表车道线,0代表背景。模型输出shape是 (N, 1, H, W),经过Sigmoid激活后得到每个像素属于车道线的概率。这种方案实现最简、训练最快,模型只关心“线和背景”,但把所有车道线混成一类。

第二种是多分类或实例化的车道线分割。标签通道区分不同的车道线,比如当前车道左线、右线、相邻车道的线,输出shape是 (N, C, H, W),C为车道线类别数。这种方式能为后续的路径规划提供每条线的归属,但标注成本更高,模型训练也相对更难。还有一种思路是预测车道起点和像素嵌入向量做实例分割,不过对工程复杂度要求较高,入门项目里不推荐。

我通常建议先从二分类起步,把整体流程跑通后,再根据实际需求升级到多分类。注意一点:多分类的模型输出如果直接用Softmax,会把“背景”也当作一个类,最后一共是C+1个通道;而二分类用Sigmoid则更方便,但在Pixel-wise的交叉熵和Dice Loss里,二值分割更常见。下面章节围绕二分类掩码展开,因为这也是这个源码项目常见的最小配置。

2.4 把项目源码跑起来:环境配置与最小命令

拿到一个语义分割车道线的Python源码包,第一步不是读代码,是先看项目说明.md或requirements.txt。Python环境建议用3.8或3.9版本,PyTorch 1.8以上在CPU和GPU上都稳定可用。如果没有GPU,小尺寸输入下这个项目也能跑,只是训练慢一些,推理速度还是能接受的。

常见做法是用conda建一个干净环境,避免和系统Python打架:

conda create -n lane_seg python=3.8 conda activate lane_seg cd /path/to/lane-segmentation pip install -r requirements.txt

requirements.txt里通常包含torch、torchvision、opencv-python、numpy、albumentations、tqdm、pillow。这些依赖版本不敏感,按默认装一般不会冲突。装了以后,先检查数据集路径是否正确,运行数据准备脚本:

python prepare_data.py --dataset tusimple --root ./data/tusimple

这段脚本会把TuSimple原始json标注转成mask格式,每张训练图对应一张同尺寸的png掩码。跑完以后用tensorboard或直接看几张可视化文件确认掩码对齐,再进入训练流程。这里的--dataset和--root是脚本里最常见的两个参数:前者指定你用的是TuSimple还是CULane,后者指定原始数据解压后的根目录。

3. 数据准备:公开数据集、数据增强与Mask制作

3.1 公开数据集选哪个:TuSimple vs CULane

车道线检测公开数据集最常用的是TuSimple和CULane。我在落地时会优先选TuSimple作为起步。它的训练集包含3626张图像,全部来自高速公路场景,视角基本固定,路面干净,标签是以json格式存储的车道线点坐标,结构简单、解析容易。

CULane的规模要大得多,有88880张训练图,覆盖城市、夜间、雨天、拥堵等更复杂的场景,标签为单通道png掩码。虽然直接可用、更有挑战性,但训练需要更多算力和时间,而且掩码里已经涵盖了路面背景,对刚接触语义分割的人来说,可视化调试的难度反而更大。

所以跑通源码、验证语义分割思路,首选TuSimple;做比赛、做论文或被要求覆盖复杂场景再上CULane。另外,如果项目给了已经转换好的mask目录,就直接使用;没有则要自己转换。

3.2 数据增强:让模型扛住夜间和雨雪

语义分割车道线的训练里,数据增强是决定鲁棒性的关键。直接拿原图训练,模型看到的光照和场景分布很单一,到了夜晚、雨雪就翻车。增强的目标是模拟真实世界的多样性,常见的有几类:

  • 亮度/对比度抖动:模拟隧道出口、阴影遮盖
  • 随机裁剪与缩放:模拟相机安装位置不同
  • 高斯噪声和模糊:模拟雨雪天气导致的对焦或传感器噪声
  • 随机翻转:注意如果是多车道线的实例分割,翻转时要把标签也对应翻转

使用albumentations库可以一条管道完成这些操作,代码很清爽:

import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform = A.Compose([ A.RandomBrightnessContrast(p=0.6), A.RandomGamma(p=0.4), A.GaussNoise(p=0.3), A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=15, p=0.3), A.RandomCrop(height=256, width=512), A.HorizontalFlip(p=0.5), A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), ToTensorV2() ])

p是每张图应用该增强的概率,RandomCrop的高度和宽度是根据你训练输入的分辨率设的。这里注意HorizontalFlip对二分类mask没有影响,因为1还是代表车道线像素;如果标签是“左线、右线”分开的多通道,翻转后通道顺序要反过来,否则模型学习到的空间关系就错了。

Normalize用的是ImageNet的均值和标准差。你要是换了别的预训练模型,这组值基本不用动,它对数值稳定性只有好处。

3.3 标签制作:从标注json到mask的转换脚本

TuSimple的标签长这样:一个json里有lanes字段,它是一组坐标点列表,每个点是 (x, y),横坐标是每一条车道线的、纵坐标是等间距采样。要转成可供语义分割训练的mask,需要把这些离散点连线并填充成一条细线。

下面是一个转换脚本的核心逻辑:

import json import numpy as np import cv2 def tusimple_json_to_mask(json_path, height=720, width=1280, thickness=8): with open(json_path, 'r') as f: data = json.load(f) mask = np.zeros((height, width), dtype=np.uint8) if not data['lanes']: return mask for lane in data['lanes']: xs = np.array(lane) ys = np.array(data['h_samples']) valid = (xs >= 0) & (xs < width) if valid.sum() < 2: continue pts = np.stack([xs[valid], ys[valid]], axis=1).astype(np.int32) cv2.polylines(mask, [pts], isClosed=False, color=1, thickness=thickness) return mask

逻辑说明:lanes里每个元素是一条车道线的所有x坐标,h_samples是对应的等级间距y坐标,只有x不为负数的那个点才是真正被标注的点。我们用valid来过滤无效点,然后用cv2.polylines把这些点连成线,画到mask上,线宽thickness设为8像素左右——这个宽度是个超参数,太细(比如1像素)会让正样本数量太少,训练不稳定;太粗会模糊车道线边界,损失函数很难收敛到一个清晰的语义边界。一般来说5~10像素比较平衡。

注意,mask用np.zeros创建的是uint8数组,背景是0,车道线是1。如果你希望可视化时背景是黑、车道线是白,可以乘以255再存成png。训练时直接读取这个mask并转为float,当作类别真值标签使用。

4. 训练一个语义分割车道线模型:Python源码拆解

4.1 数据加载器:批量读取图像与mask

PyTorch的自定义数据加载器要把img和mask成对地读出来。注意:不能直接用OpenCV默认的方式读取mask,否则mask会变成三通道BGR或者被当作彩色图读入,导致维度不匹配。常规写法如下:

import os import cv2 import torch import numpy as np from torch.utils.data import Dataset class LaneDataset(Dataset): def __init__(self, img_dir, mask_dir, transform=None): self.img_dir = img_dir self.mask_dir = mask_dir self.transform = transform self.images = sorted([f for f in os.listdir(img_dir) if f.endswith('.jpg')]) def __getitem__(self, idx): img_path = os.path.join(self.img_dir, self.images[idx]) mask_path = os.path.join(self.mask_dir, self.images[idx].replace('.jpg', '.png')) img = cv2.imread(img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) mask = (mask > 0).astype(np.float32) if self.transform: transformed = self.transform(image=img, mask=mask) img = transformed['image'] mask = transformed['mask'] else: img = torch.from_numpy(img).permute(2, 0, 1).float() / 255.0 mask = torch.from_numpy(mask).unsqueeze(0).float() return img, mask

逻辑说明:cv2.imread返回的是BGR三通道,转成RGB是因为预训练模型通常在RGB上训练。mask用IMREAD_GRAYSCALE保持单通道,把255转为1.0,确保真值只有0和1。transform同时作用在图像和mask上,这是albumentations的优势——不会出现图像翻转了但mask没翻转的错位。ToTensorV2会自动把HWC转成CHW。如果不用transform,就手动permute加unsqueeze。

参数说明:mask的尺寸必须和img一致,albumentations的RandomCrop或Resize会保证这一点;如果数据加载时发现img和mask的高宽不一样,多半是标签没有与图像保持相同尺寸,先检查预处理阶段的resize逻辑。

4.2 模型构建:以UNet为例写一个可训练的语义分割网络

这里给出一个最简UNet实现,足够来演示语义分割车道线训练。解码器通过跳跃连接融合高分辨率的浅层特征,输出一个单通道的Sigmoid概率图。

import torch.nn as nn import torch class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv = nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), nn.Conv2d(out_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True) ) def forward(self, x): return self.conv(x) class UNet(nn.Module): def __init__(self, in_channels=3, out_channels=1): super().__init__() self.pool = nn.MaxPool2d(2) self.enc1 = DoubleConv(in_channels, 64) self.enc2 = DoubleConv(64, 128) self.enc3 = DoubleConv(128, 256) self.enc4 = DoubleConv(256, 512) self.center = DoubleConv(512, 1024) self.up4 = nn.ConvTranspose2d(1024, 512, 2, stride=2) self.dec4 = DoubleConv(1024, 512) self.up3 = nn.ConvTranspose2d(512, 256, 2, stride=2) self.dec3 = DoubleConv(512, 256) self.up2 = nn.ConvTranspose2d(256, 128, 2, stride=2) self.dec2 = DoubleConv(256, 128) self.up1 = nn.ConvTranspose2d(128, 64, 2, stride=2) self.dec1 = DoubleConv(128, 64) self.out = nn.Conv2d(64, out_channels, 1) def forward(self, x): e1 = self.enc1(x) e2 = self.enc2(self.pool(e1)) e3 = self.enc3(self.pool(e2)) e4 = self.enc4(self.pool(e3)) c = self.center(self.pool(e4)) d4 = self.dec4(torch.cat([self.up4(c), e4], dim=1)) d3 = self.dec3(torch.cat([self.up3(d4), e3], dim=1)) d2 = self.dec2(torch.cat([self.up2(d3), e2], dim=1)) d1 = self.dec1(torch.cat([self.up1(d2), e1], dim=1)) return torch.sigmoid(self.out(d1))

逻辑说明:编码器部分通过不断的池化把图像下采样到原图的1/16,同时通道数升到1024。解码器通过转置卷积还原空间分辨率,再与对应编码器的特征图做通道拼接,这就是跳跃连接。最后输出层的卷积把通道降到1,接Sigmoid映射到0到1。

参数说明:初始通道数为64,这个值决定了模型容量和显存占用。显存不够时把64改成32,空间分辨率输入512x256,总参数和显存会下降很多。out_channels为1,对应二分类mask。如果要预测多类,把out_channels改成类别数,输出层后面换成Softmax。

4.3 损失函数与超参数:训练手上的三个必调参数

二值语义分割最常用的损失函数是BCE,但在车道线这种前景稀疏的场景下,BCE会让模型倾向于把所有像素都预测为背景,mIoU上不去。我通常的做法是用BCE + Dice的组合,Dice对前景像素的梯度贡献更大,能有效对抗类别不平衡。另一个选项是Focal Loss,让模型更关注难分类的前景像素。

损失函数实现:

def dice_loss(pred, target, smooth=1.0): pred = pred.flatten(1) target = target.flatten(1) intersection = (pred * target).sum(dim=1) dice = (2. * intersection + smooth) / (pred.sum(dim=1) + target.sum(dim=1) + smooth) return 1 - dice.mean() def bce_dice_loss(pred, target): bce = nn.BCELoss()(pred, target) dice = dice_loss(pred, target) return bce + dice

逻辑说明:pred期望是Sigmoid输出。bce_dice把交叉熵和dice相加,smooth防止分母为零,是一个平滑项,通常取1.0。flatten(1)将NCHW中每个样本压成一行,目的是让dice按样本分别计算再取平均,这样小批次里如果某一张图没有车道线,也不会拉垮整体损失。

训练超参数方面,三个必调的参数是:学习率、batch_size、训练轮数。学习率用Adam时取1e-4起步,比较稳;用SGD则初始0.01,配合余弦退火。batch_size取决于显存,一般8~32。对UNet和512x256的输入,16的batch要显存约8GB,不够就减半。训练轮数:TuSimple上30~50个epoch足够,过度训练会把测试集loss拉低但泛化反而变差,建议每个epoch结束后在验证集上算mIoU,保存验证集最优的checkpoint。

import torch device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = UNet().to(device) optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=15, gamma=0.5) dataset = LaneDataset(img_dir='data/tusimple/train', mask_dir='data/tusimple/train_mask', transform=train_transform) dataloader = torch.utils.data.DataLoader(dataset, batch_size=16, shuffle=True, num_workers=4, drop_last=True) for epoch in range(40): model.train() train_loss = 0.0 for images, masks in dataloader: images, masks = images.to(device), masks.to(device) optimizer.zero_grad() preds = model(images) loss = bce_dice_loss(preds, masks) loss.backward() optimizer.step() train_loss += loss.item() scheduler.step() print(f'epoch {epoch} | loss {train_loss / len(dataloader):.4f}')

逻辑说明:这个训练循环里,零梯度、前向、计算损失、反向、更新,是PyTorch标准流程。scheduler在每15个epoch把学习率衰减到原来的一半,让优化后期不会在最优解附近震荡。drop_last=True是因为如果最后一批少于batch_size,BN层统计期望会偏。

5. 语义分割车道线检测避坑指南:现象、原因与解决方案

5.1 现象1:loss下降但mIoU不涨,车道线全变成背景

训练时经常看到BCE一直在降,但验证集mIoU停在0.2以下,输出预测图几乎全黑。原因是车道线像素占比太小,比如在1280x720里一条8像素宽的车道线面积占比不到2%,BCE的梯度被背景主导,模型收敛到一个全是背景的局部最优。

解决方法是换损失。bce+dice或focal loss都能显著提升前景的梯度权重。另一个补救手段是给前景加权,在BCE里给正样本乘以一个较大的权重(比如10~20)。但dice更省事,因为它天然对类别比例不敏感。我自己的经验是,遇到这个现象先别急着加网络层、加数据,先看一眼训练集掩码里的前景像素占比,如果低于5%,直接换bce+dice组合。

5.2 现象2:白天效果好、晚上翻车,或者隧道场景失灵

如果你的人工数据集全是白天拍的,增强没有覆盖亮度区间,模型在夜间就会翻车——输出大量噪点或干脆漏检。语义分割模型十分看重数据分布,它不是“看到轮廓就知道是线”,而是学到了“大概是这个亮度对比度下的白色区域才是线”。

解决方向有两层。增强层:在训练集里加RandomBrightnessContrast、RandomGamma,亮度下限拉到50%。数据层:混入夜间或弱光数据。如果实在拿不到,可以把白天图像用Gamma变换人为压暗,模拟夜晚曝光。注意验证集也要包含弱光样本,否则你测不出模型是否真的增强了夜间鲁棒性。

5.3 现象3:车道线断成碎段,即使训练loss很好

预测输出的mask上,车道线不连续,一条完整的线中间有缺口。原因是语义分割是逐像素独立预测,本身没有线条连续性的约束。要想得到工程可用的线,必须做后处理。

常见做法是:先对概率图做阈值二值化(阈值0.5),再做形态学闭运算填充小间隔,然后做骨架提取保留主结构,最后按同一行的峰值或聚类成若干车道线。闭运算的kernel大小建议是5×5或7×7,太大会把背景噪声也焊接成线,太小又填不上gap。这一步的实现质量会直接影响最终F1分数,多花时间调试是值得的。

更高级的缓解方式是把语义分割与其他结构信息结合,例如SCNN或LaneNet里加入消息传递、实例嵌入,但复杂度很高。项目里先搞定后处理再考虑模型升级。

5.4 现象4:推理延迟高,车载平台上跑不动

UNet在GPU上推理一张512×256图像大约30~60ms,边缘设备上更慢。语义分割模型常被视为“黑匣子”,参数量大、算力要求高,这是许多项目难以落地的主要阻碍。

排查维度有两个。一是输入分辨率:车道线需要的精度并没有ImageNet级别那么高,把输入缩到256×128甚至更低,推理速度提升非常明显,mIoU损失有限。二是换轻量模型:ERFNet的参数量比UNet少一个数量级,语义分割精度仍不错;或者给UNet换MobileNetV3的encoder,配合ONNX导出和TensorRT的FP16量化,在Jetson平台上可以跑到15ms/帧以内。

还有一个老坑:批量推理时,模型要设置成eval模式并关掉梯度,否则batch norm和dropout行为错误,推理延迟和结果都会有问题。常见错误是忘了model.eval()和with torch.no_grad(),后者会一直创建计算图导致显存泄漏。

6. 进阶用法:后处理把Mask变成车道线,再用IoU做回归验证

6.1 后处理:阈值、闭运算与多项式拟合

预测得到的概率图是HxW的float数组,先做阈值得到二值mask,然后用形态学闭运算把碎段连通起来。接着在垂直方向按行扫描,找出每行概率最大的峰值点作为该行的车道线位置,这一步比单纯转二值图更能抗噪,尤其是两条线距离比较近的时候。最后把点按x坐标聚类成N条线,再用np.polyfit做二次多项式拟合,得到每条车道线的方程。

import cv2 import numpy as np def prob_map_to_lane(prob, height, width): mask = (prob > 0.5).astype(np.uint8) * 255 kernel = cv2.getStructuringElement(cv2.MORPH_CLOSE, (7, 7)) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) lane_pts = [] for row in range(0, height, 5): line = mask[row, :] if line.sum() == 0: continue col = np.argmax(line) lane_pts.append((col, row)) pts = np.array(lane_pts) if len(pts) < 3: return None coeffs = np.polyfit(pts[:, 1], pts[:, 0], deg=2) return coeffs

逻辑说明:mask是单通道的0/255图像,MORPH_CLOSE内核7×7把所有相邻的车道线碎片连通;然后每隔5行采样一行,取该行最大的值对应的列坐标,作为该行的线中心;把点对(x,y)收集起来用二次多项式拟合,因为大部分高速路段都可以用二次曲线近似。

参数说明:阈值0.5是常用默认值,如果漏检较多,降到0.4试试,但要接受更多的噪声;按行采样步长5,既能减少噪声点又保留了曲线细节。拟合时deg=2已经足够,deg=3容易过拟合短线段。

6.2 效果验证:算一下IoU和F1分数

训练完只看几张可视化图,跟用肉眼看一两张图差不多,对说服自己和审计都很单薄。工程上给这个项目的验证至少包括两部分。

第一部分是区域IoU。把预测的mask和真实mask比较:

def compute_iou(pred_bin, true_bin): pred_bin = pred_bin > 0.5 true_bin = true_bin > 0.5 inter = (pred_bin & true_bin).sum() union = (pred_bin | true_bin).sum() return inter / union

pred_bin传入的应是阈值化后的布尔数组,true_bin是真实掩码。注意union可能为零,无法计算IoU,先过滤掉完全空白的测试图,否则会出现除零错误。

更常用的是与车道线坐标相关的指标。TuSimple官方评测里,每个采样行若预测x与真实x偏差小于某个阈值(如10像素)则算该行检测正确,最终统计所有行的正确率、漏检率和误检率,合成F1。要得到这类指标,就得把拟合后的曲线在采样行上插值回x坐标,再与h_samples比对。一般写一个evaluate.py,对测试图逐一推理、解析、比对,最后打印报告。

我习惯把每个实验都跑通这个evaluate再往下一步走。很多项目看似都跑了,效果却经不起复现,就是因为缺了这条量化评价的线。只有当你把mask、模型推理和后处理三个环节分别都量化出来,才有把握说一个改动到底是提升了还是心安理得。

最后提一句:做项目时尽量早把“对测试集做一次雨雪或黑夜增强后的评估”当成标准流程,它能暴露你在训练时运气好的阶段。希望帮到你。

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

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

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

立即咨询