PolyLaneNet:基于深度多项式回归的车道估计(PolyLaneNet: Lane Estimation via Deep Polynomial Regression)

官方源代码:https://github.com/lucastabelini/PolyLaneNet

摘要

车道线检测要求实时性(>=30FPS)。

引言

车道线检测很重要巴拉巴拉。。。
车道线检测分为传统方法(手工特征提取+曲线拟合)和深度学习方法。深度学习方法能够更鲁棒,但仍有一些问题需要解决:

  1. 许多方法将车道线检测作为一个two-step的过程:feature extraction and curve fitting。

(但大多数方法都是通过基于分割的模型来提取特征的,不具有时效性。单独的分割步骤不足以提供车道标记估计,因为分割地图必须经过后处理,以输出交通线路。)
(可能忽略global information;但全局信息在有遮挡和阴影时是十分重要的)

  1. 不公开方法、源代码、数据集等。
  2. 评估方案仍有改进的余地。

(只用来自美国的数据集;评估指标过于宽松)

因此本文方法对上述问题进行了改进:

  1. 一步到位(端到端)
  2. 美国以外的道路测试
  3. 评价标准更加严格
  4. 提供源代码和训练好的模型

相关工作

没啥东西,可能不公开源代码和数据集就是在“耍流氓”。

POLYLANENET

模型定义

输入
单目相机采集到的图片

输出
Mmax:候选车道线(以多项式表示)
h:垂直高度(限制车道线标记的上限,所有车道线共享)
对每个车道线j的偏移量sj
预测的置信度cj∈[0; 1]

网络组成
backbone network:用于提取特征
fully connected layer输出:Mmax + 1个
(第1; ……;第Mmax:用于车道线标记预测)
( 第Mmax + 1:用于输出h)
由于PolyLaneNet采用多项式来代表车道线而不是用一系列点,因此,对每一个输出j,j = 1;……;Mmax,模型估计的是多项式的系数。

系数:在这里插入图片描述
车道线多项式:在这里插入图片描述
K:多项式的次数

模型可表达如下
在这里插入图片描述
其中I是输入图像;θ是模型参数;当置信度大于给定阈值时才认为检测到了车道线。
在这里插入图片描述

模型训练

对于给定图片,M是图中标注好的车道线数量。一般来讲,M<=4满足了大多数交通场景的要求。神经单元j代表了车道线j(j = 1;……;M)。因此第M+1到Mmax个输出在损失函数中应该不予考虑。
一个标注好的车道线j由一系列点表示,如下式所示:
在这里插入图片描述
其中,
在这里插入图片描述
对每一个i=1;……;N-1。
根据经验法则,N的越高,表达的结构更丰富。
我们假设车道线标记在这里插入图片描述
的排序规则如下:根据图像底部的点的x坐标。即
在这里插入图片描述
对每一个车道线标记j,垂直偏移量在这里插入图片描述
被设定为
在这里插入图片描述
置信度定义如下:
(如果车道线比M小,置信度为1,否则是0)
在这里插入图片描述
对于一个单一图像,多任务损失函数定义如下:
在这里插入图片描述(Lcls:二分类损失。表示车道线是否被检测到。
两个Lreg:MSE损失。表示车道线下限/上限和真实下限/上限之间的距离。下限是单个的,上限是共享的
Lp({PJ},{Lj}:其中{Lj}是ground-truth的一堆点;{PJ}是预测的多项式映射之后的一堆点。)
其中系数Wp,Ws,Wc,Wh用于平衡权重,回归Lreg和Lcls分别是MSE和BCE函数。Lp损失函数表示了衡量了多项式pj(等式1)对车道线点的表示程度。
考虑到标注的x坐标在这里插入图片描述(ground-truth)
在这里插入图片描述(predict)
其中,
在这里插入图片描述(如果预测的点已经很接近真实点(<τloss),那么xi,j=0;否则xi,j=pj(y*i,j))。
(这里不是很懂为什么要是0,如果是0,那么下面MSE误差不是大了吗?)

其中,在这里插入图片描述
是一个经验阈值,可以减少对已经很好地对齐的点的损失的考虑。

这种效应的产生是因为车道标记包含了几个具有不同采样差异的点
(例如,靠近相机的点比远离相机的点密度大)。

Lp定义如下(计算MSE):
在这里插入图片描述

实验方法论

A.数据集

三个:TuSimple、LLAMAS、ELAS。

TuSimple数据集用于定量分析。(有6,408张标注好的数据集,像素是1280x720)
(3,268张用于训练, 358张用于验证,2,782张用于测试)

LLAMAS和ELAS用于定性分析。LLAMAS有58,269张用于训练,20,844张用于验证,20,929张用于测试)像素是1280x717。

TuSimple和LLAMAS数据集来自美国,由于对于LLAMAS的基准和测试集注释都还不可用,所以只给出定性的结果。

