深度学习车道线检测之 LaneNet (使用tensorflow2.4.0跑通)

本文用来整理回顾所学知识,也能使视觉领域初学者的同伴们少走些弯路。

参考链接:无人驾驶汽车系统入门(三十)——基于深度神经网络LaneNet的车道线检测及ROS实现_AdamShan的博客-CSDN博客

论文原文地址:Towards End-to-End Lane Detection: an Instance Segmentation Approach

Tensorflow代码地址:lanenet-lane-detection

 现在作者维护的版本更新到了2021.4.29。我上传到了百度网盘里:

链接:https://pan.baidu.com/s/1XydBVV-niTo9GybRDhquww 
提取码:yos4 

 文件夹结构为:

```
lanenet-lane-detection-master
├─ .idea
│  └─ vcs.xml
├─ config
│  └─ tusimple_lanenet.yaml
├─ data
│  ├─ source_image
│  │  ├─ accuracy.png
│  │  ├─ binary_seg_loss.png
│  │  ├─ instance_seg_loss.png
│  │  ├─ lanenet_batch_test.gif
│  │  ├─ lanenet_binary_seg.png
│  │  ├─ lanenet_embedding.png
│  │  ├─ lanenet_instance_seg.png
│  │  ├─ lanenet_mask_result.png
│  │  ├─ network_architecture.png
│  │  ├─ qr.jpg
│  │  └─ total_loss.png
│  ├─ training_data_example
│  │  ├─ gt_binary_image
│  │  │  ├─ 0000.png
│  │  │  ├─ 0001.png
│  │  │  ├─ 0002.png
│  │  │  ├─ 0003.png
│  │  │  ├─ 0004.png
│  │  │  └─ 0005.png
│  │  ├─ gt_instance_image
│  │  │  ├─ 0000.png
│  │  │  ├─ 0001.png
│  │  │  ├─ 0002.png
│  │  │  ├─ 0003.png
│  │  │  ├─ 0004.png
│  │  │  └─ 0005.png
│  │  ├─ image
│  │  │  ├─ 0000.png
│  │  │  ├─ 0001.png
│  │  │  ├─ 0002.png
│  │  │  ├─ 0003.png
│  │  │  ├─ 0004.png
│  │  │  └─ 0005.png
│  │  ├─ train.txt
│  │  └─ val.txt
│  ├─ tusimple_ipm_remap.yml
│  └─ tusimple_test_image
│     ├─ 0.jpg
│     ├─ 1.jpg
│     ├─ 2.jpg
│     └─ 3.jpg
├─ data_provider
│  ├─ lanenet_data_feed_pipline.py
│  └─ tf_io_pipline_tools.py
├─ lanenet_model
│  ├─ lanenet.py
│  ├─ lanenet_back_end.py
│  ├─ lanenet_discriminative_loss.py
│  ├─ lanenet_front_end.py
│  ├─ lanenet_postprocess.py
│  └─ __init__.py
├─ LICENSE
├─ local_utils
│  ├─ config_utils
│  │  ├─ parse_config_utils.py
│  │  └─ __init__.py
│  └─ log_util
│     ├─ init_logger.py
│     └─ __init__.py
├─ mnn_project
│  ├─ config.ini
│  ├─ config_parser.cpp
│  ├─ config_parser.h
│  ├─ convert_lanenet_model_into_mnn_model.sh
│  ├─ dbscan.hpp
│  ├─ freeze_lanenet_model.py
│  ├─ kdtree.cpp
│  ├─ kdtree.h
│  ├─ lanenet_model.cpp
│  ├─ lanenet_model.h
│  └─ __init__.py
├─ README.md
├─ requirements.txt
├─ semantic_segmentation_zoo
│  ├─ bisenet_v2.py
│  ├─ cnn_basenet.py
│  ├─ vgg16_based_fcn.py
│  └─ __init__.py
├─ tools
│  ├─ evaluate_lanenet_on_tusimple.py
│  ├─ evaluate_model_utils.py
│  ├─ generate_tusimple_dataset.py
│  ├─ make_tusimple_tfrecords.py
│  ├─ test_lanenet.py
│  └─ train_lanenet_tusimple.py
├─ trainner
│  ├─ tusimple_lanenet_multi_gpu_trainner.py
│  ├─ tusimple_lanenet_single_gpu_trainner.py
│  └─ __init__.py
└─ _config.yml

```

