Привет, Хабр.

Меня зовут Михаил, и обычно в Itransition я выполняю роль Java-разработчика. Но иногда меня привлекают для RnD-процессов – в частности, связанных с ML и нейронными сетями. 

И сегодняшняя статья будет про учет и подсчет свиней при помощи современных технологий машинного зрения и машинного обучения.

Вообще, заголовок этой статьи должен был выглядеть так: “Средняя плотность свиной туши на квадратный пиксел”, но это было бы слишком длинно и непонятно. Во времена, когда интернет был прикован к проводам, появилось выражение, ставшее расхожим – “диагностика по аватарке”. Кто бы мне сказал, что спустя десяток лет я будут заниматься прототипированием этой самой “диагностики по аватарке” – я бы не поверил.

И.О. Деда Мороза приходит с сюрпризом

История эта, как и все невероятное, началась под Новый год. К нам обратился давний заказчик с весьма необычным запросом: ему хотелось с помощью видеокамеры обрабатывать свиней. Чтобы роботы, как пелось в детской песне, – вкалывали, а человек, хозяйствующий этих свиней, был счастлив.

–  Нам нужна идентификация и учет аппетита свинок с помощью камеры.
–  Как вы видите “учет аппетита с помощью камеры”? – уточнил я.
– Всё просто: камера смонтирована на потолке свинарника, смотрит на кормушку. Свиньи периодически подходят к кормушке и питаются. На первом этапе задача стоит “идентифицировать особей в кадре”.
– Есть какие-то метки?
– Да, цветные клипсы. Совершенно стандартная практика – крепятся по две на ухо, шесть цветов, итого – до тридцати шести комбинаций. На практике численность стада не превышает тридцати, так что хватает с избытком.

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

Школа железных мозгов

Небольшое лирическое отступление для тех, кто еще не знает, как проходит обучение нейросетей под задачи классификации: для начала вы берете большое количество образцов, например, фотографий, и для каждой определяете координаты прямоугольников с интересующими вас объектами. Руками. Вот IMG_023410282018.JPG, по координатам 0,0-230,168 у нас объект с тэгом “кошка”. И так далее. Пачка из размеченных фотографий называется набором.

Коллеги из data science утверждают, что чем больше обучающих образцов – тем лучше. Минимум десяток тысяч для обучения и пара тысяч – для контроля. Контрольный набор отделяется от обучающего, и после каждой итерации обучения сети из него берется некоторое число случайных образцов и нейросеть спрашивают: что ты здесь видишь? Ответ сравнивается с присвоенными контрольному набору метками, и по степени совпадения делаются выводы – хватит обучать или необходимо идти на следующую итерацию. 

Если в обучающем наборе образцов мало – сеть “недообучится”, будет часто путать объекты. Но, если пройти оптимум обучения – она может уже “переобучиться” и будет реагировать только на хорошо знакомые образцы. А уже немного отличные будут давать неопределенный или сбойный результат. Поэтому тут или разнообразие образцов, или их рандомная обработка в каждой итерации (поворот, растягивание, фильтрация и т.п.) перед обучением. А можно и то, и другое.

Есть целые бизнесы, построенные на потребности в подготовке наборов для обучения. Десятки людей кропотливо целыми днями размечают фотографии, которые им присылают заказчики, нуждающиеся в обучении нейросетей. Тут примерно как со сбором ягоды в сезон – нужно успеть в срок, поэтому привлекается много народу и не обязательно квалифицированного. 

Мы же тем временем решали архитектурные вопросы. Data science-команда рекомендовала попробовать open-source сеть YOLO третьей версии. Главным условием была возможность сети работать с аппаратным ускорением. Как выяснилось – у YOLO настолько много форков, что найти на гитхабе тот, что построен поверх TensorFlow, не было проблемой. Ну а TensorFlow, в свою очередь, хорошо “дружит” с Nvidia CUDA. Получился типовой ML-стек: CUDA/TensorFlow/Python.

Забегая наперед – для доставки MVP мы воспользовались Nvidia Container Runtime, который позволяет пробросить GPU в контейнеризованное приложение, а в качестве базового взяли образ TensorFlow также от Nvidia. Оставшаяся часть была довольно тривиальной – обернуть модель в REST API при помощи Flask и проверить хотя бы на “стоковой” конфигурации. А там подъехали и наборы данных от заказчика.

