Первый маленький шаг, который должен сделать автомобиль, чтобы управлять собой, — это увидеть мир. Не просто просмотрите его, но определите правила и информацию, которые ему нужны, чтобы позволить себе захватить мир! Шучу, это будет Скайнет, а не машины.

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

Но что именно мы делаем на самом деле? Визуально наш мозг видит синюю дорогу с желтыми и белыми отметинами. Мы распознаем эти формы как толстые линии, идущие вертикально. Затем мы создаем ментальные рамки, чтобы удерживать в них наши автомобили. Чтобы беспилотный автомобиль делал то же самое, он должен сначала идентифицировать эти полосы движения. Он должен иметь возможность просматривать дорогу и выделять только линии, ограничивающие полосу движения.

Достаточно легко, верно? Что ж, я собираюсь рассмотреть очень простой метод, используя некоторые из самых основных и фундаментальных концепций компьютерного зрения, которые позволяют автомобилям смотреть на шоссе и определять полосы движения. Я проведу вас через мой процесс, и вы сможете попробовать его сами после!

Это будет серия постов в течение нескольких месяцев, кульминацией которых станет добавление кода в настоящий беспилотный автомобиль!

Конвейер поиска дорожек

  1. Черный или белый

Первое, что нам нужно сделать, это преобразовать цветное изображение в черно-белое. Поиск линий на изображении не зависит от цвета, поэтому мы можем просто убрать всю эту информацию и упростить алгоритм. Способ, которым изображения хранятся в цифровом виде в цвете, представляет собой расположение пикселей и их цветовых значений. Возьмите исходное изображение ниже, например, оно ограничено примерно 1000 пикселями в длину по оси x и 550 пикселями в ширину по оси y. Почти все цвета могут быть выражены комбинацией красного, синего и зеленого. Таким образом, каждый пиксель на изображении ниже представлен значениями x и y для положения, а также значениями красного, синего и зеленого от 0 до 255 для представления цвета этого пикселя.

На python мы используем функцию OpenCV ниже, чтобы преобразовать цветное изображение в оттенки серого:

cv2.cvtColor (изображение, cv2.COLOR_RGB2GRAY)

Первый аргумент, отправленный в функцию, — это изображение, которое мы хотим преобразовать в черно-белое. Затем мы используем следующую функцию для отображения изображения:

plt.imshow(серый, cmap='серый')

И вуаля!

Теперь вы можете заняться созданием хипстерских нуарных фильмов с помощью Python!

2. Гладкий преступник

Следующим шагом в конвейере является удаление шума в информации об изображении путем его сглаживания. Мы делаем это, применяя размытие по Гауссу с различными размерами ядра. Просто представьте, что вы сглаживаете грубый деревянный стол наждачной бумагой, и размер этой наждачной бумаги определяется размером ядра. Мы можем использовать функцию:

cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)

Я обнаружил, что размер ядра 3 работает достаточно хорошо.

3. Кэнни, ты в порядке?

Теперь все становится интереснее! На этом этапе мы используем метод, известный как обнаружение Canny Edge. Функция OpenCV удаляет с изображения все, кроме его краев. Таким образом, мы можем получить очертания интересующих нас фигур. Функция дается с параметрами, которые можно настроить по своему вкусу:

cv2.Canny(img, low_threshold, high_threshold)

Принцип поиска края заключается в определении скорости изменения яркости нескольких пикселей. Двигаясь по дороге слева направо, если мы встречаемся с краем полосы движения, цвет меняется с сурового дорожного покрытия на яркую линию полосы движения. Мера этого изменения определяется градиентом. Любой градиент ниже параметра нижнего порога, который мы отправляем в функцию, отбрасывается. В то время как любой градиент выше верхнего порога обязательно будет краем. Все промежуточные сохраняются, в зависимости от того, связаны ли они с другими пикселями, которые мы определили как края. С моими значениями 100 и 250:

4. Они на самом деле не заботятся о нас

Но все эти другие края отвлекают! Что нас действительно волнует, так это только линии дорожек! Камера, которую видит наш автомобиль, закреплена вверху, поэтому у нее такое же фронтальное поле зрения, как и у автомобиля. Мы можем использовать это, чтобы геометрически сузить интересующую нас область. Мы просто создаем четырехугольник, ограничивающий области, где автомобиль обычно находит полосу движения при движении по прямой дороге. У моей области нижние углы перекрывались с углами изображения выше, а верхние углы находились в позициях (450, 330), (520, 330). В сочетании с функцией cv2.fillpoly() для рисования области и функцией cv2.bitwise_and() для слияния с имеющимся изображением мы получаем:

5. Не останавливайтесь, пока не насытитесь

Оставляем лучшее напоследок! Преобразование Хафа! Прямо сейчас наши края на изображениях — это просто набор пикселей. Нам нужна информация о переулке, такая как наклоны и точки пересечения. Мы могли бы перебрать все точки в группе и каким-то образом выяснить эту информацию. Но это тяжело! Кроме того, изображения, как правило, не идеальны.

Итак, мы хотим, чтобы геометрическое представление линии определяло ее наклон и пересечение, придавая больший вес пикселям, которые уже являются частью линии. Это именно то, что делает преобразование Хафа!

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

Очень хорошее наглядное объяснение метода описано пользователем mlai на stackexchange здесь.

строки = cv2.HoughLinesP (img, rho, тета, порог, np.array ([]), minLineLength = min_line_len, maxLineGap = max_line_gap)

Функция Hough Lines OpenCV возвращает массив позиций линий с их координатами x1, x2, y1 и y2 на изображении.

rho и theta — расстояние и угловое разрешение, соответственно, нашей сетки в пространстве hough. Минимальные значения, которые принимает ро, составляют 1 и тета,пи/180 рад.

порог указывает минимальное количество «голосов» (пересечений строк в заданной точке), которое должен иметь кандидат строки, чтобы попасть на выход. Попробуйте 2.

np.array на данный момент просто заполнитель.

minLineLength – это минимальная длина в пикселях, которая считается линией.

maxLineGap — максимальное расстояние в пикселях между сегментами линий, которое мы разрешаем соединять.

Ааааа *барабанная дробь* Та дааа!

Наконец, давайте попробуем это на видео! Что такое видео, как не серия изображений? Поэтому мы просто применяем этот конвейер к серии изображений, чтобы увидеть наши результаты:

Если вы все еще торчите, несмотря на каламбуры MJ, отличная работа! (Я никогда не остановлюсь, мухаха!)
Определенно есть много областей для улучшения. Тем не менее, я хотел изучить некоторые из самых основных концепций компьютерного зрения на вводном уровне.

Надеюсь, вы тоже получите удовольствие от реализации своего пайплайна! Пожалуйста, свяжитесь с нами, если вы хотите обсудить что-либо, упомянутое здесь, и если у вас есть лучший способ подойти к этому. Спасибо!

Далее, глубокое обучение для классификации изображений. Мы будем использовать искусственный интеллект, чтобы вычислить немецкие дорожные знаки, следите за обновлениями!