[Перевод] Подсчёт пчёл нейросетью на 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), используя деконволюцию вместо изменений размера по ближайшим соседям. Когда их исправят, можно вернуться к этой теме… Возникает куча маленьких сложностей, из-за которых множились баги.
Так мы избежим любых проблем с неподдерживаемыми операциями на Movidus Neural Compute Stick, хотя маловероятно, что результат получится таким же хорошим, как в модели центроидов v2. Модель v3: изображение RGB → подсчёт пчёл
Это привело меня к третьей версии модели: можем ли мы перейти непосредственно со входа RGB на подсчёт пчёл?
Но! Сначала я опасался пробовать этот метод: я думал, что для него потребуется гораздо больше маркировки (это больше не система на основе фрагментов). Имея модель, которая довольно хорошо справляется с поиском пчёл, и много немаркированных данных, можно сгенерировать неплохой набор синтетических данных, применив модель 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.