ELAS数据集采集自Brazil的不同城市。分辨率是640x480;有16,993张。由于数据集最初是为非基于学习的方法提出的,它不提供训练/测试分割。因此我们分割该数据集为11,036张用于训练,5,957张用于测试。
ELAS数据集不同于其他二者,ELAS只有自我车道被标记,数据集还为车道检测任务提供了其他类型的有用信息,如车道类型(例如实线或虚线,白色或黄色),但在本文中没有使用。

B.实验细节

骨架网络:EfficientNet-b0
在TuSimple训练中,数据增强的应用概率为10/11。具体如下:
旋转角度:在这里插入图片描述
以0.5的概率水平翻转。
随即裁剪到1152x648像素。

数据增强之后,应用了下列转换:

  1. resize到640x360
  2. normalization(以ImageNet的均值和标准差)
  3. Adam优化器
  4. 以及具有3e-4初始学习速率和770个epochs的余弦退火学习速率调度程序

训练部分跑了2695 epochs,在Titan V上用了35个小时。
batch size=16
在经过ImageNet预训练的网络上进行训练。
三阶多项式选为默认值。
超参数选择为:
Ws = Wc = Wh = 1;Wp = 300 (调出来的)
τloss=20pixels

在测试阶段,车道线标记的置信度cj<0.5的被忽略。

C.评价指标

来自TuSimple’s benchmark。
accuracy (Acc), false positive (FP) and false negative (FN) rates。

一个预测的车道线标记满足如下条件则被认为是true positive(即正确):
在这里插入图片描述
Acc>=ε
其中,τloss=20pixels;ε=0.85。

每条车道线计算上述三个指标后,每幅图像平均;所有图像再平均。

但上述三个指标不够严格,我们进一步采用下述指标:
Lane Position Deviation(LPD):更好地捕捉模型对自车远近视角的精度。这是对自我车道线的预测和groundtruth误差。

ego-lane:靠近图像底部中间部分的车道标记是组成ego车道的标记,即一个车道标记在左侧,另一个车道标记在右侧。

还有两个与速度相关的指标:FPS(多少帧每秒)和MACs(比较可能运行在不同框架和设置上的不同方法)

D.定量评价

SOTA方法对比。

多项式次数对比。

消融实验。

定性评价
对于定性结果,进行了广泛的评价。将在TuSimple上训练的模型作为预训练,训练了3个模型:2个在ELAS上(有和没有车道标记类型分类上各一个),1个在LLAMAS上。

在ELAS上,该模型被训练了385个额外的epochs(所选择的学习速率调度程序的一半周期,其中学习速率将是最低的)。

在LLAMAS上,该模型被训练了75个额外的epochs,ELAS上使用的迭代次数的近似值,因为LLAMAS的训练集比ELAS的训练集大五倍左右。

车道标记类型分类的实验是PolyLaneNet的直接扩展,在PolyLaneNet中,对每个车道预测一个类别,这表明扩展我们的模型是多么简单。

结果

SOTA方法对比
在这里插入图片描述
在这里插入图片描述
由于数据集中的大多数车道标志可以很好地用一阶多项式(即直线)表示,神经网络对预测线有偏置,因此对于曲率突出的车道标志性能较差

多项式次数对比
从用于表示车道标记的多项式度来看,使用低阶多项式时,精度上的微小差异表明数据集是多么的不平衡。使用一阶多项式(即直线)仅降低了0.35 p.p。
另一个重要因素是基准测试用于评估模型性能的度量。然而,LPD度量能够更好地捕捉使用一阶多项式训练的模型与其他模型之间的差异。
在这里插入图片描述
可以看出,TuSimple的度量值并不惩罚那些仅在距离车辆较近的车道上准确的预测,因为在图像中,这部分路段看起来几乎是直线的(即,可以用一阶多项式很好地表示),因为阈值可能会隐藏这些错误。

消融实验
在这里插入图片描述
虽然EfficientNet-b1获得了最高的精度,但我们没有在其他实验中使用它,因为在我们的实验中,精度增益不显著,也不一致。
在这里插入图片描述
在这里插入图片描述

定性评价
在这里插入图片描述
由于这些情况下的图像有一个非常不同的结构(例如,汽车没有朝着道路的方向行驶),在这种情况下,少量的样本可能不足以让模型了解它。

结论

在未来的工作中,可以在不同的车道检测方法(如分割)中使用的指标,以及更好地突出车道检测方法中的缺陷。

(关于车道线检测评价指标的补充)

本文共用了六个指标。关于ACC、TP、FP、FPS这四个常用的相信不用说了。
下面结合本文引用的下述论文描述其他指标:

R. K. Satzoda and M. M. Trivedi, “On Performance Evaluation Metrics for Lane Estimation,” 2014 22nd International Conference on Pattern Recognition, Stockholm, Sweden, 2014, pp. 2625-2630, doi: 10.1109/ICPR.2014.453.

