Привет!
В качестве некоторого подытога в изучении нейронок (CV), да и попросту из интереса, я хотел решить одну задачу, но не срослось. Поэтому я стал думать и обнаружил прямо под рукой подходящую задачку. Это гречка. Она содержит чёрные штуки (и не только), которые, если их не убрать, могут повредить зубы. В общем, что у нас получилось?

[Минутка справочной информации]. Чтобы очистить гречку, надо иметь яркое освещение, светлую (не сливающуюся с гречкой) поверхность и орлиное зрение (конечно, можно положить гречку в воду и убрать всё всплывшее, но это уберёт не всё, поэтому предварительно гречку надо отсортировать). В общем, всё это очень нудно. И поэтому данную задачу просто необходимо автоматизировать.
Таким образом, перед нами стоит задача детекции примесей в гречке. Для начала, чтобы быстро получить адекватные результаты по данной проблеме, я воспользовался одноэтапной моделью детекции YOLO (т.е. после обработки изображения на выходе нейросети сразу получаем координаты ограничивающих рамок, в которых по версии нейронки находятся объекты, плюс получаем дискретные распределения вероятностей, где значения - выбранные классы, в общем, имеем условные вероятности того, что объект в рамке относится к тому или иному классу). Для всего этого была выбрана "облегченная" модель YOLO8nano от ultralytics. Это высокоуровневое решение, которое избавляет пользователя от множества операций - предобработки и постобработки изображений, выбора функции потерь, изменения архитектуры модели (функции активации, добавления/изменения свёрток, нормализации и тд). Более того, был использован подход дообучения (transfer learning), во-первых, для уменьшения времени обучения, во-вторых, для избежания переобучения (в силу малости датасета) и в-третьих, т.к. наша нейронка обучена на большом датасете COCO, то глубокие слои уже хорошо обучены выявлять примитивы (границы, формы и т.д.).
При всей игрушечности и учебности данной задачи, стоит отметить, что вообще говоря, гречка очень душевная и базовая штука.
Теперь к подробностям о датасете и об обучении нейронки
Скрытый текст
1. Все примеси выделяем в один класс, как видно на картинке, по сути это два класса (если не больше), черные штуки + крупные серые камешки и уродливые крупинки.
2. Датасет состоит из фотографий гречки с примесями. Я перебрал стандартную упаковку гречки (900 гр), выбрал примеси и затем небольшое количество примесей перемешивал с чистой гречкой и фотографировал.
Качество фото - 3060 * 4080.
Количество фото - 348 шт.
Фото делались с расстояния 8-10 см над поверхностью с гречкой, под углом в 15 градусов (с расчетом на то, что "сканирование" гречки с помощью приложения с моделью будет осуществляться примерно на такой высоте).
3. Модель YOLO от ultralytics требует специального формата датасета + yaml файл с характеристиками датасета.
Формат имеет вид - папка dataset с двумя папками images (фотографии) и labels (разметка) и в каждой из этих папок есть 3 папки train, val и test.
4. Для разметки изображений было использовано приложение labellmg, т.к. roboflow не очень хотел работать. (Рекомендую устанавливать labellmg не через консоль, а скачать с гитхаба архив и из него вытащить exe-файл).
5. Т.к. labellmg только размечает фото, то для создание требуемого для YOLO формата потребовалось перемешать изображения и разметки и разложить их по вышеописанным папкам + создать yaml-файл. Фотографии разместились по папкам в соотношении 80:15:5 соответственно.
6. Мы используем transfer learning (дообучаем готовую модель), но оказывается (напомним, что решение YOLO8n от ultralytics "в коробке") , что число замораживаемых слоёв можно передать в качестве параметра в методе для обучения .train. Например, model.train(data=data_yaml, freeze = 10). Но отметим, что модель замораживает не слоями, а модулями и это логично, т.к. архитектура состоит из блоков и замораживание их частей может нарушить логику и снизить эффективность модели.
7. Также в качестве параметра метода train можно передать желаемую аугментацию (это методы изменения картинок для увеличения размера датасета без риска переобучения). В нашем случае из 278 фотографий для тренировки мы получаем с помощью аугментации 640 фотографий.
8. Далее в среде google collab, используя GPU от него, проводим обучение модели и получаем следующие лучшие метрики на 15 эпохе (т.е. модель 15 раз обучилась на всём тренировочном датасете):
P R mAP50 mAP50-95 F1
0.89 0.682 0.798 0.413 0.77
Т.е. полнота (recall) - доля выявленных примесей равна 0.682.
Точность (precision) - вероятность, что отмеченные как примеси объекты таковыми являются - 0.89.
Конечно, хотелось больший recall, в ущерб ложноположительности, но в силу редкости примесей (если одна крупинка весит 0.022 гр, а в упаковке 900 гр, то если всех примесей 80 шт, то отношение нормальной гречки к примесям в упаковке - 99.8 : 0.2) может обрушится precision (детектор слишком много будет показывать объектов и потеряется смысл использования средства автоматизации).
Также при равной важности точности и полноты (метрика F1) максимум достигается при степени уверенности conf = 0.177 (т.е. если оценка нахождения объекта в рамке меньше conf, то считаем что объекта в рамке нет), и в правду, при использовании модели в real time при conf = 0.1 порой было многовато ложных срабатываний, а после conf = 0.25 срабатываний становилось наоборот маловато.
Метрики модели
Итак, после дообучения модель имеем следующие метрики:
P R mAP50 mAP50-95 F1
0.89 0.682 0.798 0.413 0.77

