[Перевод] Подсчёт пчёл нейросетью на Raspberry Pi
Опубликовано 17 мая 2018 года
Сразу после установки улея я подумал: «Интересно, как подсчитать количество прилетающих и улетающих пчёл?»
Небольшое исследование показало: похоже, до сих пор никто не придумал хорошей неинвазивной системы, решающей эту задачу. А ведь было бы наверное полезно иметь такую информацию для проверки здоровья улья.
Во-первых, нужно собрать образцы данных. Raspberry Pi, стандартная камера Pi и солнечная панель: этого простого оборудования достаточно, чтобы записывать один кадр каждые 10 секунд и сохранять 5000+ изображений в день (с 6 утра до 9 вечера).
Ниже пример изображения… Сколько пчёл вы можете сосчитать?
Во-вторых, нужно сформулировать проблему, что конкретно делать нейронной сети. Если задача «подсчёт пчёл в изображении», то можно попробовать получить конкретные числа, но это не кажется самым простым вариантом, да и отслеживание отдельных пчёл между кадрами не доставляет никакого удовольствия. Вместо этого я решил сосредоточиться на локализации каждой пчелы в изображении.
Быстрая проверка стандартного покадрового детектора не дала особых результатов. Это как бы не удивительно, особенно учитывая плотность пчёл вокруг входа в улей (подсказка: перенос обучения не всегда работает), но это нормально. Итак, у меня очень маленькое изображение, только один класс для распознавания объектов и нет особых проблем с ограничивающим прямоугольником как таковым. Просто решить, есть пчела или нет. Какое решение будет попроще?
v1: полностью свёрточная сеть «пчела есть/нет» на фрагменте
Первым быстрым экспериментом стал детектор «пчела на картинке есть/нет». То бишь какова вероятность, что на данном фрагменте изображения есть по крайней мере одна пчела. Делать это в виде полностью сверточной сети на очень маленьких фрагментах картинки означает, что можно легко обработать данные в полном разрешении. Подход вроде работал, но терпел неудачу для района входа в улей с очень большой плотностью пчёл.
v2: изображение RGB → чёрно-белое растровое изображение
Я быстро понял, что задачу можно свести к проблеме трансформации изображения. На входе сигнал камеры RGB, а на выходе — изображение одиночного канала где «белый» пиксель обозначает центр пчёлы.
RGB-вход (фрагмент) и одноканальный выход (фрагмент)
Третий шаг — маркировка, то есть присвоение обозначений. Не слишком сложно развернуть небольшое приложение TkInter для выбора/отмены выбора пчёл на изображении и сохранения результатов в базе данных SQLite. Я потратил довольно много времени, чтобы правильно настроить этот инструмент: любой, кто выполнял вручную существенный объём маркировки, меня поймёт :/
Позже мы увидим, к счастью, что на большом количестве образцов можно получить довольно хороший результат полуавтоматическими методами.
Архитектура сети — вполне стандартная u-net.
- полностью свёрточная сеть обучена на фрагментах с половинным разрешением, но работает на изображениях с полным разрешением;
- кодирование представляет собой последовательность из четырёх свёртываний 3×3 с шагом 2
- декодирование — последовательность изменений размера по ближайшим соседям + свёртывание 3×3 с шагом 1 + пропуск соединения от кодеров;
- окончательный слой свёртывания 1×1 с шагом 1 с активацией сигмоидной функции (то есть двоичный выбор «пчела есть/нет» для каждого пикселя).
После некоторых эмпирических экспериментов я решил вернуться к декодированию с половинным разрешением. Его было достаточно.
Я сделал декодирование через изменение размера по ближайшим соседям вместо деконволюции скорее по привычке.
Сеть обучалась методом Adam и оказалась слишком мала, чтобы применить пакетную нормализацию. Дизайн оказался на удивление простым, хватило небольшого количества фильтров.
Я применил стандартный метод аугментации данных, случайное вращение и искажение цвета. Обучение на фрагментах означает, что мы по сути получаем вариант случайной нарезки изображения. Я не поворачивал изображения, потому что камера всегда стоит на одной стороне улья.
Тут есть некоторый нюанс в постобработке предсказаний на выходе. С вероятностной выдачей мы получаем размытое облако там, где могут быть пчёлы. Чтобы преобразовать его в чёткую картинку по одному пикселю на пчелу, я добавил пороговое значение, учёт связанных компонентов и обнаружение центроидов с помощью модуля skimage measure. Всё это пришлось устанавливать вручную и настраивать чисто на глаз, хотя теоретически его можно добавить в конец стека как элемент обучения. Может, есть смысл сделать это в будущем… :)
Вход, необработанная выдача и центроиды кластеров
За один день
Первоначально эксперименты велись с изображениями за короткий период в течение одного дня. Оказалось легко получить хорошую модель на этих данных с небольшим количеством промаркированных изображений (около 30).
Три образца, полученные в первый день
За много дней
Всё стало сложнее, когда я начал учитывать более длительные периоды в несколько дней. Одно из ключевых отличий — разница в освещении (время суток и разная погода). Другая причина — то, что я каждый день устанавливал камеру вручную, просто приклеивая её липучкой. Третьим и самым неожиданным отличием стало то, что при росте травы бутоны одуванчиков выглядят как пчёлы (то есть в первом раунде обученная модель не видела бутонов, а потом они появились и обеспечили непрерывный поток ложноположительных срабатываний).
Бóльшую часть проблем удалось решить аугментацией данных, и ни одна проблема не стала критичной. В целом данные не слишком варьируются. Это замечательно, потому что позволяет ограничиться простой нейросетью и схемой обучения.
Образцы, полученные за три дня
На изображении показан пример прогноза. Интересно отметить, что тут намного больше пчёл, чем на любом изображении, которые я маркировал вручную. Это отличное подтверждение, что полностью свёрточный подход с обучением на небольших фрагментах действительно работает.
Сеть работает нормально в большом диапазоне вариантов. Полагаю, здесь помогает однообразный фон, а запуск сети на каком-то произвольном улье не даст такой хороший результат.
Слева направо: высокая плотность вокруг входа; пчёлы разного размера; пчёлы на высокой скорости!
Полуконтролируемое обучение
Возможность получения большого количества изображений сразу наталкивает на мысль использовать полуконтролируемое обучение.
Очень простой подход:
- Съёмка 10 000 изображений.
- Маркировка 100 изображений и обучение
model_1
. - Использование
model_1
для маркировки остальных 9900 изображений. - Обучение
model_2
на «размеченных» 10 000 изображений.
В результате model_2
показывает лучший результат, чем model_1
.
Вот пример. Обратите внимание, что model_1
демонстрирует некоторые ложноположительные (слева посередине и травинка) и ложноотрицательные срабатывания (пчёлы вокруг входа в улей).
Слева model_1, справа model_1
Маркировка путём исправления плохой модели
Подобные данные также являются отличным примером, как исправление плохой модели происходит быстрее, чем маркировка с нуля…
- Помечаем 10 изображений и обучаем модель.
- Используем модель для разметки следующих 100 изображений.
- Применяем инструмент маркировки для исправления меток на этих 100 изображениях.
- Переобучаем модель на 110 картинках.
- Повторяем…
Это очень распространённый шаблон обучения, и иногда он заставляет немного пересмотреть свой инструмент маркировки.
Возможность обнаружения пчёл означает, что мы можем их посчитать! И нарисовать ради удовольствия прикольные графики, которые показывают количество пчёл в течение дня. Мне нравится, как они трудятся весь день и возвращаются домой около 4 вечера. :)
Запуск модели на Pi был важной частью этого проекта.
Непосредственно на железе Pi
Изначально планировалось заморозить граф TensorFlow и просто напрямую запустить его на Pi. Это работает без проблем, но вот только Pi снимает лишь 1 изображение в секунду. :/
Запуск на вычислительном модуле Movidius
Меня очень заинтересовала возможность запустить модель на Pi с помощью Movidus Neural Compute Stick. Это удивительный гаджет.
К сожалению, ничего не получилось :/. API для преобразования графа TensorFlow в их внутренний формат модели не поддерживает мой способ декодирования. Поэтому пришлось увеличивать размер (upsizing), используя деконволюцию вместо изменений размера по ближайшим соседям. Здесь нет проблем кроме той, что ничего не получилось. Возникает куча маленьких сложностей, из-за которых множились баги. Когда их исправят, можно вернуться к этой теме…
Модель v3: изображение RGB → подсчёт пчёл
Это привело меня к третьей версии модели: можем ли мы перейти непосредственно со входа RGB на подсчёт пчёл? Так мы избежим любых проблем с неподдерживаемыми операциями на Movidus Neural Compute Stick, хотя маловероятно, что результат получится таким же хорошим, как в модели центроидов v2.
Сначала я опасался пробовать этот метод: я думал, что для него потребуется гораздо больше маркировки (это больше не система на основе фрагментов). Но! Имея модель, которая довольно хорошо справляется с поиском пчёл, и много немаркированных данных, можно сгенерировать неплохой набор синтетических данных, применив модель v2 и просто подсчитывая количество обнаружений.
Такая модель довольно проста в обучении и даёт осмысленные результаты… (хотя она всё-таки не так хороша, как простой подсчёт центроидов, обнаруженных моделью v2).
Реальное и прогнозное количество пчёл в некоторых тестовых образцах | ||||||||||||
Реальное | 40 | 19 | 16 | 15 | 13 | 12 | 11 | 10 | 8 | 7 | 6 | 4 |
v2 (центроиды) прогнозное | 39 | 19 | 16 | 13 | 13 | 14 | 11 | 8 | 8 | 7 | 6 | 4 |
v3 (простой подсчёт) прогнозное | 33,1 | 15,3 | 12,3 | 12,5 | 13,3 | 10,4 | 9,3 | 8,7 | 6,3 | 7,1 | 5,9 | 4,2 |
… к сожалению, модель по-прежнему не работает на Neural Compute Stick (то есть работает, но выдаёт только случайные результаты). Я составил ещё несколько баг-репортов и опять отложил гаджет, чтобы вернуться потом… когда-нибудь…
Как всегда, осталась куча мелочей…
- Запуск на Neural Compute Stick (NCS); сейчас ждём некоторой работы с их стороны…
- Портировать всё на встроенную камеру JeVois. Я немного повозился с ней, но в первую очередь хотел запустить модель на NCS. Я хочу отслеживать пчёл на 120 FPS!!!
- Отслеживать пчёл между несколькими кадрами/камерами для визуализации оптического потока.
- Более детально изучить преимущества полуконтролируемого подхода и обучить более крупную модель маркировать данные для меньшей модели.
- Исследовать возможности NCS; что делать с настройкой гиперпарамеров?
- Перейти к разработке маленькой версии FarmBot для выполнения некоторых генетических экспериментов с рассадой под управлением ЧПУ (то есть нечто совершенно другое).
Весь код опубликован на Github.