Запись №1 в моей серии «Дневник машиниста-самоучки».
Четверг, 12 апреля 2018 г. Готовы начать проект №1: Обнаружение полосы движения.
Посмотрите статью №2 из этой серии!
В этом проекте мы создаем устройство для поиска линии полосы движения. Учитывая видеопоток через Python3 / Conda, цель состоит в том, чтобы аннотировать линии полос движения.
Отправка проекта Udacity включает четыре шага: анализ рубрики, написание кода, написание отзыва и отправка заявки. Рубрика дается нам с конкретными критериями, которым должен соответствовать наш код. Код написан в записной книжке Python 3 Jupyter. Запись - это просто текстовая запись, объясняющая, как код соответствует каждой спецификации и процессу написания кода. Отправка может быть либо ZIP-файлом, либо ссылкой на репозиторий Github. Для целей этой серии дневников все мои проекты и материалы будут открываться в моем профиле Github и ссылаться на них внизу каждого сообщения.
Начало проекта
Базовое репо для проекта находится по адресу https://github.com/udacity/CarND-LaneLines-P1. Я рекомендую клонировать его и следовать инструкциям по настройке в правильной среде.
Я перешел на https://github.com/dhruvshah1214/CarND-LaneLines-P1. Это публичный репозиторий моего кода для первого проекта.
Я зашел в терминал, клонировал репозиторий, который я разветвил, настроил среду conda, активировал ее, открыл блокнот Jupyter и начал писать код!
Написание конвейера
Целью проекта было написать конвейер для детектора дорожек. Функции обработки изображений нам передали в ноутбуке; нам пришлось соединить их вместе и превратить обычное изображение с камеры в обработанное изображение с аннотированными полосами движения.
В число предоставленных функций входили: преобразователь градаций серого, детектор контуров, размытие по Гауссу, маска области интереса, функция аннотатора линии, функция горизонтальной линии и функция наложения линейного изображения на исходное изображение.
Я начал с написания конвейера. Вот что у меня получилось:
Вот результат этого кода:
Фух! Багов нет. Ну это впервые! :)
Но при более внимательном рассмотрении обнаружилось кое-что странное. Есть еще несколько лишних, нежелательных линий. Вероятно, это потому, что линии такие маленькие; Поскольку получение всех необходимых настроек пороговых значений для таких мелких деталей в 2D-изображении сложно и редко возможно, понижение верхней границы y для интересующей области отфильтрует эти нежелательные линии. Кроме того, на левом маркере полосы мы не ловим все линии полосы движения, поэтому нам придется настроить параметр mininum_line_length
, который принимает функция hough_lines()
.
После изменения границы переменная img_roi теперь выглядит так:
img_roi = region_of_interest(img_edges, np.array([[ (125,imshape[0]), (imshape[1]/2–25, imshape[0]/2 + 50), (imshape[1]/2 + 25, imshape[0]/2 + 50), (imshape[1] - 70,imshape[0]) ]], dtype=np.int32))
Параметры hough_lines()
были изменены на:
lines = hough_lines(img_roi, 1, np.pi/180, 1, 5, 1)
Новый и улучшенный вывод:
Намного лучше.
Следующей задачей было отредактировать метод draw_lines()
, чтобы на изображении рисовались только две линии: левая и правая.
Я решил различать линии по их наклону и поместил точки для каждого сегмента в четыре разных списка: список левой линии x, список левой линии y и две точки для правой линии, x и y. Мне также нужно было бы экстраполировать всю строку из списка точек. Я написал другой метод, draw_full_line()
, который брал изображение, значения x и значения y, и использовал функцию numpy polyfit()
, чтобы выполнить своего рода «линейную регрессию» и подогнать все точки к многочлену 1-й степени (известному некоторым как линия! ).
Код для метода:
def draw_full_line(img, x_list, y_list, color=[255, 0, 0], thickness=3): if len(x_list) == 0 or len(x_list) != len(y_list): return line = np.polyfit(x_list, y_list, 1) m = line[0] b = line[1] maxY = img.shape[0] maxX = img.shape[1] #take the lowest point on the image, and use the linear equation forumla to solve for its x y1 = maxY x1 = int((y1 - b)/m) # take an arbitrary point near the middle of the img y2 = int((maxY/2) + 60) # solve for x x2 = int((y2 - b)/m) cv2.line(img, (x1, y1), (x2, y2), color, thickness)
Но все равно выглядело не идеально. Линии переулков были точными для некоторых кадров, но кружились как сумасшедшие. Мне нужно было выполнить дополнительную настройку параметров. Я посмотрел, что означает каждый из параметров, и сделал так, чтобы я мог действовать систематически.
Настройка параметров
Размытие по Гауссу
Когда есть край, интенсивность пикселей быстро изменяется (от 0 до 255), которую мы хотим обнаружить. Чтобы это было проще, нам следует загладить края. Как видите, на приведенных выше изображениях много шероховатых краев, что приводит к обнаружению многих зашумленных краев.
Размытие по Гауссу принимает положительное, нечетное целое числоkernel_size,
, с которым вам нужно поиграть, чтобы найти то, что работает лучше всего. Я попробовал 1, 3, 5, 9, 11, 15, 17 (и проверьте результат обнаружения края (см. Следующий раздел). Чем больше значение kernel_size
, тем более размытым становится изображение.
Я начал с 5, работал до 17, затем снова снизился до 13, а затем переключился на редактирование других параметров, а после работал до 1.
Большее значение kernel_size
требует больше времени для обработки. Это не заметно на тестовых изображениях, но об этом следует помнить при обработке потокового видео в реальном времени с камер в беспилотном автомобиле. Постарайтесь свести это к минимуму и сохранить качество кромки.
Обнаружение хитрого края
Из Википедии:
важно отфильтровать краевой пиксель со слабым значением градиента и сохранить край с высоким значением градиента. Таким образом, два пороговых значения устанавливаются для пояснения различных типов краевых пикселей, одно называется высоким пороговым значением, а другое - нижним пороговым значением. Если значение градиента краевого пикселя выше верхнего порогового значения, они помечаются как сильные краевые пиксели. Если значение градиента краевого пикселя меньше верхнего порогового значения и больше нижнего порогового значения, они помечаются как слабые краевые пиксели. Если значение пикселя меньше нижнего порогового значения, они будут подавлены.
В итоге:
- Если градиент пикселя (разница между собой и окружением) выше верхнего порога, пиксель принимается как край
- Если значение градиента пикселя ниже нижнего порога, оно отклоняется.
- Если градиент пикселя находится между двумя пороговыми значениями, он будет принят только в том случае, если он связан с пикселем, превышающим верхний порог.
- Кэнни рекомендовала соотношение верхних и нижних частот от 2: 1 до 3: 1.
Производная функции f (x) показывает, насколько быстро f (x) изменяется относительно x.
Вкратце, градиент - это производная от нескольких переменных; градиент функции f (x, y) - это скорость изменения f относительно x и y. Для черного пикселя (значение 0) в точке (0,0) [поэтому для точки f (0,0) = 0] рядом с белым пикселем (значение 255) в точке (1, 1) [f (1 , 1) = 255] градиент f (x, y) рядом с (1, 1) будет большим, потому что окружающие пиксели темные и изменение значения пикселя по сравнению с его окружением велико.
Я рекомендую сначала установить low_threshold
на ноль, а затем отрегулировать high_threshold
. Если high_threshold
слишком высокий, вы не найдете краев. Если high_threshold
слишком низко, вы обнаружите слишком много краев. Как только вы найдете хороший high_threshold
, отрегулируйте low_threshold
, чтобы отбросить слабые края (шумы), связанные с сильными краями.
Hough Lines
Параметры HoughLinesP довольно просты:
rho
иtheta
- это расстояние и угловое разрешение нашей сетки в пространстве Хафа. Подробнее о Hough Space здесь и Hough Transforms на OpenCV.threshold
: возвращаются только те линии, которые получили достаточно голосов (количество пересечений на ячейку сетки).minLineLength
: Минимальная длина строки (пикселей). Более короткие отрезки линии отфильтровываются.maxLineGap
: Максимально допустимый зазор (в пикселях) между точками на одной линии для их связывания.
Видео с конечного продукта:
Выводы и дальнейшие размышления
Линии переулков на самом деле не являются линиями. Дороги могут поворачивать и изгибаться, а вместе с ними и полосы движения. Использование полиномов для подгонки линий полосы движения предоставит гораздо более точные данные и лучший автономный автомобиль.
Мне также интересно, можно ли использовать метод глубокого обучения для поиска линий полос движения, а не чистую модель компьютерного зрения / обработки изображений. Прямо сейчас настройка параметров была очень специфичной для данных видео, и я чувствую, что она не будет работать, когда будет предоставлено больше сценариев, таких как вождение в ночное время, вождение под дождем и т. Д.
Это был захватывающий и сложный первый проект, который научил меня многому в области обработки и фильтрации изображений. Я с нетерпением жду еще более сложных проектов, которые расширят мои знания и приведут меня к созданию полностью автономного беспилотного автомобиля, который может обеспечить лучшее будущее для всех нас.
Мой код можно найти в моем репозитории Github здесь.