Шаги, предпринятые для достижения результата, и используемые методы

В целом мой конвейер выглядит следующим образом:

Давайте подробно рассмотрим каждый этап.

Калибровка камеры

На этом этапе я прочитал серию тестовых изображений из каталога camera_cal. Каждое изображение представляет собой вид шахматной доски под разными углами, как показано ниже.

Я использую функцию OpenCV findChessBoardCorners (), чтобы найти координаты x, y каждого угла на данном изображении. Когда у меня есть массив со всеми этими углами, я использую функцию OpenCV calibrateCamera () для вычисления матрицы камеры, вектора коэффициентов искажения и векторов
вращения и перемещения камеры.

Неискажать изображение

После того, как мы откалибровали камеру, как указано выше, теперь мы можем применить матрицу камеры и коэффициенты искажения для исправления эффектов искажения на входных изображениях камеры. Это делается с помощью функции OpenCV undistort (). Например, вот два изображения: одно до исправления неискажения, а другое
после. Эффект едва заметен и наиболее заметен по краям изображения.

Например, посмотрите на дерево в правой части кадра. На неискаженном изображении можно увидеть, как она несколько изменилась.

Двоичное пороговое значение

На этапе определения порога мы обрабатываем неискаженное изображение и пытаемся замаскировать, какие пиксели являются частью дорожек, и удалить те, которые не являются. Звучит намного проще, чем есть на самом деле. Для этого я использую два метода и комбинирую результаты обоих для получения результата.

У меня есть мини-конвейер для этого этапа, который выглядит следующим образом:

Я беру входное изображение RGB и на верхнем пути конвертирую его в оттенки серого. Затем я применяю фильтр Собела в направлении X, чтобы края изображения совпадали с направлением линий полос. Затем я применяю к нему пороговую функцию, чтобы отфильтровать пиксели, которые не представляют интереса. Путем экспериментов я обнаружил, что минимальные / максимальные пороговые значения 30 и 150 работают хорошо. Я использую этот вывод для создания двоичного изображения интересующих пикселей.

На нижнем пути я конвертирую изображение RGB в цветовое пространство HLS, а затем использую из него S-канал. Канал насыщенности S полезен для выделения линий полос в разных цветовых и контрастных условиях, например в тенях. Затем я передаю S-канал в функцию InRange, чтобы
отфильтровать ненужные пиксели. Путем экспериментов я обнаружил, что здесь лучше всего работают значения 175 и 250. Я также генерирую двоичное изображение, используя этот вывод.

Наконец, я комбинирую как Threshold Binary, так и InRange Binary, чтобы сгенерировать мой окончательный результат - Combined Binary. Для справки это выглядит так:

Вы можете видеть, что здесь довольно четко очерчены полосы движения.

Преобразование перспективы
Получив бинарное пороговое изображение выше, я применяю преобразование перспективы к изображению, чтобы создать изображение с эффектом взгляда на дорогу сверху. Для этого я использую функцию OpenCV warpPerspective (). Я определил предустановленный список координат для использования для преобразования перспективы, и функция OpenCV getPerspectiveTransform () генерирует матрицу перспективы, используя их.

После того, как я применил перспективное преобразование к бинарному пороговому изображению, я получаю изображение, которое выглядит так, будто полосы сверху, как показано ниже.

Вы можете ясно видеть эту левую сплошную полосу движения и правую пунктирную полосу. На изображении все еще есть некоторый шум, который будет устранен на следующем этапе.

Найдите полосы

На этом этапе мы попытаемся извлечь фактические пиксели полосы для левой и правой полос из приведенного выше изображения.

Для первого кадра с камеры или для случаев, когда полосы «потеряны» (т. Е. Мы не смогли надежно определить «хорошую» полосу для ряда кадров), я генерирую гистограмму нижней половины изображение. Затем, используя два пика на этой гистограмме, я определяю хорошую отправную точку, чтобы начать
поиск пикселей изображения в нижней части изображения. Назовем эти точки x_left и x_right.

После расчета этих точек я делю изображение на 10 горизонтальных полос равного размера. Для нижней полосы я маскирую все, что находится за пределами небольшого окна вокруг x_left и x_right, чтобы выделить пиксели, принадлежащие полосе, эффективно отбрасывая все остальные «шумовые» пиксели.

Я повторяю этот процесс для каждой полосы, используя гистограммы на полосах, чтобы определить правильные значения x_left и x_right. После того, как я обработал все полосы, у меня остались изображения как для левой, так и для правой полосы. Вот как выглядит изображение левой полосы:

Как видите, это довольно хорошее представление левой полосы в перспективе.

Поскольку описанный выше подход довольно медленный, я использую его только в первом кадре или когда полосы «теряются». Для всех других кадров я использую полиномиальное уравнение дорожки для предыдущего кадра (вычисленное на этапе ниже) для оценки значений left_x и right_x. Я также отслеживаю значение тренда координаты полосы x, чтобы
помочь лучше захватить линии полосы движения. Вот изображение того, как мой подход со скользящим окном выглядит для типичного фрейма:

Здесь вы можете увидеть значения left_x и right_x, показанные синими линиями для каждой горизонтальной полосы. Вы также можете видеть окна, которые я использую вокруг этих значений, чтобы определить, какие пиксели являются частью полосы, и, таким образом, маскируют все остальные «шумы».

Fit Lanes

Когда у меня есть изображение для левой и правой полос сверху, я использую функцию Numpy polyfit (), чтобы подогнать полиномиальное уравнение к каждой полосе. На этом этапе я также вычисляю радиус кривизны для каждой полосы.

После того, как они были рассчитаны, я провожу несколько проверок, чтобы определить, является ли рассчитанная полоса «хорошей» или нет. Сначала я проверяю, чтобы радиус кривизны полосы превышал минимальный порог. Я выбрал этот порог, посмотрев на требования правительства США для кривизны шоссе. Фактически, это проверяет, не поворачивает ли рассчитанная полоса быстрее, чем мы ожидали.

Вторая проверка, которую я выполняю, - это убедиться, что координаты left_x и right_x, по существу, там, где полоса пересекает нижнюю часть изображения, не слишком сильно менялись от кадра к кадру. Если задуматься, при 30 кадрах в секунду с камеры полосы не должны сильно меняться от кадра к кадру. Я обнаружил, что проверка дельты в 15 пикселей хорошо работает для этой проверки.

Наконец, я проверяю, что Радиус кривизны каждой полосы находится в пределах 100 раз (больше или меньше), чем значения предыдущих кадров. Поскольку RoC может быть очень большим для вертикальных линий, я обнаружил, что проверка границ 100x работает довольно хорошо.

Если какая-либо из вышеперечисленных проверок не удалась для полосы, я считаю полосу «потерянной» или необнаруженной для этого кадра и использую значения из предыдущего кадра. Опять же, при 30 кадрах в секунду это должно быть нормально для короткого количества кадров, поскольку полосы не будут слишком сильно меняться между последовательными кадрами. Если полоса не была
обнаружена в течение 5 последовательных кадров, я запускаю полное сканирование для обнаружения полос в разделе «Найти дорожки».

Рисование дорожек

Наконец, я снова рисую обнаруженные полосы на неискаженном изображении. Для этого нам нужно сделать обратное отображение перспективы, так как полосы были обнаружены в перспективе. Я также аннотирую координаты left_x и right_x, а также радиус кривизны для обеих полос. Я вычисляю значение того, как далеко машина находится от центра полосы движения, и тоже отмечаю это.

Окончательный результат выглядит так: