В этом посте объясняется, как использовать глубокие нейронные сети для обнаружения полос шоссе. Разметка полосы движения является основным статическим элементом на автомагистралях. Они инструктируют автомобили в интерактивном режиме и безопасно ездить по автомагистралям. Обнаружение полосы движения также является важной задачей при автономном вождении, которое предоставляет информацию о местоположении для управления автомобилем. Он также используется в ADAS (Расширенная система помощи водителю).

Для задачи обнаружения полос у нас есть два набора данных с открытым исходным кодом. Один - это набор данных Tusimple, а другой - набор данных CULane. Давайте кратко рассмотрим один из наборов данных.

Набор данных Tusimple

Этот набор данных был выпущен как часть Tusimple Lane Detection Challenge. Он содержит 3626 видеороликов продолжительностью 1 сек каждый. Каждый из этих видеоклипов содержит 20 кадров, из которых последний кадр аннотирован. Эти видео были сняты путем установки камер на приборную панель автомобиля. Вы можете скачать набор данных здесь или посетить https://github.com/TuSimple/tusimple-benchmark/issues/3.

Структура каталогов выглядит как на рисунке ниже,

Каждый подкаталог содержит 20 последовательных изображений, из которых последний кадр аннотирован. label_data_ (date) .json содержит метки в формате JSON для последнего кадра. Каждая строка в файле JSON представляет собой словарь со значениями ключей…

raw_file: строковый тип. путь к файлу в клипе

полосы: это список полос. Каждый список соответствует полосе, и каждый элемент внутреннего списка является координатой x наземной полосы истинности.

h_samples: это список значений высоты, соответствующих полосам движения. Каждый элемент в этом списке является координатой Y наземной полосы истинности.

В этом наборе данных аннотировано не более четырех полос - две полосы движения (две границы полос, на которых в настоящее время находится транспортное средство) и полосы справа и слева от полос движения. Все дорожки аннотируются с одинаковым интервалом по высоте, поэтому h_samples содержит только один список, элементы которого соответствуют y-координатам для всех списков в дорожках. Для точки в h_samples, если в этом месте нет полосы движения, соответствующая ей координата x имеет значение -2. Например, строка в файле JSON выглядит так:

{
  "lanes": [
        [-2, -2, -2, -2, 632, 625, 617, 609, 601, 594, 586, 578, 570, 563, 555, 547, 539, 532, 524, 516, 508, 501, 493, 485, 477, 469, 462, 454, 446, 438, 431, 423, 415, 407, 400, 392, 384, 376, 369, 361, 353, 345, 338, 330, 322, 314, 307, 299],
        [-2, -2, -2, -2, 719, 734, 748, 762, 777, 791, 805, 820, 834, 848, 863, 877, 891, 906, 920, 934, 949, 963, 978, 992, 1006, 1021, 1035, 1049, 1064, 1078, 1092, 1107, 1121, 1135, 1150, 1164, 1178, 1193, 1207, 1221, 1236, 1250, 1265, -2, -2, -2, -2, -2],
        [-2, -2, -2, -2, -2, 532, 503, 474, 445, 416, 387, 358, 329, 300, 271, 241, 212, 183, 154, 125, 96, 67, 38, 9, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2],
        [-2, -2, -2, 781, 822, 862, 903, 944, 984, 1025, 1066, 1107, 1147, 1188, 1229, 1269, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2]
       ],
  "h_samples": [240, 250, 260, 270, 280, 290, 300, 310, 320, 330, 340, 350, 360, 370, 380, 390, 400, 410, 420, 430, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710],
  "raw_file": "path_to_clip"
}

В нем говорится, что на изображении четыре полосы, первая полоса начинается в (632,280), вторая полоса начинается в (719,280), третья полоса начинается в (532,290), а четвертая полоса начинается в (781,270).

Визуализация набора данных

# import required packages
import json
import numpy as np
import cv2
import matplotlib.pyplot as plt
# read each line of json file
json_gt = [json.loads(line) for line in open('label_data.json')]
gt = json_gt[0]
gt_lanes = gt['lanes']
y_samples = gt['h_samples']
raw_file = gt['raw_file']
# see the image
img = cv2.imread(raw_file)
cv2.imshow('image',img)
cv2.WaitKey(0)
cv2.destroyAllWindows()

Теперь посмотрим на визуализацию точек JSON на изображении.

gt_lanes_vis = [[(x, y) for (x, y) in zip(lane, y_samples)
                  if x >= 0] for lane in gt_lanes]
img_vis = img.copy()

for lane in gt_lanes_vis:
    cv2.polylines(img_vis, np.int32([lane]), isClosed=False,
                   color=(0,255,0), thickness=5)

Теперь мы поняли набор данных, но мы не можем передать это изображение в качестве метки для нейронной сети, поскольку изображения в оттенках серого со значениями от нуля до num_classes -1 должны передаваться в нейронную сеть глубокой свертки. сеть для вывода изображения, содержащего прогнозируемые полосы. Итак, нам нужно сгенерировать изображения меток для файлов JSON. Изображения этикеток можно создать с помощью OpenCV путем рисования линий, проходящих через точки в файле JSON.