一、使用TensorFlow2.4.0实现及基于tuSimple数据集的模型训练

1. tuSimple数据集准备

下载地址:tusimple数据集

我也将它上传到了百度网盘:

链接:https://pan.baidu.com/s/1ZyH4_tV7Nxphrb4MBt7Lcw 
提取码:9ioq 

下载完成后解压缩到一个目录下,目录内容如下:

tuSimple/
├── clips
│   ├── 0313-1
│   ├── 0313-2
│   ├── 0530
│   ├── 0531
│   └── 0601
├── label_data_0313.json
├── label_data_0531.json
├── label_data_0601.json
├── readme.md
└── test_tasks_0627.json

将解压好的tuSimple文件夹放置于data目录下。

2. 如何将代码在 TensorFlow 2.4.0上使用

只需在每个需要导入 TensorFlow 模块的.py文档中加入下述语句:

import tensorflow
tensorflow.compat.v1.disable_eager_execution()
tf = tensorflow.compat.v1

这样就可以在TensorFlow 2.4.0运行 TensorFlow 1.0 版本的代码。

使用以下命令安装一下运行代码所需的包。注意这里的requirements.txt文件中的包可能版本比较老,如果和TensorFlow 2.4.0不兼容,升级一下即可。

pip install -r requirements.txt

3. 生成用于训练的数据

使用项目lanenet-lane-detection中的脚本generate_tusimple_dataset.py产生用于训练的binary mask和instance mask;

3.1 生成文件夹training和testing

根据.json文件转换训练集,生成图片文件夹gt_image、gt_binary_image、gt_instance_image 以及文本文件 train.txt。

在终端输入以下命令:

cd ./tools
python generate_tusimple_dataset.py --src_dir=D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\data\tuSimple

此处需要等待几分钟:

在tuSimple目录下自动生成了trainingtesting两个目录,如下所示:

tuSimple/
|
├──training/
|   ├── gt_binary_image
|   ├── gt_image
|   ├── gt_instance_image
|   ├── label_data_0313.json
|   ├── label_data_0531.json
|   ├── label_data_0601.json
|   └── train.txt
└──testing/
    └── test_tasks_0627.json

 可见该脚本仅生成了train.txt,我们可以手动分割一下train set和val set,也就是剪切train.txt中的一部分到一个新建的val.txt和test.txt文件中。

打开train.txt,可以看到共有3626行数据,此处我粘贴了400行到val.txt中,200行粘贴到test.txt中。这里可以在合理范围内自己设置。

之后运行代码可能遇到的问题:

ModuleNotFoundError: No module named 'data_provider'  

只需在代码前加上以下语句:

import sys
import os
sys.path.append(os.getcwd())

3.2 将标注格式转换成TFRecord

3.2.1 更改config目录下的tusimple_lanenet.yaml文件。

更改数据集的路径,如下所示。

DATASET:
    DATA_DIR: 'D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\data\tuSimple\training'
    IMAGE_TYPE: 'rgb'  # choice rgb or rgba
    NUM_CLASSES: 2
    TEST_FILE_LIST: 'D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\data\tuSimple\training\test.txt'
    TRAIN_FILE_LIST: 'D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\data\tuSimple\training\train.txt'
    VAL_FILE_LIST: 'D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\data\tuSimple\training\val.txt'
    IGNORE_INDEX: 255
    PADDING_VALUE: [127.5, 127.5, 127.5]
    MEAN_VALUE: [0.5, 0.5, 0.5]
    STD_VALUE: [0.5, 0.5, 0.5]
    CPU_MULTI_PROCESS_NUMS: 8