车道位置偏差(Lane Position Deviation,LPD)

用于确定道路场景中估计车道的准确性。尤其是从 ego-vehicle的视角来看。参考图2,车道特征(图2中的(b))用于估计车道,其由图2中的虚线(d)表示。车道位置偏差(图2中的(e))测量通过连接实际车道标线(图2中的(h))获得的检测车道(d)与实际车道的偏差。
在这里插入图片描述从图2可以看出,它捕获了ego-vehicle近景深和远景深中车道估计过程的精度。此外,该方法还评估了用于确定车道曲率的道路模型的准确性。对于给定的车道估计算法,可以利用不同参数来确定它们对LPD的影响。

参考示出可能的输入图像场景的图3,让我们考虑实线是输入图像中地面真值中的实际车道。让我们考虑下面公式的左车道。让虚线表示由车道估计方法确定的左车道位置。LPD度量确定图像场景中实际和检测车道位置之间、ymax和ymin位置之间x方向上的平均偏差δLPD。
在这里插入图片描述
计算公式如下:
在这里插入图片描述
其中,ymin≤y≤ymax是测量车道偏离的道路场景的选定区域。可以使用适当的因子(基于摄像机校准)缩放相同的度量,以确定世界坐标系中的车道位置偏差。

MACs

先说下面几个概念:
FLOPS 注意全部大写 是floating point of per second的缩写,意指每秒浮点运算次数。用来衡量硬件的性能。

FLOPs 是floating point of operations的缩写,是浮点运算次数,可以用来衡量算法/模型复杂度。

关于进一步的内容,可以参考下述引用:

https://blog.csdn.net/weixin_39833897/article/details/105807172


衡量计算量除了FLOPs外还有一种概念是求MACs(Multiply Accumulate)乘积累加运算次数,一次乘积,然后把这个乘积和另外一个数求和就叫一次MAC,显然与上面计算结果的关系就在于是否要乘2的关系。

MACS 每秒执行的定点乘累加操作次数的缩写,它是衡量计算机定点处理能力的量,这个量经常用在那些需要大量定点乘法累加运算的科学运算中,记为MACS。

关于进一步的内容,可以参考下述引用:

https://www.mobibrw.com/2019/17864
https://www.wikiwand.com/zh-cn/%E4%B9%98%E7%A9%8D%E7%B4%AF%E5%8A%A0%E9%81%8B%E7%AE%97
http://blog.sina.com.cn/s/blog_ebbe6d790102uzcy.html
https://blog.csdn.net/m0_38065572/article/details/104610524

网上说什么的都有,我认为文中的MACs指的是是FLOPs的2倍,用来衡量算法/模型复杂度。

粗略地说,一个乘法累积(Multiply-Accumulate,MAC)相当于2次FLOPs。MACs使用以下库进行计算:

https://github.com/mit-han-lab/torchprofile

代码解读

该论文被ICPR 2020接收了。

安装

官方是Python 3.5.2,但更高版本理论兼容。
安装依赖:

pip install -r requirements.txt

注意事项

这里需要注意python和pytroch版本的对应。requirements.txt中那些版本对应的是3.5.2的python,注意找你对应版本的包的安装,以及包之间的函数的变化。(我目前觉得最省事的就是重建一个环境)
不然导致pytroch调用不了GPU,又去重新配置了下。

