В статье покажем, как алгоритмы компьютерного зрения помогают решить задачу автоматического определения объема круглого леса в лесовозе по фотографии. Пройдем путь от идеи до прототипа. Расскажем, какие были выбраны решения и почему.
Необходимая подготовка читателя — должно быть общее представление о компьютерном зрении (computer vision) и нейронных сетях. Здесь не будет описаний, что такое сверточная нейронная сеть и т.п., статей по таким основам найдете много на хабре (вот хорошая Глубокое обучение для новичков: распознаем изображения с помощью сверточных сетей). В то же время, совсем новички могут получить представление, какие знания и компетенции нужны для решения подобных задач.
Применялись:
Keras/Tensorflow
OpenCV
YOLOv5
В статье ниже упоминаются еще всякие штуки.
По условию задачи требовалось определить объем бревен в лесовозе по фотографии с телефона. «Линейкой» будет автомобильный номер.
Размер автомобильного номерного знака определен в ГОСТ Р 50577-2018:
Для расчета объема нужна еще длина бревен — задается вручную.
Теперь по шагам, какие были выбраны решения и почему.
Алгоритм:
Обнаружить бревна
Обнаружить автомобильный номер, распознать и определить его размер на фото
Рассчитать диаметры бревен в см
С чего начать? Конечно посмотреть, какие уже есть решения и подходы. При этом не забыть заглянуть в ГОСТ 32594-2013 «Лесоматериалы круглые. Методы измерений».
В интернете нашлось несколько готовых решений для автоматического расчета количества и размеров бревен. Это дало понимание, что задача точно решаемая. Но готовых opensource по распознаванию кругляка не попалось.
Для обнаружения бревен предпочтение сразу отдали нейронным сетям. Алгоритмы классического машинного обучения не рассматривали, в computer vision задачах они проигрывают нейронным сетям с большим отрывом. Ниже есть пример, где это наглядно видно.
Обнаруживаем на изображении бревна
Если задуматься, то чем обнаружение отдельного бревна на изображении отличается от задачи обнаружения лица человека? Гипотеза, что решение, которое хорошо обнаруживает лица при соответствующем обучении справится и с бревнами, оказалась верной.
Остановились в итоге на решении YOLOv5. Оно не специализируется прям на лицах/бревнах, но выбрано т.к.:
найдены примеры, подтверждающие качественное обнаружение лиц с помощью YOLOv5
впечатляющая производительность (может работать с видеопотоком в режиме реального времени).
решение достаточно популярное и простое для внедрения
И не прогадали. Для тестирования было выбрано несколько фото, где бревен поменьше. Лучше разметить несколько разных фото с малым количеством бревен, чем много бревен на одном фото. Уже на трех(!) размеченных фото с лесовозами, на изображениях 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. Но у этого инструмента два существенных недостатка:
Для конвертации в формат YOLO пришлось использовать дополнительную библиотеку Labelme2YOLO.
Из-за отсутствия направляющих линий у курсора, неудобно делать ограничивающие рамки для круглых объектов.
Есть бесплатный онлайн инструмент https://www.makesense.ai/, который умеет сохранять сразу в формате YOLO.
Сейчас, все чаще используем для разметки решение Superannotate.
Обнаруживаем и распознаем автомобильный номер
К этой задаче подступались с мыслями: «Здесь точно проблем не будет. Решений, статей, описаний найдется миллион. Самое трудное будет выбрать лучшее из хороших«. И ошиблись. Самым трудным оказалось найти нормально работающее. Казалось бы, распознавание автомобильных номеров в computer vision, это как создание калькулятора в традиционном программировании.
После изучения темы сложилось впечатление, что решений по распознаванию номеров очень много, но бОльшая часть построена на алгоритмах классического машинного обучения и годится для решения задач со строгими условиями, например: номер чистый, положение номера +/- фиксированное (перед шлагбаумом или на расстоянии 20-30 метров от камеры), яркое освещение.
Хорошее решение на нейронных сетях сделали и развивают ребята nomeroff.net. Его использовали в первом прототипе. Плюс этого решения — сразу умеет возвращать координаты углов номера, которые как раз нужны для определения размеров. Nomeroff.net показал себя лучше классического ML.
Но именно для российских номеров работа nomeroff.net оказалась недостаточно качественной. Может быть дело в базе российских номеров, на которой обучали, нужна больше. Возможно причина еще в том, что у прицепов другая комбинация символов на номере, а в базе был другой, более распространенный тип номеров.
Здесь важно отметить, что нам нужно не просто решение обнаруживающее номера. Оно должно возвращать координаты углов номера и быть обучено на базе, которую размечали с целью использовать автомобильный номер как эталон размера. Нельзя просто обвести номер рамкой, как при разметке бревен.
Таких готовых решений не нашлось. Задачу поиска углов ставили перед собой далеко не все авторы. Вот пример, который справляется с распознаванием бельгийских номеров без поиска углов, с использованием YOLOv4 и сверточной сети https://medium.com/@theophilebuyssens/license-plate-recognition-using-opencv-yolo-and-keras-f5bfe03afc65 (eng)
Попадались также решения, которые потенциально можно адаптировать. Вот пример made in chine (ch):
Отмечу, что годных статей по ИИ на китайском языке, или английском, но с китайскими авторами, попадалось очень много. Вывод здесь делайте сами. PS: славянские фамилии тоже встречаются часто, в т.ч. в признанных сообществом решениях (тот же yolo, albumentation и др.), что лично нас радует.
Готового и полностью устраивающего решения не нашлось, поэтому стали делать свое. Тут пришлось окунуться в удивительный мир разных подходов. Только основная задача у них — распознать номер. А нам надо еще точные координаты рамки.
Как распознавать номерные знаки
Сначала про распознавание номерного знака. У этой задачи больше практической ценности, чем у определения точных координат углов. Оговорюсь, что не считаю себя экспертом в ANPR. Если среди читателей найдутся специалисты, которые съели на этом собаку, и поправят/дополнят меня, буду только благодарен.
Современные подходы распознавания номеров сводятся к следующему алгоритму:
Найти на изображении автомобильный номер (сегментация или object detection с помощью нейронной сети)
Выделить на номере отдельные буквы/цифры. Перед этим можно сделать геометрические преобразования, чтобы убрать искажения перспективы и упростить дальнейшее OCR.
Распознать буквы и цифры с помощью нейронной сети или решений типа Tesseract. Но опять же, нейронные сети обогнали классический ML в таких задачах, поэтому на Tesseract я бы время не стал тратить.
Если нужен OCR, посмотрите в сторону решений Распознавание изогнутого текста (рус). В нашем случае текст не изогнутый, но статью рекомендую, если планируете заниматься темой распознавания текста. Читать было очень интересно.
Мы поставили перед собой цель не просто распознавать номер, а делать это не хуже человека. Даже если номер грязный или размытый. Для такого обучения нужна внушительная база. Мысль о необходимости размечать отдельные символы на нескольких тысячах фотографий номеров приводила в уныние.
Поэтому решено проверить сначала две гипотезы:
Обучать на сгенерированных изображениях номеров.
Номер распознавать целиком, без разметки отдельных символов.
Обе гипотезы подтвердились. Тестовый результат даже превзошел ожидания:
Проверили также распознавание на изображении из примера выше. Правда нейронная сеть здесь после дополнительной доработки:
Как сгенерировать базу номеров
Можно взять готовый SVG файл и менять в нем знаки номера (https://github.com/ulex/generate_license_plates).
Купить скрипт (например здесь avto-nomer.ru).
Сделать свой генератор.
Чтобы быть уверенным, что все по ГОСТу, выбрали третий путь (свой генератор).
Раньше Вы возможно не обращали внимание, что автомобильные номера бывают разных типов. Конечно, возможно, знали, что у полиции номера синие, на маршрутках бывают желтые, а у мотоцикла квадратные. Но то что у них еще и разная комбинация букв-цифр обращают внимание не все.
Изображение номеров в формате 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 изображениях (на некоторых изображениях было больше одного номера) результат уже приемлемый:
Сеть ищет отдельно углы «левый верхний», «левый нижний», «правый верхний», «правый нижний».
Результат и выводы
Следующим шагом подбираются гиперпараметры, делается «тюнинг» архитектур и проводится обучение нейронных сетей на расширенной базе. Потом все модули объединяются в единое решение.
Финальное решение работает следующим образом:
Первая YOLOv5 обнаруживает бревна.
Вторая YOLOv5 обнаруживает автомобильный номер. Фрагмент с номером (размером 128х128) передается для точного определения углов и распознавания.
Сеть на базе InceptionResNetV2 распознает номерной знак.
Сеть на базе ResNet50 определяет углы номерного знака.
Вычисляется диаметр бревен, площадь и объем, опираясь на координаты углов номера.
Описан пилотный проект, с большим потенциалом для оптимизаций.
В следующей части есть планы рассказать про интеграцию в телеграмм бот.
И у нас осталась задача определения сортности и сравнения лесовозов.
Группа авторов: Дмитрий Мокачев и Георгий Брегман
Комментарии (17)
Yurik79
18.04.2022 21:51У нас тут в городе, в Новосибирске, мало машин с чистыми, , читаемыми номерами.
В городе... Ожидать от лесовозов вообще возможность найти контур номера - фантастика.
Грузовые машины(и прицепы) +/- можно сгруппировать по размеру кузова/ширины моста. На той же нейросети натренироваться на определение размера по контуру - больше приближение к рабочему варианту даст. Имх.
Mdm3 Автор
18.04.2022 22:33+2Протереть номер не сложно, если это требуется для сдачи груза установленными правилами.
Считать размер по расстояниям между колёсами, фарами и пр. думаю тоже рабочий вариант. Надо только учесть разную плоскость у бревен и условного моста. И большую базу собрать будет сложнее.
intersolar
19.04.2022 01:13+2По ГОСТ'у водитель лесовоза должен протирать номер перед приемкой груза.
Yurik79
20.04.2022 06:22По госту, машина не должна иметь неисправностей. Не должно быть подтёков масла, не должно быть люфта рулевой, фары должны светить и т.д.
В реальности ничего не соблюдается.
Если делается система, которая должна облегчать контроль, путём избегания ошибок и сложностей для обслуживания участниками, то надо и делать такую систему, а не надеяться на госты, которые нарушаются налево и направо.
Номер по госту имеет размеры и читаемость, в жизни они помятые и стёртые. Машина может быть с прицепом(на фотографии есть такой пример) и на буксире номера не видно за прицепом, как и торцов брёвен.
С таким подходом, можно налеплять/прислонять на машину рамку контрольную, с ней фотографировать, а от неё отталкиваться в вычислении.
Но так и не понятно, как по фотографии сзади, высчитать объем леса, т.к как уже отметили, нет понимания его геометрического размера в куче.
Вобщем статья интересная из-за технических деталей обучения и подхода, но применение практическое как-то плохо видится в таком подходе.
Зы: отвечать раз в сутки могу, потому молчал, хотя были сразу замечания.
Mdm3 Автор
20.04.2022 10:12Вы правы, что для встраивания такой автоматизации в бизнес-процесс надо еще решить ряд вопросов.
Но как раз с протиранием номера проблем не видится. На примере складов Wildberries, там автоматический шлагбаум, который открывается если номер авто есть в заказанном электронном пропуске. И вы не найдете ни одного авто на приемке с грязными номерами)
Advocatt
19.04.2022 10:25Очень перспективное с точки зрения комерциализации направление.
встречался с подобной проблемой.
Есть некоторые вопросы, особенно по в ГОСТ 32594-2013 .
Предположим вы внедрили свою программу на складе пиломатерьялов:
1 - какая погрешность по отношению к объему леса, а не вашей экспертной оценки картинок?
2 - как вы учитываете требования ГОСТа по конусности, в зависимости от сорта древесины?
3 - Как вы делали разметку - я имею ввиду измерения диаметра стволов на лесовозе?
4 - у вас идеально уложенные лесовозы, в реальности все стволы разной длинны.
5 - Как ваш ИИ научился работать с точностью измерений больше см?
6 - ну вот вы старательно фотографируете, и по расчету площади выставляете счета заказчикам, кто то из заказчиков проверяет ваши расчеты и естественным образом выдвигает вам претензию - привезли леса меньше чем в накладной, Как вы будете переучивать вашу систему если у вас нет размеченных данных по ошибочным лесовозам?
7 - ну и как в налоговой и суде вы будете обьяснять расхождения с реальными измерениями?
Mdm3 Автор
19.04.2022 12:13Средняя точность в сравнении с ручным методом измерения находится в пределах 2% с выбросами до 6%. Проверялось на 12 лесовозах. Также проверяли этим методом длину приложенной метровой линейки, ошибка получилась в пределах 3%
В пилотном решении разную конусность у разных пород не учитывали. Но можно научить сеть определять сорт и применять соответствующий коэффициент по таблицам ГОСТа.
Размечали рамками по контуру бревна, упоминается в статье. В ТЗ учитывалось перекрытие, нецелые бревна и прочее.
Да, влияние разной длины бревен будет как у ручного измерения без разгрузки лесовоза. Это ограничение.
Не понял вопрос. ИИ счтитает с тоностью предусмотренной типом float) Дальше вопрос округления и размера ошибки.
1) Добавление ручной корректировки, если визуально видно ошибку. 2) Дообучение, если ошибки возникают слишком часто.
Есть ограничение любого решения ИИ - это черный ящик, и как получен результат ИИ объяснить не может. Т.е. использовать для судов и прочее не получится. Также как распознавание преступника на камере не может служить 100% доказательством вины. Но упростить ручной расчет вполне можно.
RGrimov
19.04.2022 23:06+1Отличная статья!
Использовать номерной знак как линейку - оригинальная идея.
Несколько замечаний:Для расширения тренировочной базы применена аугментация
Это необязательно было делать. В самом train скрипте YoLOv5 уже есть подбор аугментаций.
Важно учитывать, что не все варианты аугментации одинаково полезны. Например в нашей задаче поворот изображения на случайный угол собьет координаты рамки относительно контура бревна и ухудшит точность определения размеров
Albumentations умеет корректно работать с рамками при поворотах.
Mdm3 Автор
20.04.2022 08:47Спасибо за положительный отзыв и полезный комментарий.
YoLOv5 уже есть подбор аугментаций.
Обучающая база состояла сначала из трех, потом 7 фото лесовозов. Понадобилось расширять базу до подачи в YOLO. Часть аугментации в самой YOLO отключал. И хотелось визуально контролировать на чем обучается сеть. Можно конечно было попробовать отдавать копии изображений и аугментировать самой YOLO. Надо проверить, интересно сравнить.
Albumentations умеет корректно работать с рамками при поповоротах.
чтобы повернуть рамку вокруг круглого объекта надо понимать, что внутри рамки именно круг. Иначе рамка увеличится. Но такой параметр нигде не задаётся. Или есть способ зафиксировать размер bbox при повороте? ????
DV73
21.04.2022 09:25Как при подсчёте кругляка учитывается, то что не все стволы деревьев погружены вершинками в одну сторону? А кубатуру ствола меряют с вершины.
Mdm3 Автор
21.04.2022 11:06кубатуру ствола меряют с вершины
Измерение по верхнему диаметру и среднему сбегу один из методов. Нам не подходит, т.к. стволы уложены "вальтом". Такие вводные от заказчика.
Проверка объема рассчитанная на основе диаметров бревен (1го сорта) с торца в нашем случае показала, что при правильном подборе коэффициентов автоматического расчета он соответствует объему рассчитанному способом "Групповой метод определения объема бревен в штабеле, сформированном на железнодорожном и автомобильном транспорте".
Kavazakii
Есть много аутсорс фирм, которые и занимаются обработкой фоток по подобным параметрам. Например, отмечать дорожные знаки/разметку и много чего другого, в том числе и лидары. Как я понимаю, большинство из такого идёт на гуглы и тд, для обучения машин(в которой я работал делали для обучения безпилотных автомобилей(тут прям был запар с слоированием изображения, тоесть выделением всего что есть на картинке отдельными категориями и раставлять в порядке приближения) и много чего другого). Есть и прикольные случаи, например надо было для експеримента пить пиво весь день! Тоесть по определёному графику ты выпиваешь некоторую порцию алкоголя и потом перед камерой выполняешь одни и те же действия. Или же когда надо было отмечать на видео расположение конечностей людей(там один и тот же сюжет был, кароч мол идёт кто-то и на него нападают: то ножом пырнут, то ещё чем) таких видео уйма была и всё по-кадрово отмечалось. Думаю можна догадатся для чего подобное может понадобится.
Работа интересная была, жаль платили не очень, продержался там 2 года только из-за команды
Yu-Ree
В Яндекс-Толоке подобные задания есть.