3.2.2 使用脚本生成tfrecord文件

命令如下:

cd ..
python tools/make_tusimple_tfrecords.py --dataset_dir D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\data\tuSimple\training --tfrecords_dir D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\data\tuSimple\training\tfrecords

等待几分钟,脚本会在项目的data/training/tfrecords目录下生成相应的tfrecord文件,如下所示:

4. 训练自己的数据集

4.1更改代码:

 更改data_provider\lanenet_data_feed_pipline.py第799行

def get_next(self,name=None):
    return self._next_internal()
# def get_next(self):
#    return self._next_internal()

4.2 开始训练

运行 tools/train_lanenet_tusimple.py,开始训练:

也可以在终端中输入:

python tools/train_lanenet.py --dataset_dir D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\data\tuSimple\training --multi_gpus False

使用tensorboard查看训练过程

cd tboard/tusimple/bisenetv2_lanenet
tensorboard --logdir=.

在训练过程中,可以通过tensorboard查看模型在验证集上的总损失(val_cost)、分割损失(val_binary_seg_loss)、嵌入损失(val_instance_seg_loss)以及分割精度(val_accuracy)变化曲线,如下所示:

在这里插入图片描述

 还可以查看模型在训练过程中的分割分支和嵌入分支输出到预测图,如下图所示:

在这里插入图片描述

若出现以下问题:

2021-11-15 21:43:06.595835: I tensorflow/stream_executor/cuda/cuda_driver.cc:789] failed to allocate 2.20G (2363280384 bytes) from device: CUDA_ERROR_OUT_OF_MEMORY: out of memory

更改config目录下的tusimple_lanenet.yaml文件中的 batch_num (从32改为8)

TRAIN:
    MODEL_SAVE_DIR: 'model/tusimple/'
    TBOARD_SAVE_DIR: 'tboard/tusimple/'
    MODEL_PARAMS_CONFIG_FILE_NAME: "model_train_config.json"
    RESTORE_FROM_SNAPSHOT:
        ENABLE: False
        SNAPSHOT_PATH: ''
    SNAPSHOT_EPOCH: 8
    BATCH_SIZE: 8
    VAL_BATCH_SIZE: 4
    EPOCH_NUMS: 905
    WARM_UP:
        ENABLE: True
        EPOCH_NUMS: 8
    FREEZE_BN:
        ENABLE: False
    COMPUTE_MIOU:
        ENABLE: True
        EPOCH: 1
    MULTI_GPU:
        ENABLE: True
        GPU_DEVICES: ['0', '1']
        CHIEF_DEVICE_INDEX: 0
SOLVER:
    LR: 0.001
    LR_POLICY: 'poly'
    LR_POLYNOMIAL_POWER: 0.9
    OPTIMIZER: 'sgd'
    MOMENTUM: 0.9
    WEIGHT_DECAY: 0.0005
    MOVING_AVE_DECAY: 0.9995
    LOSS_TYPE: 'cross_entropy'
    OHEM:
        ENABLE: False
        SCORE_THRESH: 0.65
        MIN_SAMPLE_NUMS: 65536
GPU:
    GPU_MEMORY_FRACTION: 0.9
    TF_ALLOW_GROWTH: True
POSTPROCESS:
    MIN_AREA_THRESHOLD: 100
    DBSCAN_EPS: 0.35
    DBSCAN_MIN_SAMPLES: 1000
LOG:
    SAVE_DIR: './log'
    LEVEL: INFO

这里我们仍然是训练的tuSimple数据集,也可以制作自己的数据集。

训练好的权重 百度网盘链接:

链接:https://pan.baidu.com/s/1iNvf6IfU9Fg718xIhze4rg 
提取码:9b0a 
复制这段内容后打开百度网盘手机App,操作更方便哦

将权重文件放在model目录下,然后就可以测试结果了。