(这时我的电脑报了个错,不知道是不是大家都这样,但这里还是记录一下。
在这里插入图片描述好像和网络什么相关,很简单,按照下述教程输入两行命令即解决。

https://www.jianshu.com/p/547d0b447490

在这里插入图片描述

export all_proxy="socks5://127.0.0.1:1080"
unset all_proxy && unset ALL_PROXY

用法

训练

训练的每个设置都是通过YAML配置文件配置的。因此,为了训练模型,你必须设置配置文件,示例如下:

# Training settings
exps_dir: 'experiments' # 实验目录的根目录路径(不仅是您要运行的目录)
iter_log_interval: 1 # 每N次迭代记录一次训练迭代
iter_time_window: 100 # 移动平均迭代窗口用于打印loss指标
model_save_interval: 1 # 每N个epochs保存一次模型
seed: 0 # 随机数种子
backup: drive:polylanenet-experiments # 训练结束后,将使用rclone自动上传实验目录。 如果您不想这样做,请留空。
model:
  name: PolyRegression
  parameters:
    num_outputs: 35 # (5 lanes) * (1 conf + 2 (upper & lower) + 4 poly coeffs)
    pretrained: true
    backbone: 'efficientnet-b0'
    pred_category: false
loss_parameters:
  conf_weight: 1
  lower_weight: 1
  upper_weight: 1
  cls_weight: 0
  poly_weight: 300
batch_size: 16
epochs: 2695
optimizer:
  name: Adam
  parameters:
    lr: 3.0e-4
lr_scheduler:
  name: CosineAnnealingLR
  parameters:
    T_max: 385

# Testing settings
test_parameters:
  conf_threshold: 0.5 # 将置信度低于此置信度的预测设置为0(即,将其设置为对于指标无效)

# Dataset settings
datasets:
  train:
    type: PointsDataset
    parameters:
      dataset: tusimple
      split: train
      img_size: [360, 640]
      normalize: true
      aug_chance: 0.9090909090909091 # 10/11
      augmentations: # ImgAug augmentations
       - name: Affine
         parameters:
           rotate: !!python/tuple [-10, 10]
       - name: HorizontalFlip
         parameters:
           p: 0.5
       - name: CropToFixedSize
         parameters:
           width: 1152
           height: 648
      root: "datasets/tusimple" # Dataset root

  test: &test
    type: PointsDataset
    parameters:
      dataset: tusimple
      split: val
      img_size: [360, 640]
      root: "datasets/tusimple"
      normalize: true
      augmentations: []

  # val = test
  val:
    <<: *test`在这里插入代码片`

创建配置文件后,运行训练脚本:

python train.py --exp_name tusimple --cfg config.yaml

脚本选项有:

  --exp_name            实验名称
  --cfg                 Config file for the training (.yaml)
  --resume              断点续训。 如果训练过程被中断,请使用相同的参数和此选项再次运行它,以从最后一个检查点恢复训练。
  --validate            Wheter to validate during the training session. Was not in our experiments, which means it has not been thoroughly tested.
  --deterministic       set cudnn.deterministic = True and cudnn.benchmark = False

测试

训练好后,运行test.py脚本得到评价指标:

python test.py --exp_name tusimple --cfg config.yaml --epoch 2695

脚本选项有:

  --exp_name            Experiment name.
  --cfg                 Config file for the test (.yaml). (probably the same one used in the training)
  --epoch EPOCH         Epoch to test the model on
  --batch_size          Number of images per batch
  --view                Show predictions. Will draw the predictions in an image and then show it (cv.imshow)

复现论文效果

模型

所有训练好的模型:

https://drive.google.com/drive/folders/1oyZncVnUB1GRJl5L4oXz50RkcNFM_FFC

数据集

https://github.com/TuSimple/tusimple-benchmark

https://github.com/rodrigoberriel/ego-lane-analysis-system/tree/master/datasets
https://unsupervised-llamas.com/llamas/

具体做法

要复现结果,您可以使用相同的设置重新训练模型(其结果应与文中的设置非常接近),也可以仅测试模型。 如果要重新训练,则仅需要修改相应的YAML设置文件,您可以在cfgs目录中找到该文件。 如果您只想通过测试模型来重现文中的确切指标,则必须:

  1. 下载实验目录。 您不需要下载所有模型检查点,只需要最后一个(model_2695.pt,除了ELAS和LLAMAS上的实验除外)。
  2. 在实验目录内的config.yaml文件中修改所有与路径相关的字段(即,dataset paths和exps_dir)。
  3. 将下载的实验目录移至您的exps_dir文件夹。

然后,运行

python test.py --exp_name $exp_name --cfg $exps_dir/$exp_name/config.yaml --epoch 2695

$ exp_name替换为您下载的目录的名称(实验的名称),并将$ exps_dir替换为您在config.yaml文件中定义的exps_dir值。 该脚本将查找名为$ exps_dir / $ exp_name / models的目录以加载模型。

代码解读

官方test.py代码如下:

import os
import sys
import random
import logging
import argparse # 关于argparse模块的用法:https://docs.python.org/zh-cn/dev/library/argparse.html
import subprocess
from time import time

import cv2
import numpy as np
import torch

from lib.config import Config
from utils.evaluator import Evaluator


def test(model, test_loader, evaluator, exp_root, cfg, view, epoch, max_batches=None, verbose=True):
    if verbose:
        logging.info("Starting testing.")

    # Test the model
    if epoch > 0:
        model.load_state_dict(torch.load(os.path.join(exp_root, "models", "model_{:03d}.pt".format(epoch)))['model'])

    model.eval()
    criterion_parameters = cfg.get_loss_parameters()
    test_parameters = cfg.get_test_parameters()
    criterion = model.loss
    loss = 0
    total_iters = 0
    test_t0 = time()
    loss_dict = {
   }
    with torch.no_grad():
        for idx, (images, labels, img_idxs) in enumerate(test_loader):
            if max_batches is not None and idx >= max_batches:
                break
            if idx % 1 == 0 and verbose:
                logging.info("Testing iteration: {}/{}".format(idx + 1, len(test_loader)))
            images = images.to(device)
            labels = labels.to(device)

            t0 = time()
            outputs = model(images)
            t = time() - t0
            loss_i, loss_dict_i = criterion(outputs, labels, **criterion_parameters)
            loss += loss_i.item()
            total_iters += 1
            for key in loss_dict_i:
                if key not in loss_dict:
                    loss_dict[key] = 0
                loss_dict[key] += loss_dict_i[key]

            outputs = model.decode(outputs, labels, **test_parameters)

            if evaluator is not None:
                lane_outputs, _ = outputs
                evaluator.add_prediction(img_idxs, lane_outputs.cpu().numpy(), t / images.shape[0])
            if view:
                outputs, extra_outputs = outputs
                preds = test_loader.dataset.draw_annotation(
                    idx,
                    pred=outputs[0].cpu().numpy(),
                    cls_pred=extra_outputs[0].cpu().numpy() if extra_outputs is not None else None)
                cv2.imshow('pred', preds)
                cv2.waitKey(0)

    if verbose:
        logging.info("Testing time: {:.4f}".format(time() - test_t0))
    out_line = []
    for key in loss_dict:
        loss_dict[key] /= total_iters
        out_line.append('{}: {:.4f}'.format(key, loss_dict[key]))
    if verbose:
        logging.info(', '.join(out_line))

    return evaluator, loss / total_iters


def parse_args():
    parser = argparse.ArgumentParser(description="Lane regression")
    parser.add_argument("--exp_name", default="default", help="Experiment name", required=True)
    parser.add_argument("--cfg", default="config.yaml", help="Config file", required=True)
    parser.add_argument("--epoch", type=int, default=None, help="Epoch to test the model on")
    parser.add_argument("--batch_size", type=int, help="Number of images per batch")
    parser.add_argument("--view", action="store_true", help="Show predictions")

    return parser.parse_args()


def get_code_state():
    state = "Git hash: {}".format(
        subprocess.run(['git', 'rev-parse', 'HEAD'], stdout=subprocess.PIPE).stdout.decode('utf-8'))
    state += '\n*************\nGit diff:\n*************\n'
    state += subprocess.run(['git', 'diff'], stdout=subprocess.PIPE).stdout.decode('utf-8')

    return state


def log_on_exception(exc_type, exc_value, exc_traceback):
    logging.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))


