Создание системы обнаружения полос с использованием Python 3 и OpenCV

Я начал заниматься самоуправлением Udacity Car Engineer Nanodegree в декабре, и до сих пор это было абсолютным взрывом. В настоящее время я завершаю свой второй проект по классификации дорожных знаков с использованием сверточной нейронной сети, в которой используется модифицированная архитектура LeNet. Если вам интересно, можете почитать мой пост об этом здесь. Я хотел вернуться к своему первому проекту, обнаружив линии полос движения с помощью OpenCV, и показать всем, кто может быть заинтересован в элементарном компьютерном зрении, как именно оно работает и как оно выглядит. Это был отличный проект для начинающего человека, плохо знакомого с компьютерным зрением. Я многому научился и в конечном итоге построил конвейер, который работает. Вы можете найти код на GitHub. Я настоятельно рекомендую вам попробовать этот код на себе - вы даже можете запустить его в своем видео!

Соревнование:

Когда мы едем, мы используем глаза, чтобы решить, куда ехать. Линии на дороге, которые показывают нам, где проходят полосы движения, служат нашим постоянным ориентиром для определения того, куда направить автомобиль. Естественно, первое, что мы хотели бы сделать при разработке беспилотного автомобиля, - это автоматически определять полосы движения с помощью алгоритма.

Мы хотим начать с такого изображения:

Обработайте изображение для определения полосы движения:

И, наконец, экстраполируйте и усредните эти линии для плавного определения полосы движения, которое мы можем применить к видеокадрам:

Подход:

Первым шагом к работе с нашими изображениями будет их преобразование в оттенки серого. Это важный шаг к использованию Canny Edge Detector внутри OpenCV. Я расскажу больше о том, что делает canny() через минуту, но сейчас важно понять, что мы сворачиваем 3 канала значений пикселей (красный, зеленый и синий) в один канал с диапазоном значений пикселей [0,255] . gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

Прежде чем мы сможем обнаружить наши края, нам нужно четко указать, что мы ищем. Линии движения всегда желто-белые. Желтый цвет сложно выделить в пространстве RGB, поэтому давайте вместо этого конвертируем его в цветовое пространство Насыщенность значения тона или цветовое пространство HSV. Вы можете найти целевой диапазон для желтых значений с помощью поиска Google. Те, что я использовал, ниже. Затем мы применим маску к исходному RGB image, чтобы вернуть интересующие нас пиксели.

lower_yellow = np.array([20, 100, 100], dtype = “uint8”)
upper_yellow = np.array([30, 255, 255], dtype=”uint8")
mask_yellow = cv2.inRange(img_hsv, lower_yellow, upper_yellow)
mask_white = cv2.inRange(gray_image, 200, 255)
mask_yw = cv2.bitwise_or(mask_white, mask_yellow)
mask_yw_image = cv2.bitwise_and(gray_image, mask_yw)

Мы почти добились хороших результатов! Мы определенно немного обработали со времени нашего исходного изображения, но волшебство еще не произошло. Применим быстрое размытие по Гауссу. Этот фильтр поможет подавить шум в нашем Canny Edge Detection, усредняя значения пикселей в окрестности.

kernel_size = 5
gauss_gray = gaussian_blur(mask_yw_image,kernel_size)

Обнаружение хитрого края

Мы готовы! Давайте посчитаем наш Canny Edge Detection. Быстрое освежение ваших расчетов действительно поможет понять, что именно здесь происходит! По сути, canny() анализирует значения пикселей в соответствии с их производной по направлению (т. Е. Градиентом). Остались края - или там, где есть крутая производная хотя бы в одном направлении. Нам нужно будет указать пороги для canny(), поскольку он вычисляет градиент. Сам Джон Кэнни рекомендовал соотношение низкого и высокого порога 1: 2 или 1: 3.

low_threshold = 50
high_threshold = 150
canny_edges = canny(gauss_gray,low_threshold,high_threshold)

Мы прошли долгий путь, но еще не закончили. Мы не хотим, чтобы наша машина обращала внимание на что-либо на горизонте или даже на другой полосе движения. Наш конвейер обнаружения полосы движения должен фокусироваться на том, что находится впереди машины. Сделайте это, мы собираемся создать еще одну маску, называемую нашей областью интереса (ROI). Все, что находится за пределами области интереса, будет установлено на черный / ноль, поэтому мы работаем только с соответствующими краями. Я избавлю вас от подробностей о том, как я создал этот многоугольник - загляните в репозиторий GitHub, чтобы увидеть мою реализацию. roi_image = region_of_interest(canny_edges, vertices)

Hough Space

Приготовьтесь к тому, что ваш мозг взорвется. Ваше любимое уравнение y = mx + b вот-вот обнаружит свое альтер-эго - преобразование Хафа. Udacity предоставляет потрясающий видеоконтент о космосе Hough, но в настоящее время он предназначен только для студентов. Однако это отличная статья, которая познакомит вас с предметом. Если научные публикации не для вас, не волнуйтесь. Важный вывод заключается в том, что в пространстве XY линии - это линии, а точки - это точки, но в пространстве Hough линии соответствуют точкам в пространстве XY и точкам соответствуют линиям в пространстве XY. Вот как будет выглядеть наш конвейер:

  1. Пиксели считаются точками в пространстве XY.
  2. hough_lines() преобразует эти точки в линии внутри пространства Хафа
  3. Везде, где эти линии пересекаются, есть точка пересечения в пространстве Хафа.
  4. Точка пересечения соответствует линии в пространстве XY.

Если вас интересует код для этой части, обязательно просмотрите его в блокноте Jupyter в репо. Вы можете найти более подробную информацию о параметрах преобразования Хафа здесь. Не бойтесь экспериментировать и пробовать разные пороги! Посмотрим, как это выглядит в действии:

Ключевым замечанием по поводу изображения выше является то, что оно содержит нулевые пиксельные данные с любой из фотографий, которые мы обработали для его создания. Это строго черные / нули и нарисованные линии. Кроме того, то, что выглядит как две строки, на самом деле может быть множество. В пространстве Хафа могло быть много-много точек пересечения, представляющих прямые в XY. Мы захотим объединить все эти строки в два основных средних. Решение, которое я построил для перебора строк, находится в репо.

Когда у нас есть две основные линии, мы можем усреднить наше линейное изображение с исходным неизмененным изображением дороги, чтобы получить красивое и плавное наложение. complete = cv2.addWeighted(initial_img, alpha, line_image, beta, lambda)

Обработка видео

Это несколько коротких строк для редактирования одного кадра, создания скользящего среднего и обработки 30 кадров в секунду.

Результаты, достижения:

Это было фантастическое введение в Udacity SDC Engineer Nanodegree. Мне было очень весело работать над этим проектом и создавать свое решение. Тем не менее, есть несколько вещей, которые я хотел бы улучшить.

  • Область обнаружения полосы интереса (ROI) должна быть гибкой. При движении вверх или вниз по крутому склону горизонт изменится и больше не будет зависеть от пропорций кадра. Это также то, что нужно учитывать при крутых поворотах и ​​движении от бампера до бампера.
  • Вождение ночью. Процесс определения и выбора цвета очень хорошо работает при дневном свете. Добавление теней вызовет некоторый шум, но не обеспечит таких строгих испытаний, как вождение ночью или в условиях ограниченной видимости (например, сильный туман).

А пока я буду строить сверточную нейронную сеть для классификации дорожных знаков, узнавать больше о TensorFlow и знакомиться с Keras. Будьте на связи!