OpenCV имеет встроенную функцию для рисования нескольких линий через набор точек. Здесь можно использовать метод OpenCV Polylines. Сначала создайте маску всех нулей с высотой и шириной, равной высоте и ширине необработанного файла, используя numpy. Размер изображения можно уменьшить, чтобы сократить объем вычислений во время обучения, но не забывайте поддерживать такое же соотношение сторон.

Создание ярлыков

Этикетка должна быть изображением в оттенках серого. Создайте одну метку для каждого клипа из файла JSON. Сначала создайте маску из черных пикселей с формой, подобной изображению raw_file из файла JSON. Теперь, используя метод полилиний OpenCV, нарисуйте линии разного цвета (каждый соответствует каждой полосе дорожек) на изображении маски, используя полосы и h_samples из файла JSON. Из трехканального изображения маски сгенерируйте изображение маски серой шкалы со значениями в виде номеров классов. Аналогичным образом создайте метки для всех изображений в файле JSON. Вы можете изменить размер изображения и его метки до меньшего размера для меньших вычислений.

mask = np.zeros_like(img)
colors = [[255,0,0],[0,255,0],[0,0,255],[0,255,255]]
for i in range(len(gt_lanes_vis)):
    cv2.polylines(mask, np.int32([gt_lanes_vis[i]]), isClosed=False,color=colors[i], thickness=5)
# create grey-scale label image
label = np.zeros((720,1280),dtype = np.uint8)
for i in range(len(colors)):
   label[np.where((mask == colors[i]).all(axis = 2))] = i+1

Построить и обучить модель

Обнаружение полос - это, по сути, проблема сегментации изображения. Поэтому я использую модель ERFNET для этой задачи, которая эффективна и быстро. Первоначально ERFNET был предложен для задач семантической сегментации, но он также может быть расширен для решения других задач сегментации изображений. Вы можете ознакомиться с его статьей здесь. Это CNN с кодировщиком, декодером и расширенными свертками вместе с остаточными слоями, не являющимися узкими местами. См. Рисунок 1 для архитектуры модели.

Постройте и создайте объект модели. Обучите его по набору данных, созданному выше, для достаточного количества эпох с потерей двоичной кросс-энтропии или настраиваемой функцией потерь, которая минимизирует погрешность на пиксель. Для лучшего использования памяти создайте генератор набора данных и обучите модель над ним. Генераторы снимают нагрузку с загрузки всех изображений в память (если ваш набор данных имеет большой размер, вы должны использовать генератор), что приводит к съеданию всей памяти, и другие процессы не могут работать должным образом. На рис. 2 показаны слои ERFNET с входными и выходными измерениями.

Оценить модель

После обучения получите прогнозы модели, используя приведенный ниже фрагмент кода. Я реализовал это в Pytorch. Я использую метод color_lanes для преобразования выходных изображений из модели (которые имеют два канала со значениями в виде номеров классов) в три изображения с каналом. im_seg - это окончательное наложенное изображение, показанное на изображении 4.

# using pytorch
import torch
from torchvision.transforms import ToTensor
def color_lanes(image, classes, i, color, HEIGHT, WIDTH):
    buffer_c1 = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
    buffer_c1[classes == i] = color[0]
    image[:, :, 0] += buffer_c1
    buffer_c2 = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
    buffer_c2[classes == i] = color[1]
    image[:, :, 1] += buffer_c2
    buffer_c3 = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
    buffer_c3[classes == i] = color[2]
    image[:, :, 2] += buffer_c3
    return image
img = cv2.imread('images/test.jpg') 
img = cv2.resize(img,(WIDTH, HEIGHT),interpolation = cv2.INETR_CUBIC)
op_transforms = transforms.Compose([transforms.ToTensor()])
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
im_tensor = torch.unsqueeze(op_transforms(img), dim=0)
im_tensor = im_tensor.to(device)
model = ERFNET(5)
model = model.to(device)
model = model.eval()
out = model(im_tensor)
out = out.max(dim=1)[1]
out_np = out.cpu().numpy()[0]
out_viz = np.zeros((HEIGHT, WIDTH, 3))
for i in range(1, NUM_LD_CLASSES):
    rand_c1 = random.randint(1, 255)
    rand_c2 = random.randint(1, 255)
    rand_c3 = random.randint(1, 255)
    out_viz = color_lanes(
            out_viz, out_np,
            i, (rand_c1, rand_c2, rand_c3), HEIGHT, WIDTH)
instance_im = out_viz.astype(np.uint8)
im_seg = cv2.addWeighted(img, 1, instance_im, 1, 0)

Спасибо, что прочитали ...

использованная литература

  1. ERFNet: эффективная остаточная факторизованная ConvNet для семантической сегментации в реальном времени.
  2. Обнаружение и классификация полос с использованием CNN.
  3. Https://www.mdpi.com/sensors/sensors-19-00503/article_deploy/html/images/sensors-19-00503-g004.png