if __name__ == "__main__":
    args = parse_args()
    cfg = Config(args.cfg)

    # Set up seeds
    torch.manual_seed(cfg['seed'])
    np.random.seed(cfg['seed'])
    random.seed(cfg['seed'])

    # Set up logging
    exp_root = os.path.join(cfg['exps_dir'], os.path.basename(os.path.normpath(args.exp_name)))
    logging.basicConfig(
        format="[%(asctime)s] [%(levelname)s] %(message)s",
        level=logging.INFO,
        handlers=[
            logging.FileHandler(os.path.join(exp_root, "test_log.txt")),
            logging.StreamHandler(),
        ],
    )

    sys.excepthook = log_on_exception

    logging.info("Experiment name: {}".format(args.exp_name))
    logging.info("Config:\n" + str(cfg))
    logging.info("Args:\n" + str(args))

    # Device configuration
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    # Hyper parameters
    num_epochs = cfg["epochs"]
    batch_size = cfg["batch_size"] if args.batch_size is None else args.batch_size

    # Model
    model = cfg.get_model().to(device)
    test_epoch = args.epoch

    # Get data set
    test_dataset = cfg.get_dataset("test")

    test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                              batch_size=batch_size if args.view is False else 1,
                                              shuffle=False,
                                              num_workers=8)
    # Eval results
    evaluator = Evaluator(test_loader.dataset, exp_root)

    logging.basicConfig(
        format="[%(asctime)s] [%(levelname)s] %(message)s",
        level=logging.INFO,
        handlers=[
            logging.FileHandler(os.path.join(exp_root, "test_log.txt")),
            logging.StreamHandler(),
        ],
    )
    logging.info('Code state:\n {}'.format(get_code_state()))
    _, mean_loss = test(model, test_loader, evaluator, exp_root, cfg, epoch=test_epoch, view=args.view)
    logging.info("Mean test loss: {:.4f}".format(mean_loss))

    evaluator.exp_name = args.exp_name

    eval_str, _ = evaluator.eval(label='{}_{}'.format(os.path.basename(args.exp_name), test_epoch))

    logging.info(eval_str)

为了实现更好的对test.py的理解和调试,我对其修改如下:

import os
import sys
import random
import logging
import argparse
import subprocess
from time import time

import cv2
import numpy as np
import torch

from lib.config import Config
from utils.evaluator import Evaluator