5. 测试结果

 终端输入:

python tools/test_lanenet.py --weights_path D:\DOCS\deepl_lane_detection\lanenet-lane-detection-master\model\tusimple_lanenet.ckpt --image_path ./data/tusimple_test_image/0.jpg

结果: 

二、代码及原理知识梳理(未完待续)

整体架构总览图

上图为LaneNet车道线检测的网络结构。这是一种端到端的车道线检测算法,包括LaneNet和H-Net两个网络模型。

其中,LaneNet是一种将语义分割和对像素进行向量表示结合起来的多任务模型,目的是将不同车道线实例化(通过用车道 id 标记每个车道像素来输出车道实例图);

H-Net是由卷积层和全连接层组成的网络模型,负责预测转换矩阵H,目的是对属于同一车道线的像素点进行回归(该矩阵学习以输入图像为条件的透视变换)。

对于每条车道,拟合三阶多项式并将车道重新投影到图像上。下文将具体介绍每一部分实现的功能。

1. LaneNet网络架构

LaneNet网络架构

如图,有两个解码分支:

1)Segmentation branch 负责对输入图像进行语义分割(对像素进行二分类,判断像素属于车道线还是背景),得到Binary image;

为了训练一个这样的分割网络,原论文使用了tuSimple数据集,在数据集中,车道线可能被其他车辆阻挡,在这种情况下,将车道线的标注(Ground truth)贯穿障碍物,如下图所示,从而使得分割网络在车道线被其他障碍物阻挡的情况下,依然可以完整检测出完整的车道线像素。

分割网络使用标准的交叉熵损失函数进行训练,对于这个逐像素分类任务而言(车道线像素/非车道线像素分类),由于两个类别的像素极度不均衡,为了处理此问题,作者使用了bounded inverse class weighting方法。

实例分割任务由两部分组成,分割和聚类部分,在下面的部分中更详细地解释。 为了在速度和准确性方面提高性能 [27],这两个部分在多任务网络中联合训练

2) Embedding branch 对像素进行嵌入式表示,论文将嵌入式向量的维度设置为4,训练得到的 embedding 向量用于聚类。最后将两个分支的结果进行结合利用 Mean-Shift 算法进行聚类,得到实例分割的结果。

不受可以检测的车道数量的限制,并且能够应对车道变化。

通过叠加嵌入分支和分割分支,在使用神经网络提取出车道线像素的同时,还能够对每个车道线实现聚类(即像素属于哪一根车道线)。为了训练这样的聚类嵌入网络,聚类损失函数(嵌入网络)包含两部分,方差项 L v a r L_{var}Lvar​ 和距离项 L d i s t L_{dist}Ldist​ ,其中 L v a r L_{var}Lvar​ 将每个嵌入的向量往某条车道线聚类中心(均值)方向拉,这种“拉力”激活的前提是嵌入向量到平均嵌入向量的距离过远,大于阈值 δ v \delta_vδv​ ; L d i s t L_{dist}Ldist​ 使两个类别的车道线越远越好,激活这个“推力”的前提是两条车道线聚类中心的距离过近,近于阈值 δ d \delta_dδd​ 。最后总的损失函数L的公式如下:

在这里插入图片描述

其中 C CC 表示聚类的数量(也就是车道线的数量),N c N_cNc​ 表示聚类 c cc 中的元素数量,x i x_ixi​ 表示一个像素嵌入向量, μ c \mu_{c}μc​ 表示聚类 c cc 的均值向量,[ x ] + = m a x ( 0 , x ) [x]_{+} = max(0, x)[x]+​=max(0,x),最后的损失函数为 L = L v a r + L d i s t L = L_{var} + L_{dist}L=Lvar​+Ldist​ 。该损失函数在实际实现(TensorFlow)中代码如下:

2. H-Net网络架构

