Pull to refresh

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

Level of difficultyMedium
Reading time9 min
Views16K
Полученный результат
Полученный результат

В статье покажем, как алгоритмы компьютерного зрения помогают решить задачу автоматического определения объема круглого леса в лесовозе по фотографии. Пройдем путь от идеи до прототипа. Расскажем, какие были выбраны решения и почему.

Необходимая подготовка читателя — должно быть общее представление о компьютерном зрении (computer vision) и нейронных сетях. Здесь не будет описаний, что такое сверточная нейронная сеть и т.п., статей по таким основам найдете много на хабре (вот хорошая Глубокое обучение для новичков: распознаем изображения с помощью сверточных сетей). В то же время, совсем новички могут получить представление, какие знания и компетенции нужны для решения подобных задач.

Применялись:

  • Keras/Tensorflow

  • OpenCV

  • YOLOv5

В статье ниже упоминаются еще всякие штуки.

По условию задачи требовалось определить объем бревен в лесовозе по фотографии с телефона. «Линейкой» будет автомобильный номер.

Размер автомобильного номерного знака определен в ГОСТ Р 50577-2018:

Для расчета объема нужна еще длина бревен — задается вручную. 

Теперь по шагам, какие были выбраны решения и почему.

Алгоритм:

  1. Обнаружить бревна

  2. Обнаружить автомобильный номер, распознать и определить его размер на фото

  3. Рассчитать диаметры бревен в см

С чего начать? Конечно посмотреть, какие уже есть решения и подходы. При этом не забыть заглянуть в ГОСТ 32594-2013 «Лесоматериалы круглые. Методы измерений».

В интернете нашлось несколько готовых решений для автоматического расчета количества и размеров бревен. Это дало понимание, что задача точно решаемая. Но готовых opensource по распознаванию кругляка не попалось.

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

Обнаруживаем на изображении бревна

Если задуматься, то чем обнаружение отдельного бревна на изображении отличается от задачи обнаружения лица человека? Гипотеза, что решение, которое хорошо обнаруживает лица при соответствующем обучении справится и с бревнами, оказалась верной.

Остановились в итоге на решении YOLOv5. Оно не специализируется прям на лицах/бревнах, но выбрано т.к.:

  1. найдены примеры, подтверждающие качественное обнаружение лиц с помощью YOLOv5

  2. впечатляющая производительность (может работать с видеопотоком в режиме реального времени).

  3. решение достаточно популярное и простое для внедрения

И не прогадали. Для тестирования было выбрано несколько фото, где бревен поменьше. Лучше разметить несколько разных фото с малым количеством бревен, чем много бревен на одном фото. Уже на трех(!) размеченных фото с лесовозами, на изображениях 320х320, yolo показала свой потенциал. То что yolo прекрасно справится с задачей, сомнений не осталось.

Фото слева попало в тестовую обучающую выборку, правое нет
Фото слева попало в тестовую обучающую выборку, правое нет

Для тренировки YOLOv5 необходимо для каждого изображения создать текстовый файл с тем же именем, но расширением .txt. В файл записывается номер класса объекта и координаты рамки (bounding box):

<object-class> <x_center> <y_center> <width> <height>

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

Пример содержимого файла разметки:

0 0.263172 0.139302 0.093695 0.09838
0 0.310295 0.219907 0.095899 0.094797
0 0.40275 0.224041 0.083499 0.090939
0 0.53971 0.11891 0.104993 0.116567
0 0.502508 0.211089 0.097828 0.093144

Обратите внимание, что координаты относительные, в интервале от 0 до 1.

Для расширения тренировочной базы применена аугментация (получение новых изображений с помощью случайного сдвига, масштабирования, растягивания исходных изображений). С этой задачей отлично справилась библиотека Albumentations  — простая в использовании и функциональная.

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

 Новое положение рамки относительно бревна после поворота. Так делать не надо!
Новое положение рамки относительно бревна после поворота. Так делать не надо!

От качества разметки зависит многое. А в задаче с определением точных размеров тем более. Поэтому к разметке подошли серьезно, подготовили ТЗ.

Один из пунктов ТЗ на разметку
Один из пунктов ТЗ на разметку

Тестовые изображения были размечены в бесплатном labelme. Но у этого инструмента два существенных недостатка:

  1. Для конвертации в формат YOLO пришлось использовать дополнительную библиотеку Labelme2YOLO

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

Есть бесплатный онлайн инструмент https://www.makesense.ai/, который умеет сохранять сразу в формате YOLO. 

Сейчас, все чаще используем для разметки решение Superannotate.

Обнаруживаем и распознаем автомобильный номер

К этой задаче подступались с мыслями: «Здесь точно проблем не будет. Решений, статей, описаний найдется миллион. Самое трудное будет выбрать лучшее из хороших«. И ошиблись. Самым трудным оказалось найти нормально работающее. Казалось бы, распознавание автомобильных номеров в computer vision, это как создание калькулятора в традиционном программировании.