def test(model, test_loader, evaluator, exp_root, cfg, view, epoch, max_batches=None, verbose=True):
# model:模型结构
# test_loader:{DataLoader:23}
# evaluator:<utils.evaluator.Evaluator object at 0x7fd8a62a04c0>
# exp_root:'experiments/default'
# cfg:yaml配置文件
# view:False
# epoch:2695
# max_batches:None
# verbose:True

    if verbose:
        logging.info("Starting testing.")

    # Test the model
    if epoch > 0:
        model.load_state_dict(torch.load(os.path.join(exp_root, "models", "model_{:03d}.pt".format(epoch)))['model']) # 载入模型。根据报错信息去修改

    model.eval() # 告诉模型在推理
    criterion_parameters = cfg.get_loss_parameters() # {'conf_weight': 1, 'lower_weight': 1, 'upper_weight': 1, 'cls_weight': 0, 'poly_weight': 300}
    test_parameters = cfg.get_test_parameters() # {'conf_threshold': 0.5}
    criterion = model.loss # 调用model的loss函数。用于后续的loss计算
    loss = 0
    total_iters = 0
    test_t0 = time() # 记录测试开始时间
    loss_dict = {
   }
    with torch.no_grad(): # 好像是被warp起来的部分不进行梯度更新,但是还是参与了计算的(深度学习基础知识,得补)
    # https://blog.csdn.net/weixin_46559271/article/details/105658654
    # https://www.jianshu.com/p/1cea017f5d11
    # https://blog.csdn.net/weixin_44134757/article/details/105775027
        for idx, (images, labels, img_idxs) in enumerate(test_loader): # 关于enumerate:https://www.runoob.com/python/python-func-enumerate.html
        # test_loader:<torch.utils.data.dataloader.DataLoader object at 0x7f8816a2fee0>
        # test_loader.dataset:<lib.datasets.lane_dataset.LaneDataset object at 0x7f8816a2fe50>
        # test34 lane_dataset222 tusimple137 lane_dataset203 (为什么是这样一个执行顺序,我想是调用enumerate函数时,需要先知道test_loader的长度,然后再用[]方法去取值
        # 先执行enumerate,返回一个enumerate对象。
        # idx是一个batch的索引(一个batch是16,这里358//16=23。)
        # images:{Tensor:16}
        # labels:{Tensor:16}
        # img_idxs是图片的索引。
            if max_batches is not None and idx >= max_batches:
                break
            if idx % 1 == 0 and verbose:
                logging.info("Testing iteration: {}/{}".format(idx + 1, len(test_loader)))
            images = images.to(device)
            labels = labels.to(device) # 放到GPU上

            t0 = time()
            outputs = model(images) # 将图像输入模型,记录输出 虽然{tuple:2}。但是16x35(16张图片,每张图片35个输出)
            # 一个例子如下:
            # (tensor([[ 1.9717e+01,  3.5665e-01,  5.9289e-01,  6.0696e-01, -1.2504e+00,
         -9.5759e-01,  8.3573e-01,  1.9728e+01,  1.7501e-02,  8.4806e-01,
          3.0594e-01, -5.8771e-01, -3.6744e-01,  6.2564e-01,  1.4671e+01,
          1.8156e-02,  8.9165e-01, -2.3603e-01,  1.1759e-01,  7.8406e-01,
          1.9520e-01,  5.1091e-02, -3.0347e-02,  6.6394e-01, -8.3053e-01,
          8.3323e-01,  1.3598e+00, -2.2746e-02, -1.1667e+01,  2.9069e-02,
          5.9165e-01, -1.0211e-01,  7.8156e-01,  9.9158e-01,  1.5408e-01],
        [ 2.0524e+01,  3.5466e-01,  5.9829e-01,  9.8226e-01, -1.1990e+00,
         -1.4157e+00,  1.0521e+00,  2.0533e+01, -5.5082e-04,  1.0035e+00,
          1.8302e-01, -2.6475e-01, -5.1310e-01,  6.9090e-01,  1.3714e+01,
          3.1270e-02,  9.0050e-01, -2.2248e-01,  4.2548e-01,  5.4658e-01,
          3.0025e-01, -5.9779e+00, -2.1180e-02,  5.4276e-01, -5.6866e-01,
          1.2482e+00,  1.2983e+00,  2.0630e-02, -1.2991e+01,  1.8783e-02,
          5.1402e-01,  1.3041e-01,  9.1163e-01,  1.0488e+00,  2.3146e-01],
        [ 1.9811e+01,  3.4818e-01,  6.0123e-01,  1.2543e+00, -1.1373e+00,
         -1.9976e+00,  1.3299e+00,  1.9818e+01, -1.3726e-02,  9.8255e-01,
          1.6025e-01, -4.9490e-04, -9.2998e-01,  8.9230e-01,  9.5679e+00,
          3.7305e-02,  9.9070e-01, -1.2178e-01,  5.5367e-01, -1.1417e-01,
          5.9704e-01, -1.2919e+01, -1.1187e-02,  5.6699e-01,  2.8218e-01,
          9.4523e-01,  2.8753e-01,  4.6897e-01, -1.2240e+01,  6.4192e-03,
          5.0729e-01,  4.6846e-01,  4.4547e-01,  4.8933e-01,  5.2881e-01],
        [ 1.8849e+01,  3.5481e-01,  5.9489e-01,  1.1236e+00, -1.0921e+00,
         -1.9187e+00,  1.2948e+00,  1.8853e+01, -8.1959e-03,  9.8278e-01,
          1.4345e-01, -1.6455e-02, -9.2424e-01,  8.8395e-01,  9.0486e+00,
          3.7280e-02,  1.0009e+00, -6.9186e-02,  5.1333e-01, -1.8387e-01,
          6.1838e-01, -6.3384e+00, -7.4000e-03,  6.0970e-01,  3.7614e-01,
          7.7696e-01,  1.2253e-01,  5.2114e-01, -1.0654e+01,  2.0078e-03,
          5.2485e-01,  3.7526e-01,  4.0945e-01,  4.7751e-01,  5.0353e-01],
        [ 1.8772e+01,  3.2760e-01,  5.7178e-01,  1.3995e+00, -1.1920e+00,
         -2.1902e+00,  1.3575e+00,  1.8774e+01, -8.2448e-03,  9.6515e-01,
          2.2256e-01, -1.8641e-02, -1.1020e+00,  9.3922e-01,  8.3400e+00,
          3.5112e-02,  9.8313e-01, -3.7752e-02,  5.1563e-01, -3.2519e-01,
          6.7639e-01, -9.9023e+00, -7.2143e-03,  6.2546e-01,  5.8917e-01,
          6.4413e-01, -1.6515e-01,  6.6375e-01, -7.5666e+00,  1.9182e-03,
          5.3885e-01,  4.7531e-01,  2.1594e-01,  2.6025e-01,  6.4260e-01],
        [ 1.8206e+01,  3.2264e-01,  6.0505e-01,  1.1304e+00, -9.7306e-01,
         -1.8966e+00,  1.2448e+00,  1.8205e+01, -1.2127e-02,  9.9538e-01,
          1.3683e-01,  8.2247e-02, -9.3255e-01,  8.7522e-01,  7.1296e+00,
          3.7168e-02,  9.7917e-01, -7.7272e-02,  6.0907e-01, -2.0614e-01,
          6.3528e-01, -1.3433e+01, -8.9997e-03,  5.7927e-01,  5.7465e-01,
          7.5690e-01, -4.6105e-02,  6.2752e-01, -9.0991e+00,  2.3277e-03,
          5.1762e-01,  5.5586e-01,  2.6600e-01,  2.8972e-01,  6.5162e-01],
        [ 1.8724e+01,  3.1786e-01,  5.7596e-01,  1.3195e+00, -1.0658e+00,
         -2.0516e+00,  1.2803e+00,  1.8728e+01, -1.3836e-02,  9.7149e-01,
          1.7835e-01, -1.4811e-03, -1.0227e+00,  9.0505e-01,  7.4657e+00,
          3.5839e-02,  9.9364e-01, -8.7356e-02,  5.4268e-01, -2.3358e-01,
          6.4212e-01, -1.4904e+01, -7.8026e-03,  6.0794e-01,  5.7523e-01,
          6.7591e-01, -1.1579e-01,  6.5063e-01, -1.0407e+01,  1.2761e-03,
          5.3218e-01,  6.0676e-01,  1.9660e-01,  1.8991e-01,  6.7666e-01],
        [ 1.9729e+01,  3.8188e-01,  1.0091e+00,  2.2574e-01, -3.9591e-01,
         -3.3652e-01,  6.8122e-01,  1.9735e+01,  1.4888e-02,  9.7211e-01,
         -1.8558e-01,  3.4326e-01,  5.1835e-01,  3.1772e-01,  1.1779e+01,
          1.6677e-02,  6.0190e-01, -8.7055e-01,  9.8156e-01,  1.5660e+00,
         -9.3855e-02, -7.9949e+00, -1.7439e-02,  5.0544e-01, -5.9118e-01,
          1.1279e+00,  1.4808e+00, -4.8462e-03, -1.1008e+01,  1.4023e-02,
          4.9886e-01,  1.2144e-01,  9.3424e-01,  1.1006e+00,  1.8501e-01],
        [ 2.0828e+01,  3.7389e-01,  9.5909e-01,  4.0849e-01, -6.5877e-01,
         -5.4334e-01,  7.9542e-01,  2.0835e+01,  1.8135e-02,  1.0151e+00,
         -9.3961e-02,  1.8864e-01,  3.3228e-01,  4.2197e-01,  1.4412e+01,
          1.3447e-02,  5.9736e-01, -8.0056e-01,  8.6799e-01,  1.4857e+00,
         -2.8108e-02,  2.2950e+00, -1.7361e-02,  4.9760e-01, -9.8458e-01,
          1.5259e+00,  1.9876e+00, -2.2419e-01, -9.7860e-01,  1.4131e-02,
          4.5145e-01, -2.6719e-01,  1.6454e+00,  1.9343e+00, -1.7758e-01],
        [ 1.8541e+01,  3.6052e-01,  9.8302e-01,  1.2630e-01, -3.6989e-01,
         -4.0694e-01,  6.9298e-01,  1.8542e+01,  1.2207e-02,  1.0018e+00,
         -1.6262e-01,  3.3378e-01,  3.3623e-01,  4.0727e-01,  1.0124e+01,
          1.9962e-02,  5.8260e-01, -8.2206e-01,  9.8333e-01,  1.3858e+00,
          3.0251e-02, -6.4354e+00, -1.2738e-02,  4.9564e-01, -4.4351e-01,
          1.0422e+00,  1.2766e+00,  1.4124e-01, -5.1403e+00,  1.1081e-02,
          4.7627e-01,  6.2715e-02,  9.4187e-01,  1.1648e+00,  2.3920e-01],
        [ 2.1700e+01,  3.6963e-01,  1.0161e+00,  3.5365e-01, -5.1549e-01,
         -3.8895e-01,  7.3650e-01,  2.1710e+01,  1.2069e-02,  9.6929e-01,
         -1.0482e-01,  2.9372e-01,  4.4866e-01,  3.7781e-01,  1.8020e+01,
          1.0207e-02,  5.3495e-01, -8.2141e-01,  1.0396e+00,  1.6766e+00,
         -1.0593e-01,  6.8139e+00, -2.0878e-02,  4.6487e-01, -1.3351e+00,
          1.8629e+00,  2.4661e+00, -4.0683e-01, -3.9403e+00,  8.8815e-03,
          4.3651e-01, -3.1911e-01,  2.0130e+00,  2.3060e+00, -3.2863e-01],
        [ 1.9982e+01,  3.5527e-01,  9.9771e-01,  1.8310e-01, -3.2837e-01,
         -2.6487e-01,  6.1877e-01,  1.9988e+01,  1.3263e-02,  9.2778e-01,
         -2.2666e-01,  3.8196e-01,  6.1604e-01,  2.7222e-01,  1.1386e+01,
          2.2135e-02,  5.5518e-01, -1.1819e+00,  1.1790e+00,  1.9540e+00,
         -2.2913e-01, -8.7199e+00, -1.3203e-02,  5.0026e-01, -6.2586e-01,
          1.1869e+00,  1.5888e+00, -8.7219e-03, -9.9509e+00,  1.3846e-02,
          4.7484e-01,  1.0665e-01,  1.0456e+00,  1.2277e+00,  1.8260e-01],
        [ 1.9321e+01,  3.5748e-01,  1.0011e+00,  8.5000e-02, -3.1382e-01,
         -5.8064e-02,  5.0936e-01,  1.9330e+01,  1.5940e-02,  9.3347e-01,
         -2.7937e-01,  3.4139e-01,  7.9552e-01,  1.9267e-01,  1.1115e+01,
          1.9466e-02,  5.5904e-01, -1.1650e+00,  1.0820e+00,  2.0302e+00,
         -2.5906e-01, -8.1233e+00, -1.4825e-02,  5.1039e-01, -6.9422e-01,
          1.1615e+00,  1.6638e+00, -7.1307e-02, -1.1717e+01,  1.7474e-02,
          4.8936e-01,  1.3536e-01,  1.0647e+00,  1.1938e+00,  1.5716e-01],
        [ 1.9986e+01,  3.4426e-01,  1.0036e+00,  6.0444e-02, -3.1636e-01,
         -5.3784e-02,  5.1158e-01,  1.9998e+01,  1.5422e-02,  9.3486e-01,
         -2.3981e-01,  3.1167e-01,  7.6142e-01,  2.0912e-01,  1.2843e+01,
          1.9240e-02,  5.4574e-01, -1.3053e+00,  1.1310e+00,  2.1718e+00,
         -3.1241e-01, -9.2920e+00, -1.1794e-02,  5.1373e-01, -7.2432e-01,
          1.2056e+00,  1.7307e+00, -8.6010e-02, -1.2046e+01,  1.8683e-02,
          4.7902e-01,  9.8536e-02,  1.0589e+00,  1.2306e+00,  1.6226e-01],
        [ 1.9049e+01,  3.6031e-01,  9.8259e-01,  1.7886e-01, -3.9499e-01,
         -3.9496e-01,  6.7416e-01,  1.9055e+01,  1.4615e-02,  9.9518e-01,
         -1.4237e-01,  3.1150e-01,  3.9308e-01,  3.6029e-01,  1.0483e+01,
          2.2869e-02,  6.1555e-01, -8.7037e-01,  9.7719e-01,  1.4753e+00,
         -4.7680e-02, -1.0114e+01, -1.3152e-02,  5.2934e-01, -3.8377e-01,
          1.0160e+00,  1.2257e+00,  1.1184e-01, -9.7097e+00,  1.3663e-02,
          4.9965e-01,  2.1355e-01,  8.3867e-01,  9.5725e-01
  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值