1. generate_tusimple_dataset.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 18-5-18 下午7:31
# @Author  : MaybeShewill-CV
# @Site    : https://github.com/MaybeShewill-CV/lanenet-lane-detection
# @File    : generate_tusimple_dataset.py
# @IDE: PyCharm Community Edition
"""
generate tusimple training dataset
"""
import argparse
import glob
import json
import os
import os.path as ops
import shutil
import sys
sys.path.append(os.getcwd())

import cv2
import numpy as np


def init_args():
    """

    :return:
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('--src_dir', type=str, help='The origin path of unzipped tusimple dataset')

    return parser.parse_args()


def process_json_file(json_file_path, src_dir, ori_dst_dir, binary_dst_dir, instance_dst_dir):
    """

    :param json_file_path:
    :param src_dir: origin clip file path
    :param ori_dst_dir:
    :param binary_dst_dir:
    :param instance_dst_dir:
    :return:
    """
    assert ops.exists(json_file_path), '{:s} not exist'.format(json_file_path)

    image_nums = len(os.listdir(ori_dst_dir))

    with open(json_file_path, 'r') as file:
        for line_index, line in enumerate(file):
            info_dict = json.loads(line)

            image_dir = ops.split(info_dict['raw_file'])[0]
            image_dir_split = image_dir.split('/')[1:]
            image_dir_split.append(ops.split(info_dict['raw_file'])[1])
            image_name = '_'.join(image_dir_split)
            image_path = ops.join(src_dir, info_dict['raw_file'])
            assert ops.exists(image_path), '{:s} not exist'.format(image_path)

            h_samples = info_dict['h_samples']
            lanes = info_dict['lanes']

            image_name_new = '{:s}.png'.format('{:d}'.format(line_index + image_nums).zfill(4))

            src_image = cv2.imread(image_path, cv2.IMREAD_COLOR)
            dst_binary_image = np.zeros([src_image.shape[0], src_image.shape[1]], np.uint8)
            dst_instance_image = np.zeros([src_image.shape[0], src_image.shape[1]], np.uint8)

            for lane_index, lane in enumerate(lanes):
                assert len(h_samples) == len(lane)
                lane_x = []
                lane_y = []
                for index in range(len(lane)):
                    if lane[index] == -2:
                        continue
                    else:
                        ptx = lane[index]
                        pty = h_samples[index]
                        lane_x.append(ptx)
                        lane_y.append(pty)
                if not lane_x:
                    continue
                lane_pts = np.vstack((lane_x, lane_y)).transpose()
                lane_pts = np.array([lane_pts], np.int64)

                cv2.polylines(dst_binary_image, lane_pts, isClosed=False,
                              color=255, thickness=5)
                cv2.polylines(dst_instance_image, lane_pts, isClosed=False,
                              color=lane_index * 50 + 20, thickness=5)

            dst_binary_image_path = ops.join(binary_dst_dir, image_name_new)
            dst_instance_image_path = ops.join(instance_dst_dir, image_name_new)
            dst_rgb_image_path = ops.join(ori_dst_dir, image_name_new)

            cv2.imwrite(dst_binary_image_path, dst_binary_image)
            cv2.imwrite(dst_instance_image_path, dst_instance_image)
            cv2.imwrite(dst_rgb_image_path, src_image)

            print('Process {:s} success'.format(image_name))


def gen_train_sample(src_dir, b_gt_image_dir, i_gt_image_dir, image_dir):
    """
    generate sample index file
    :param src_dir:
    :param b_gt_image_dir:
    :param i_gt_image_dir:
    :param image_dir:
    :return:
    """

    with open('{:s}/training/train.txt'.format(src_dir), 'w') as file:

        for image_name in os.listdir(b_gt_image_dir):
            if not image_name.endswith('.png'):
                continue

            binary_gt_image_path = ops.join(b_gt_image_dir, image_name)
            instance_gt_image_path = ops.join(i_gt_image_dir, image_name)
            image_path = ops.join(image_dir, image_name)

            assert ops.exists(image_path), '{:s} not exist'.format(image_path)
            assert ops.exists(instance_gt_image_path), '{:s} not exist'.format(instance_gt_image_path)

            b_gt_image = cv2.imread(binary_gt_image_path, cv2.IMREAD_COLOR)
            i_gt_image = cv2.imread(instance_gt_image_path, cv2.IMREAD_COLOR)
            image = cv2.imread(image_path, cv2.IMREAD_COLOR)

            if b_gt_image is None or image is None or i_gt_image is None:
                print('图像对: {:s}损坏'.format(image_name))
                continue
            else:
                info = '{:s} {:s} {:s}'.format(image_path, binary_gt_image_path, instance_gt_image_path)
                file.write(info + '\n')
    return


def process_tusimple_dataset(src_dir):
    """

    :param src_dir:
    :return:
    """
    traing_folder_path = ops.join(src_dir, 'training')
    testing_folder_path = ops.join(src_dir, 'testing')

    os.makedirs(traing_folder_path, exist_ok=True)
    os.makedirs(testing_folder_path, exist_ok=True)

    for json_label_path in glob.glob('{:s}/label*.json'.format(src_dir)):
        json_label_name = ops.split(json_label_path)[1]

        shutil.copyfile(json_label_path, ops.join(traing_folder_path, json_label_name))

    for json_label_path in glob.glob('{:s}/test*.json'.format(src_dir)):
        json_label_name = ops.split(json_label_path)[1]

        shutil.copyfile(json_label_path, ops.join(testing_folder_path, json_label_name))

    gt_image_dir = ops.join(traing_folder_path, 'gt_image')
    gt_binary_dir = ops.join(traing_folder_path, 'gt_binary_image')
    gt_instance_dir = ops.join(traing_folder_path, 'gt_instance_image')

    os.makedirs(gt_image_dir, exist_ok=True)
    os.makedirs(gt_binary_dir, exist_ok=True)
    os.makedirs(gt_instance_dir, exist_ok=True)

    for json_label_path in glob.glob('{:s}/*.json'.format(traing_folder_path)):
        process_json_file(json_label_path, src_dir, gt_image_dir, gt_binary_dir, gt_instance_dir)

    gen_train_sample(src_dir, gt_binary_dir, gt_instance_dir, gt_image_dir)

    return


if __name__ == '__main__':
    args = init_args()

    process_tusimple_dataset(args.src_dir)

2. 

三、一些参考文献

1、图森TuSimple车道线检测数据集介绍_十栋前的猫的博客-CSDN博客

 tusimple-benchmark/doc/lane_detection at master · TuSimple/tusimple-benchmark · GitHub

简略总结:

tuSimple数据集大约包括7000个1秒钟长的20帧的视频剪辑(clips)。只有每个剪辑的最后一帧的车道带标签。

数据集的规模:

  1. 训练:3626个视频剪辑,3626个带标注的帧(每个剪辑clips有20帧,最后一帧带有标注)
  2. 测试:2782个视频剪辑

数据集采集时复杂条件:1. 复杂的天气条件(良好和中等) 2. 白天傍晚 3. 车道线数量(2/3/4/甚至更多) 4. 复杂的路况

注释类型: 折现标记

2、[深度学习] 车道线检测调研(lane detection)_Holeung blog-CSDN博客

 深度学习车道线检测论文集

版权声明:本文为CSDN博主「tiger&sheep」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_49332456/article/details/121276739

tiger&sheep

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

SAHI强化YOLOv5在小目标上的表现

环境ubuntu 18.04 64bitsahi 0.8.4yolov5 5.0pytorch 1.7.1cu101前言目标检测和实例分割是迄今为止计算机视觉中最重要的应用领域,各种目标检测网络层出不穷,然而&#xf

小目标数据增强

环境ubuntu 18.04 64位YOLO数据集简介近年来,目标检测已经取得了长足的进步,不过,尽管如此,小目标与大目标之间,依然有着非常大的差距。论文 Augmentat