После изучения темы сложилось впечатление, что решений по распознаванию номеров очень много, но бОльшая часть построена на алгоритмах классического машинного обучения и годится для решения задач со строгими условиями, например: номер чистый, положение номера +/- фиксированное (перед шлагбаумом или на расстоянии 20-30 метров от камеры), яркое освещение.

Не всегда номера распознаются идеально
Не всегда номера распознаются идеально

Хорошее решение на нейронных сетях сделали и развивают ребята nomeroff.net. Его использовали в первом прототипе. Плюс этого решения — сразу умеет возвращать координаты углов номера, которые как раз нужны для определения размеров. Nomeroff.net показал себя лучше классического ML.

 Классический ML (слева) безнадежно проигрывает нейронным сетям (справа).
Классический ML (слева) безнадежно проигрывает нейронным сетям (справа).

Но именно для российских номеров работа nomeroff.net оказалась недостаточно качественной. Может быть дело в базе российских номеров, на которой обучали, нужна больше. Возможно причина еще в том, что у прицепов другая комбинация символов на номере, а в базе был другой, более распространенный тип номеров.

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

Таких готовых решений не нашлось. Задачу поиска углов ставили перед собой далеко не все авторы. Вот пример, который справляется с распознаванием бельгийских номеров без поиска углов, с использованием YOLOv4 и сверточной сети https://medium.com/@theophilebuyssens/license-plate-recognition-using-opencv-yolo-and-keras-f5bfe03afc65 (eng)

Попадались также решения, которые потенциально можно адаптировать. Вот пример made in China (ch):

Отмечу, что годных статей по ИИ на китайском языке, или английском, но с китайскими авторами, попадалось очень много. Вывод здесь делайте сами. PS: славянские фамилии тоже встречаются часто, в т.ч. в признанных сообществом решениях (тот же yolo, albumentation и др.), что лично нас радует.

Готового и полностью устраивающего решения не нашлось, поэтому стали делать свое. Тут пришлось окунуться в удивительный мир разных подходов. Только основная задача у них — распознать номер. А нам надо еще точные координаты рамки.

Как распознавать номерные знаки

Сначала про распознавание номерного знака. У этой задачи больше практической ценности, чем у определения точных координат углов. Оговорюсь, что не считаю себя экспертом в ANPR. Если среди читателей найдутся специалисты, которые съели на этом собаку, и поправят/дополнят меня, буду только благодарен.

Современные подходы распознавания номеров сводятся к следующему алгоритму:

  1. Найти на изображении автомобильный номер (сегментация или object detection с помощью нейронной сети)

  2. Выделить на номере отдельные буквы/цифры. Перед этим можно сделать геометрические преобразования, чтобы убрать искажения перспективы и упростить дальнейшее OCR.

  3. Распознать буквы и цифры с помощью нейронной сети или решений типа Tesseract. Но опять же, нейронные сети обогнали классический ML в таких задачах, поэтому на Tesseract я бы время не стал тратить.  

Если нужен OCR, посмотрите в сторону решений Распознавание изогнутого текста (рус). В нашем случае текст не изогнутый, но статью рекомендую, если планируете заниматься темой распознавания текста. Читать было очень интересно.

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

Поэтому решено проверить сначала две гипотезы:

  1. Обучать на сгенерированных изображениях номеров.

  2. Номер распознавать целиком, без разметки отдельных символов.

Обе гипотезы подтвердились. Тестовый результат даже превзошел ожидания:

Предсказание после обучения на 5 тыс. сгенерированных номерах
Предсказание после обучения на 5 тыс. сгенерированных номерах
Распознавание реальных номеров сетью обученной на "синтетических" данных
Распознавание реальных номеров сетью обученной на "синтетических" данных
Архитектура сети, на которой проверялась гипотеза
Архитектура сети, на которой проверялась гипотеза

Проверили также распознавание на изображении из примера выше. Правда нейронная сеть здесь после дополнительной доработки:

Как сгенерировать базу номеров

  1. Можно взять готовый SVG файл и менять в нем знаки номера (https://github.com/ulex/generate_license_plates).

  2. Купить скрипт (например здесь avto-nomer.ru).

  3. Сделать свой генератор.

Чтобы быть уверенным, что все по ГОСТу, выбрали третий путь (свой генератор).

Раньше Вы возможно не обращали внимание, что автомобильные номера бывают разных типов. Конечно, возможно, знали, что у полиции номера синие, на маршрутках бывают желтые, а у мотоцикла квадратные. Но то что у них еще и разная комбинация букв-цифр обращают внимание не все.

Типы российских номеров (взято здесь https://49.img.avito.st/640x480/3328866249.jpg)
Типы российских номеров (взято здесь https://49.img.avito.st/640x480/3328866249.jpg)

Изображение номеров в формате SVG создавалось с помощью библиотеки drawSvg. Пример кода, генерирующего номер:

import drawSvg as draw
plate_text = 'K627PO790'
 
plate_w = 520   # width 
plate_h = 112   # height
th = 8          # thickness
 
plate_pos, plate_reg_w = ([59, 88, 142, 196, 275, 329, 372, 2], 160)
k_offset = {'K':36, 'P':252, 'O':302}
 
d = draw.Drawing(plate_w, plate_h, displayInline=False)
 
# Draw rectangle
r = draw.Rectangle(0,0,plate_w,plate_h, fill='#000000', rx=14, ry=14)
d.append(r)
 
base_colour = '#ffffff'
reg_colour = '#ffffff'
 
r = draw.Rectangle(th,th,plate_w-th,plate_h-th, fill=base_colour, rx=10, ry=10)
d.append(r)
 
# Draw region part
r = draw.Rectangle(plate_w-plate_reg_w,0,plate_reg_w,plate_h, fill='#000000', rx=9, ry=9)
d.append(r)
 
r = draw.Rectangle(plate_w-plate_reg_w+th,th,plate_reg_w-th,plate_h-th, fill=reg_colour, rx=5, ry=5)
d.append(r)
 
r = draw.Rectangle(0,0,plate_w-plate_reg_w+th//4,plate_h, fill='#000000', rx=9, ry=9)
d.append(r)
 
r = draw.Rectangle(th,th,plate_w-plate_reg_w-th//4,plate_h-th, fill=base_colour, rx=5, ry=5)
d.append(r)
 
 
# How many simbols
plate_len = 6
 
# Draw text
font_style = 'font-family:RoadNumbers'
 
for i, s in enumerate(plate_text[:plate_len]):
  if s.isdigit():
    d.append(draw.Text(s, 118, plate_pos[i], 16, fill='black', style=font_style))  # Text
  else:
    d.append(draw.Text(s, 118, k_offset[s], 16, fill='black', style=font_style))  # Text
 
# Text region
font_style = f'letter-spacing:{plate_pos[plate_len+1]}px;font-family:RoadNumbers'
d.append(draw.Text(plate_text[plate_len:], 94,  plate_pos[plate_len], 36, fill='black', style=font_style)) Text
 
# Draw flag
flag_style="stroke-width:0.4;stroke:rgb(0,0,0)"
r = draw.Rectangle(465,12,38,21, fill='#ffffff', style=flag_style)
d.append(r)
 
flag_style="stroke-width:0.4;stroke:rgb(0,0,0)"
r = draw.Rectangle(465,19,38,7, fill='#00f')
d.append(r)
 
flag_style="stroke-width:0.4;stroke:rgb(0,0,0)"
r = draw.Rectangle(465,12,38,7, fill='#f00')
d.append(r)
 
# draw RUS
font_style = 'font-style:normal;letter-spacing:2px;font-stretch:normal;font-family:Arial'
d.append(draw.Text('RUS', 28, 400, 12, fill='black', style=font_style))  
 
# Draw circle
d.append(draw.Circle(20, 56, 4,
            fill='gray', stroke_width=1, stroke='black'))
 
d.append(draw.Circle(500, 56, 4,
            fill='gray', stroke_width=1, stroke='black'))
 
d
Результат работы скрипта
Результат работы скрипта

Как найти углы номера

Попытка научить yolo искать углы номера ни к чему не привела. Поиск углов в итоге сделан на обученной с нуля Resnet-50 (предобученная давала хуже результат). После обучения на 103 изображениях (на некоторых изображениях было больше одного номера) результат уже приемлемый:

Сеть ищет отдельно углы «левый верхний», «левый нижний», «правый верхний», «правый нижний».

Результат и выводы

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

Иллюстрация разработки нашего AI-решения
Иллюстрация разработки нашего AI-решения

Финальное решение работает следующим образом:

  1. Первая YOLOv5 обнаруживает бревна.

  2. Вторая YOLOv5 обнаруживает автомобильный номер. Фрагмент с номером (размером 128х128) передается для точного определения углов и распознавания.

  3. Сеть на базе InceptionResNetV2 распознает номерной знак.

  4. Сеть на базе ResNet50 определяет углы номерного знака.

  5. Вычисляется диаметр бревен, площадь и объем, опираясь на координаты углов номера.

Описан пилотный проект, с большим потенциалом для оптимизаций.

В следующей части есть планы рассказать про интеграцию в телеграмм бот.

И у нас осталась задача определения сортности и сравнения лесовозов.

Группа авторов: Дмитрий Мокачев и Георгий Брегман

Tags:
Hubs:
Total votes 26: ↑25 and ↓1+26
Comments21

Articles