Применение модели к фото из тестовой выборки.

Вторая цель. Real-time детекция.
Второй моей целью было сделать сканер - наводишь камеру телефона на гречку и тебе подсвечивает, где находятся примеси. Не надо использовать свои глаза, всё происходит через экран телефона. После детекции, не отрываясь от экрана телефона убираешь примесь и продолжаешь “сканировать”. В связи с этим были предприняты попытки разной степени успешности.
Попытка №2. Работающая демка. Демка на Hugging Face - Docker + FastAPI.
Как и решение с помощью библиотеки Gradio данное решение работает, но также как и с Gradio подвисает. Поэтому выходной видеопоток был заменен на обработанное изображение (нажимаешь сделать фото после того как через входной поток увидел интересующую область и получаешь обработанное изображение). Так можно делать на разных секторах гречки. В качестве MVP вполне подходит.

Другие попытки сделать детекцию входного видеопотока
Скрытый текст
Попытка №1. Реализация через библиотеку Gradio.
Gradio - решение, позволяющее создавать на Python веб-интерфейсы для моделей машинного обучения и предоставляющее бесплатные ресурсы для демонстрации работы модели через созданный интерфейс.
К минусам данного решения можно отнести: отзеркаливание входного потока, синий цвет выходного потока и задержки сервера, из-за чего выходной поток лагает. Отметим, что при последнем запуске синий цвет выходного потока пропал.

