Первый маленький шаг, который должен сделать автомобиль, чтобы управлять собой, — это увидеть мир. Не просто просмотрите его, но определите правила и информацию, которые ему нужны, чтобы позволить себе захватить мир! Шучу, это будет Скайнет, а не машины.
Видение и контроль — это то, что мы с вами считаем само собой разумеющимся. Когда мы едем по шоссе, мы почти инстинктивно распознаем разные полосы движения и ведем машину по ним.
Но что именно мы делаем на самом деле? Визуально наш мозг видит синюю дорогу с желтыми и белыми отметинами. Мы распознаем эти формы как толстые линии, идущие вертикально. Затем мы создаем ментальные рамки, чтобы удерживать в них наши автомобили. Чтобы беспилотный автомобиль делал то же самое, он должен сначала идентифицировать эти полосы движения. Он должен иметь возможность просматривать дорогу и выделять только линии, ограничивающие полосу движения.
Достаточно легко, верно? Что ж, я собираюсь рассмотреть очень простой метод, используя некоторые из самых основных и фундаментальных концепций компьютерного зрения, которые позволяют автомобилям смотреть на шоссе и определять полосы движения. Я проведу вас через мой процесс, и вы сможете попробовать его сами после!
Это будет серия постов в течение нескольких месяцев, кульминацией которых станет добавление кода в настоящий беспилотный автомобиль!
Конвейер поиска дорожек
- Черный или белый
Первое, что нам нужно сделать, это преобразовать цветное изображение в черно-белое. Поиск линий на изображении не зависит от цвета, поэтому мы можем просто убрать всю эту информацию и упростить алгоритм. Способ, которым изображения хранятся в цифровом виде в цвете, представляет собой расположение пикселей и их цветовых значений. Возьмите исходное изображение ниже, например, оно ограничено примерно 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, отличная работа! (Я никогда не остановлюсь, мухаха!)
Определенно есть много областей для улучшения. Тем не менее, я хотел изучить некоторые из самых основных концепций компьютерного зрения на вводном уровне.
Надеюсь, вы тоже получите удовольствие от реализации своего пайплайна! Пожалуйста, свяжитесь с нами, если вы хотите обсудить что-либо, упомянутое здесь, и если у вас есть лучший способ подойти к этому. Спасибо!
Далее, глубокое обучение для классификации изображений. Мы будем использовать искусственный интеллект, чтобы вычислить немецкие дорожные знаки, следите за обновлениями!