Закат солнца по Page Down

Наблюдать за поведением свинок мне, как человеку, выросшему вдали от деревень и выпасов, было весьма любопытно. Если зажать кнопку Page Down – можно было увидеть, как проходит “свинский” день жизни. Колорита добавляли солнечные блики на фотографиях, проползавшие из угла в угол в зависимости от времени суток. Эти же блики попили нам крови после того, как мы расправились с этапом определения местоположения меток в кадре.

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

И снова лирическое отступление. Обучение – процесс не только долгий, но и дорогой. И не только потому, что надо заплатить за разметку фотографий, но и за железо. Тут всего два варианта – пользоваться GPU-инстансом в облаке и надеяться, что стоимость аренды не вылетит из бюджета, либо купить топовую железку хотя бы потребительского класса (GTX, RTX) и воткнуть в импровизированный ML-сервер. И тут, в первую очередь, решают даже не терафлопсы, а гигабайты. Потому что развернутая TF-модель может не во всякий лимит памяти влезть. Меньше 10 гигабайт GDDR уже в 2019 году смысла брать не было. 

И хотя метки на Full HD кадре занимали примерно 80х150 пикселов, и при дефолтном размере скользящего окна YOLO в 204х204 это давало очень плохие результаты. Расширение до 800х800 исправило ситуацию, а вот на попытку воткнуть 1024х1024 CUDA сказала, что “у неё лапки” и высыпалась с ошибкой “ваш тензор не пролезает в мою память, умерьте пыл”.

А дальше оставалось только ждать. Потому что, если тебе нечего править в коде, а осталось только обучить сеть – это в чистом виде “режим Хатико” на несколько часов. Даже если у тебя субтоповая на тот момент RTX 2080. В день можно было провести три-четыре итерации, и еще одна на ночь, чтобы с утра оценить очередную попытку.

Наконец настал тот день, когда сеть у нас лихо справлялась с определением меток. И пришла очередь распознавать цвет. Теперь надо было вырезать пикселы и определить, к какому из эталонных цветов относится конкретная метка. Я говорил, что освещение попило нам кровь? Подержи моё пиво.

Цифровой дальтоник: инструкция по сборке

Самой очевидной идеей было измерить цветовое расстояние по RGB. Нам были известны HEX-коды цветов меток по каталогу Pantone, у нас был PyOpenCV, NumPy, Pandas… Не то, чтобы это все было нужно, но когда всерьез начал заниматься машинным обучением, – надо идти в своей одержимости до конца.

Цвета меток оказались неравномерно разбросаны по цветовому кубу, и, в случае различных вариаций освещения, вектор легко прилипал к ошибочному цвету, выдавая cyan вместо yellow, или magenta вместо red. Окей, с кубом не прокатило. Мы выкинули RGB и поставили цветовое колесо, нормализуя яркость. Помогло, но не сильно. В итоге, после пары убитых дней, мой датасатанист с воплем “Да как так-то!” отложил на полчаса свой текущий проект и быстренько набросал двухслойный перцептрон на шесть выходов. И вы знаете – сработало! Конечно, перцептрон тоже пришлось обучить, но это было уже сильно проще и быстрее.

Настал день икс. Мы сели показывать заказчику “товар лицом”. Чуть-чуть не дотянули по надежности распознавания до заданной. А вот с цветом уже проблем не было – эту планку мы взяли, о чем и было честно сказано и показано во время презентации. И через несколько дней заказчик принял решение стартовать второй этап.

Press F for next level

– В идеале нам надо по фотографии свиньи находить разность и вычислять привес или потерю веса. 
После созвона коллега озадаченно почесал бороду.

– Вес по фото. Прямо какая-то диагностика по аватарке. Это как? Это нам как-то нормализовать и усреднять объем свиньи по множеству её ракурсов? А потом считать плотность свиньи на квадратный пиксел?!
– Прекрасная метрика, – пробормотал я, – Пожалуй, запишу.

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