Попытка №3. Реализация через приложение TensorFlow Lite Object Detection Android Demo.
TensorFlow Lite Object Detection Android Demo - приложение для камеры, которое непрерывно обнаруживает объекты (ограничивающие прямоугольники и классы) в кадрах. После сборки данного готового решения через Android Studio сразу возникают ошибки, требующие обновления версии Kotlin, targetSdkVersion, compileSdkVersion и тд. После добавления собственной дообученной модели YOLO8n, экспортированной в формат tensorflowlite возникает ошибка нормализации входных данных. После устранения ошибки с нормализацией приложение работает, но после выбора нашей модели зависает. Связано это с тем, что главная библиотека проекта - Task Library ждёт модели с выходом в 4 тензора {locations, classes, scores, num_detections} (с метаданными), в то время как наша модель имеет на выходе тензор размера [Batch, 4+C, N], а именно [1, 5, 8400]. Что остаётся? Изменить формат выхода модели. Для этого придётся изменить метод detect класса ObjectDetector из tensorflow_lite_support/python/task/vision/object_detector.py , который применяется в lite/examples/object_detection/android/app/src/main/java/org/tensorflow/lite/examples/objectdetection/ObjectDetectorHelper.kt - детекторе данного приложения. Но явного кода детекции в методе detect класса ObjectDetector нет, т.к. применяется другой метод detect фактически из файла tensorflow_lite_support/cc/task/vision/object_detector.cc. Т.е. изменения должны произойти в нём, а именно в методе PostProcess, в проверке формата вывода (вывод установлен для SSD моделей), ну и того что ещё потребуется.
Также можно создавать свой интерпретатор в ObjectDetectorHelper.kt. После этого необходимо убрать рамки, относящиеся к одним объектам, т.е. использовать NMS/ Distance-IoU NMS. После изменения ObjectDetectorHelper.kt возникает каскад изменений всех важных файлов проекта. И в итоге всё падает.
Попытка №4. Изменение выхода модели YOLO8n.
Если сталкиваемся с неудачей из-за вывода не того формата, то нужно изменить вывод. Давайте сделаем это сразу в питоне и затем просто переведём это в формат tflite. Итого - переводим модель в формат .tf (для работы с нашей моделью в библиотеке tensorflow). Затем выделяем из выхода модели тензор ограничивающих рамок (bounding box) и тензор дискретного распределения вероятностей (scores). Далее применяем метод немаксимумов NMS. После чего функцию, которая вышеописанные действия проделывает (выделение двух тензоров + NMS) пытаемся конвертировать в формат .tflite. Но нас постигает неудача - ConverterError: Could not translate MLIR to FlatBuffer.
Попытка №5. Использование в проекте вывода как есть.
Придется ещё больше менять файлов в проекте и в Task Library.
Проблемы/предложения ~Гильберта~
Что можно и нужно улучшить.
Даешь огромный recall. Цена ошибки - комфорт пользователя. В одном кадре детекции (лично мне) комфортно видеть 1-2 истинных значения и 6-7 ложных, в общем минимальная точность около 0.23.
-
Можно из одного класса примесей сделать два (или даже больше).
Как изменится recall? (Важно общее количество найденных объектов-примесей, а не верная принадлежность конкретному классу.)
Оставить только черные штуки и посмотреть на изменения.
-
Куда это всё внедрять и где использовать?
В быту.
В общепите.
В рамках честного знака.
(мне это больше всего нравится) В приложениях продуктовых сетей для углубления взаимодействия покупателя с приложением (т.е. не только ради карты лояльности магазина). А там уже и изучение функционала, предложений, доставки и восприятие бренда как технологического.
Приложения/ сервис от самих производителей (QR код на упаковке), некоторое конкурентное преимущество.
Применить решения с доступными лицензиями.
Воспользоваться другими моделями, в т.ч. одноэтапными.
Обучить все слои, а не 12 первых модулей или наоборот меньше модулей обучить.
Дообучить модель, обученную на пшеничном датасете Global Wheat Head Dataset.
Если взять ту же YOLO и на Torch заново собрать послойно и немного изменить активации и т.д. перестанет ли на это авторское право распространяться?
Попытаться сделать приложение-детектор гречки.
Забацать соревнования на Kaggle. Посоревноваться и решить задачу детекции гречки, да и просто разрешить эту проблему.
Инструкция для самых маленьких
Рекомендую заценить демку и воспользоваться её при готовке!
Если демка на Hugging Face "спит" из-за неактивности, то нажмите "Restart this Space" и ждите.
Нажмите "Старт".
Выберите камеру.
Можете выбрать подходящую степень уверенности conf и метрику iou.
Наведите камеру на гречку и нажмите "Сделать фото".
Подождите и появится фото с детекцией. Далее проделывайте так (5,6 пункты) с другими областями.
Ссылки:
Скрытый текст
Демка нейронки на Hugging Face: https://huggingface.co/spaces/Paradise151/GrechnikNet
-
Ноутбук с кодом на GitHub:
Датасет на Hugging Face: https://huggingface.co/datasets/Paradise151/GrechnikDataset
Спасибо за внимание!
Комментарии (3)

Markscheider
24.11.2025 15:26Куда это всё внедрять и где использовать?
Дык к роботу прикрутить. Если пустить крупу по транспортеру/лотку узким потоком (по 1-2 зернышка) то точность будет лучше, а черные крупинки можно сбивать в сторону штоком соленоида.
Она содержит чёрные штуки (и не только), которые, если их не убрать, могут повредить зубы
Ну и истины ради: черные штуки - это не обязательно камешки, а просто недоочищенные зерна той же гречки. Если их сварить вместе с обычными - они тоже разварятся. Зубы точно НЕ сломают, хотя за счет жесткой оболочки действительно способны доставить некоторый дискомфорт

Paradise151 Автор
24.11.2025 15:26Дык к роботу прикрутить.
Тоже идея. Я просто это на "последнюю милю" готовки хотел переложить, чтобы можно было поразвлекаться + некая популяризация нейронок, ну и т.к. особо не понимаю как сортировка устроена на заводе (по-хорошему надо узнать). Мне вообще кажется, что там используют грубые физические методы: проваливается ли крупа в сито или , например, что-то слишком лёгкое - сдувают под напором воздуха, т.к. человек или какая-то "роборука" просто не смогут успевать убирать крупинки с конвейера, их же очень много (т.е. если загнать в узкий поток, то по длине будет какой-то адронный коллайдер).
Если пустить крупу по транспортеру/лотку узким потоком (по 1-2 зернышка) то точность будет лучше, а черные крупинки можно сбивать в сторону штоком соленоида.
Хотя вот представил - после широкой ленты с грубыми методами сортировки идёт лента увеличенной ширины, на которой много параллельных пластинок и там уже в разделенных потоках вышеописанным методом убирают оставшийся мусор.
черные штуки - это не обязательно камешки
Это да. Но перестраховка не помешает + я тут немного по-маркетинговому повысил градус проблемы)
Markscheider
Ааааааааа, флэшбэки пошли! Меня в детстве бабушка рядом с собой сажала и мы на белой скатерти руками перебирали гречку от "черненьких". Не скажу, что это было совсем уж мучительно, бабушка сказки рассказывала, да и внимательность я натренировал... :)