Привет! 

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

Рис. 1: Гречка с примесями.
Рис. 1: Гречка с примесями.

[Минутка справочной информации]. Чтобы очистить гречку, надо иметь яркое освещение, светлую (не сливающуюся с гречкой) поверхность и орлиное зрение (конечно, можно положить гречку в воду и убрать всё всплывшее, но это уберёт не всё, поэтому предварительно гречку надо отсортировать). В общем, всё это очень нудно. И поэтому данную задачу просто необходимо автоматизировать.

Таким образом, перед нами стоит задача детекции примесей в гречке. Для начала, чтобы быстро получить адекватные результаты по данной проблеме, я воспользовался одноэтапной моделью детекции 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

Рис. 2. Функции потерь и метрики дообученной модели YOLO8n.
Рис. 2. Функции потерь и метрики дообученной модели YOLO8n.

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

Рис. 2: Детекция примесей в гречке.
Рис. 3: Детекция примесей в гречке.

Вторая цель. Real-time детекция.  

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

Попытка №2. Работающая демка. Демка на Hugging Face - Docker + FastAPI.

Как и решение с помощью библиотеки Gradio данное решение работает, но также как и с Gradio подвисает. Поэтому выходной видеопоток был заменен на обработанное изображение (нажимаешь сделать фото после того как через входной поток увидел интересующую область и получаешь обработанное изображение). Так можно делать на разных секторах гречки. В качестве MVP вполне подходит. 

Рис. 4: Скриншот работы демки (Docker + FastApi) на Hugging Face. 
Рис. 4: Скриншот работы демки (Docker + FastApi) на Hugging Face. 

Другие попытки сделать детекцию входного видеопотока 

Скрытый текст

Попытка №1. Реализация через библиотеку Gradio. 

Gradio - решение, позволяющее создавать на Python веб-интерфейсы для моделей машинного обучения и предоставляющее бесплатные ресурсы для демонстрации работы модели через созданный интерфейс. 

К минусам данного решения можно отнести: отзеркаливание входного потока, синий цвет выходного потока и задержки сервера, из-за чего выходной поток лагает. Отметим, что при последнем запуске синий цвет выходного потока пропал. 

     Рис. 5:  Скриншот работы нейросети через Gradio
     Рис. 5:  Скриншот работы нейросети через Gradio

Попытка №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. 

Проблемы/предложения ~Гильберта~

Что можно и нужно улучшить. 

  1. Даешь огромный recall. Цена ошибки - комфорт пользователя. В одном кадре детекции (лично мне) комфортно видеть 1-2 истинных значения и 6-7 ложных, в общем минимальная точность около 0.23.

  2. Можно из одного класса примесей сделать два (или даже больше).

    Как изменится recall? (Важно общее количество найденных объектов-примесей, а не верная принадлежность конкретному классу.) 

  3. Оставить только черные штуки и посмотреть на изменения. 

  4. Куда это всё внедрять и где использовать?

    • В быту.

    • В общепите. 

    • В рамках честного знака.

    • (мне это больше всего нравится) В приложениях продуктовых сетей для углубления взаимодействия покупателя с приложением (т.е. не только ради карты лояльности магазина). А там уже и изучение функционала, предложений, доставки и восприятие бренда как технологического.

    • Приложения/ сервис от самих производителей (QR код на упаковке), некоторое конкурентное преимущество.

  5. Применить решения с доступными лицензиями.

  6. Воспользоваться другими моделями, в т.ч. одноэтапными.

  7. Обучить все слои, а не 12 первых модулей или наоборот меньше модулей обучить.

  8. Дообучить модель, обученную на пшеничном датасете Global Wheat Head Dataset. 

  9. Если взять ту же YOLO и на Torch заново собрать послойно и немного изменить активации и т.д. перестанет ли на это авторское право распространяться? 

  10. Попытаться сделать приложение-детектор гречки.

  11. Забацать соревнования на Kaggle. Посоревноваться и решить задачу детекции гречки, да и просто разрешить эту проблему. 

Инструкция для самых маленьких

Рекомендую заценить демку и воспользоваться её при готовке!

  1. Если демка на Hugging Face "спит" из-за неактивности, то нажмите "Restart this Space" и ждите.

  2. Нажмите "Старт".

  3. Выберите камеру.

  4. Можете выбрать подходящую степень уверенности conf и метрику iou.

  5. Наведите камеру на гречку и нажмите "Сделать фото".

  6. Подождите и появится фото с детекцией. Далее проделывайте так (5,6 пункты) с другими областями.

Ссылки: 

Скрытый текст
  1. Демка нейронки на Hugging Face: https://huggingface.co/spaces/Paradise151/GrechnikNet 

  2. Ноутбук с кодом на GitHub:

    https://github.com/Paradise151/GrechnikNet 

  3. Датасет на Hugging Face: https://huggingface.co/datasets/Paradise151/GrechnikDataset 

 Спасибо за внимание!

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


  1. Markscheider
    24.11.2025 15:26

    Ааааааааа, флэшбэки пошли! Меня в детстве бабушка рядом с собой сажала и мы на белой скатерти руками перебирали гречку от "черненьких". Не скажу, что это было совсем уж мучительно, бабушка сказки рассказывала, да и внимательность я натренировал... :)


  1. Markscheider
    24.11.2025 15:26

    Куда это всё внедрять и где использовать?

    Дык к роботу прикрутить. Если пустить крупу по транспортеру/лотку узким потоком (по 1-2 зернышка) то точность будет лучше, а черные крупинки можно сбивать в сторону штоком соленоида.

    Она содержит чёрные штуки (и не только), которые, если их не убрать, могут повредить зубы

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


    1. Paradise151 Автор
      24.11.2025 15:26

      Дык к роботу прикрутить.

      Тоже идея. Я просто это на "последнюю милю" готовки хотел переложить, чтобы можно было поразвлекаться + некая популяризация нейронок, ну и т.к. особо не понимаю как сортировка устроена на заводе (по-хорошему надо узнать). Мне вообще кажется, что там используют грубые физические методы: проваливается ли крупа в сито или , например, что-то слишком лёгкое - сдувают под напором воздуха, т.к. человек или какая-то "роборука" просто не смогут успевать убирать крупинки с конвейера, их же очень много (т.е. если загнать в узкий поток, то по длине будет какой-то адронный коллайдер).

      Если пустить крупу по транспортеру/лотку узким потоком (по 1-2 зернышка) то точность будет лучше, а черные крупинки можно сбивать в сторону штоком соленоида.

      Хотя вот представил - после широкой ленты с грубыми методами сортировки идёт лента увеличенной ширины, на которой много параллельных пластинок и там уже в разделенных потоках вышеописанным методом убирают оставшийся мусор.

      черные штуки - это не обязательно камешки

      Это да. Но перестраховка не помешает + я тут немного по-маркетинговому повысил градус проблемы)