– Слушай, вы тут какую-то штуку для измерения свиней прототипировали…
– Да-да, могу в красках все рассказать!
– Расскажи на чем она у вас работает, мне ее портировать придется.
– Ну, э … Стандартный x86-64 Linux, TensorFlow, CUDA, Docker с пробросом GPU. А на что портировать?
– На Raspberry Pi 3. RTX 2070  дорого, говорят. Надо в пятьдесят долларов уложиться.

Что-то больно ударило меня по ноге, и это была моя челюсть. Забегая вперед – таки да, этот парень впихнул нашу модель на “малинку” и добился того, что распознавание на ней занимало не более десятка секунд – тогда как на видеокарте мы добились минимум 2-3 секунды. Но это уже другая история. Не переключайтесь.

Комментарии (13)


  1. SnakeSolid
    11.01.2022 16:06

    Хотелось бы услышать историю о том, почему на Raspberry Pi модель работала быстрее чем на видоекарте и как она вообще там работала.


    1. micza Автор
      11.01.2022 16:26

      Забегая вперед – таки да, этот парень впихнул нашу модель на “малинку” и добился того, что распознавание на ней занимало не более десятка секунд – тогда как на видеокарте мы добились минимум 2-3 секунды.

      Я нигде не говорил, что на "малинке" модель работала быстрее. Она в итоге стала работать сопоставимо - порядка десяти секунд. А на стоковой конфигурации (Rpi3 4Gb + Raspbian) она ворочалась примерно 40-60 секунд. Это будет в следующей части :)


      1. SnakeSolid
        11.01.2022 17:48

        Действительно. Мне, почему-то показалось, что на Raspberry получилось быстрее видеокарты. Тем не менее, даже 10 секунд для Raspberry - хороший результат.


  1. amarao
    11.01.2022 16:20

    Кубический пиксел называется voxel.

    ... Массу свиньи можно оценивать по инерции при движении на поворотах.


    1. micza Автор
      11.01.2022 16:33
      +2

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


      1. mSnus
        12.01.2022 10:55
        +3

        Толстая свинья = довольная свинья. Распознавайте эмоции!


        1. drWhy
          12.01.2022 11:09

          Тогда камера не нужна, достаточно микрофона.


  1. ainu
    11.01.2022 21:21

    Есть неплохая Nvidia Jetson Nano 2Gb, как раз 50 баксов (ну, почти, где покупать). И ускорение есть, и официальные докеры с пробросом ускорителя от нвидии, и тензофлоу образы. Только нюансы с сетью (родного вайфая нет, нужно или usb или LAN по витой паре), но это решаемо.


  1. diogen4212
    12.01.2022 12:27

    Может, на каждую свинью повесить индивидуальную RFID-метку (например, вместе с цветной клипсой на ухе или сделать метку цветной и тогда она сразу будет выполнять две функции) и детектировать, когда она подходит к кормушке? Дорого, но дороже ли обучения нейросети?


    1. vvzvlad
      13.01.2022 16:25

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


  1. Radisto
    12.01.2022 13:20

    Татуировку на спине в виде qr делать, а по абсолютному размеру оценивать, насколько выросла свинная спина ну вся свинья целиком


  1. gameplayer55055
    12.01.2022 21:06

    Raspberry pi может opencv использовали на предмет ярких штук? Удивительно, ведь у меня она даже видео 720 не особо тянет, а тут свиней различает.

    Как по мне нанять бабку для этого дела дешевле легче и проще намного


    1. vvzvlad
      13.01.2022 16:23
      +1

      Как по мне нанять бабку для этого дела дешевле легче и проще намного

      О, вы ошибаетесь. Точнее, не видите всей картины. В файте бабка vs малина, выиграет, конечно, бабка. Тем более если сравнивать стоимость разработки, она хорошо так покроет зарплату на бабки в поселке усть-пердюево на десяток-другой лет. Но если ты агрохолдинг, какой-нибудь мираторг, то у тебя уже задача 1)нанять 1000 бабок 2)обучить их и добиться нормального уровня обучения 3)контролировать качество 4)платить фот. И когда начинаете прикидывать бой «1000 малин + стоимость разработки» vs «1000 бабок с hr-структурой для обучения и сопровождения и фот всех этих людей», то малины выигрывают.