- BrainTools - https://www.braintools.ru -
В этой статье я хочу поделиться опытом [1] разработки iOS-приложения для роботизированного микроскопа с AI-распознаванием клеток крови — как оно устроено, какие задачи пришлось решать, на какие грабли пришлось наткнуться и как iPhone можно использовать в качестве лабораторного инструмента.
Это не очередной todo-лист с авторизацией или приложение для наложения масок на селфи — в центре внимания [2]: видеопоток с окуляра микроскопа, нейронки, работа с железом, Bluetooth-управление перемещением стекол, и всё это — прямо на iPhone.
При этом я постарался не уходить в чрезмерные технические детали, чтобы статья оставалась доступной для большей части аудитории.
Даже с современными гематологическими анализаторами до 15% образцов всё равно требуют ручного пересмотра под микроскопом — особенно если в крови найдены аномалии. Автоматизированные системы микроскопирования есть, но стоят как крыло от боинга, поэтому большинство лабораторий продолжают смотреть мазки вручную. Мы делаем это иначе: наш набор превращает обычный лабораторный микроскоп в цифровой сканер с автоматической подачей и съёмкой — просто, дёшево и эффективно. Мы его разрабатываем вместе с @ansaril3 [3] и командой.
Комплект подключается к стандартному лабораторному микроскопу, превращая его в цифровой сканер. Его хардварная часть состоит из:
iPhone — управление системой, анализ клеток
Линза-адаптер — подключение смартфона к окуляру микроскопа
Роботизированный предметный столик — позволяет перемещать препарат, управлять фокусировкой и переходить между образцами
Программно система состоит из:
мобильного приложения на iPhone
контроллера на столике
веб-портала/облака.
Мобильное приложение решает следующие задачи:
обрабатывает поток изображения с камеры, детектирует, классифицирует, подсчитывает клетки
отправляет их на веб-сервер вместе с другими артефактами анализа
управляет перемещением предметного столика, выполняя сканирование мазка по заложенному алгоритму
и также отвечает за конфигурацию/запуск анализа, настройку параметров съемки, просмотр краткого отчета анализа и другие сопутствующие задачи
Веб-портал предназначен для просмотра результатов, подтверждения анализа врачом и экспорта отчётов. Ниже видео [4], где показано как это работает вместе:
Основной анализ, который мы делаем — это микроскопия мазка крови. Он является частью общего анализа крови (ОАК), одного из самых распространённых и базовых медицинских тестов. Многие его делали и видели у себя подобные таблицы:

При выполнении ОАК, образцы крови прогоняются через геманализатор. Если аппарат показывает отклонение от нормы, то образец микроскопируют.
Это выглядит следующим образом:
лаборант наносит каплю крови на стекло,
окрашивает по Романовскому (или аналогам)1, фиксирует
глазами изучает препарат под микроскопом.
Именно здесь можно:
увидеть аномальные формы клеток (незрелые нейтрофилы, атипичные лимфоциты, бластные клетки),
оценить зрелость, размер, гранулярность, включения и другие параметры,
иногда — поставить предварительный диагноз до получения данных из ПЦР2 или ИФА3
Но ручной анализ это боль [5]:
очень субъективен,
зависит от опыта лаборанта,
человек подвержен усталости и ошибкам,
плохо масштабируется.
Автоматические системы микроскопирования хороши, но стоят дорого (от 5 млн руб и выше), поэтому более 90% лабораторий довольствуются ручным методом до сих пор!
Мы поставили задачу сделать недорогой комплект автоматизации микроскопа (в пределах нескольких сотен тысяч рублей), который можно массово ставить в лаборатории. И тут на сцену выходит iPhone.
1 – образец крови на стекле обрабатывается специальным красителем, разработанным Дмитрием Леонидовичем Романовском (1861-1921). Этот краситель позволяет сделать различные компоненты клеток крови более видимыми под микроскопом, так как они окрашиваются в разные цвета.
2 – ПЦР (полимеразная цепная реакция [6]) делает возможным обнаружение даже очень малого количества генетического материала, например, вирусов или бактерий, что важно для диагностики инфекционных заболеваний. Помним с ковидных времен
3 – ИФА (иммуноферментный анализ) применяется, когда важно определить наличие специфических белков.
Когда мы говорим «AI на смартфоне», чаще всего представляют какие-то фильтры в камере, автодополнение текста или чат-боты. Но современные iPhone — это мини-компьютеры с выделенными нейромодулями, способные выполнять серьезную работу: в нашем случае анализ клеток крови в реальном времени, рассмотрим 3 ключевые компонента, которые делают это возможным:
Графический процессор (GPU). Используется для операций с изображениями: преобработка, фильтрация, коррекция. Например: оценка блюра, цветокоррекция, удаление артефактов и другие специфичные операции с графикой / анализом картинок.
Нейронный движок (NPU / Neural Engine) Apple встраивает Neural Engine в свои устройства начиная с A11 (iPhone 8/X), а с A12 (iPhone XR и новее) уже можно выполнять 5+ трлн операций в секунду на NPU [7] (TOPS). На момент написании статьи последнии A17 Pro и A18/A18 Pro выполняют 35 TOPS. [8] Это используется для inference моделей детекции и классификации клеток, оценки фона препарата и т.п, освобождая CPU/GPU.
Процессор (CPU) Отвечает за общую логику [9], управление, обработку конфигураций, сериализацию/десериализацию, работу с API и файловой системой – все, что не вошло в предыдущие два пункта
Говорить будем на примере iPhone XR (A12 Bionic, 2018), как некий baseline, хоть и старый. Даже на нем у нас получалось:
обрабатывать видеопоток 50fps с камеры микроскопа,
одновременно выполнять CoreML-инференс (~15ms per frame),
параллельно сохранять данные на диск и синхронизировать с облаком,
удерживать температуру в допустимых пределах (если аккуратно настроить throttling и приоритизацию задач)
Тем не менее, устройство могло заметно нагреваться и начинать тормозить. Например, при анализе мазков на малярии, где требуется обрабатывать 100+ клеток на одном кадре, уже на втором-третьем мазке начинался thermal throttling — снижалась частота CPU, появлялись лаги и подтормаживания интерфейса. К тому же плотное прилегание адаптера к задней панели устройства мешает отводу тепла.
На скриншоте ниже показан другой анализ, не малярия — но тут важно, сколько срабатывают детекций на один кадр.
В целом, в iOS можно отслеживать термальное состояние системы через ProcessInfo.processInfo.thermalState [10]. У нас в бою до Critical дело не доходило, но Serious происходит регулярно при очень интенсивной нагрузке. Для perfomance замеров, мы использовали Xcode Profiler, где можно измерять как загрузку CPU, GPU, памяти [11], так и Thermal State:
А вот Таблица значений thermalState с расшифровкой из документации:
|
Состояние (Thermal State) |
Рекомендации (Recommendations) |
Действия системы (System Actions) |
|
Nominal (Нормальное) |
Корректирующие действия не требуются. |
— |
|
Fair (Умеренное) |
Слегка повышена температура.Приложения могут заранее начинать энергосберегающие меры. |
Анализ фото ставится на паузу |
|
Serious (Серьёзное) |
Производительность системы снижается. Приложения должны сократить использование CPU, GPU и операций ввода-вывода. |
ARKit и FaceTime снижают частоту кадров (FPS)Восстановление из резервной копии iCloud приостанавливается |
|
Critical (Критическое) |
Приложения должны сократить использование CPU, GPU и операций ввода-вывода, а также прекратить использование периферийных устройств (например, камеры) |
ARKit и FaceTime значительно снижают частоту кадров (FPS) |
Полный тепловой и энергетический разбор тянет на отдельную статью — как в начале говорил, глубоко уходить не хочу. По открытым источникам [12] можно грубо предположить, что serious соответствует 80-90°C у чипа и ~40°C у поверхности.
iPhone работает с любыми Bluetooth Low Energy [13] устройствами. Для других отдельный флоу, где устройство должно иметь сертификат MFI [14] (made for iPhone), работать по протоколу iAP2 (Apple Accessory Protocol) и т.д. Короче говоря — это не наш кейс.
Тут полезно напомнить базовые роли и структуру протокола:
Peripheral (периферийное устройство) — это устройство, к которому подключаются. Обычно именно периферия рассылает данные или ждёт подключения (пример: часы, термометр, пульсометр).
Central (центральное устройство) — это устройство, которое подключается к периферийному. Он инициирует соединение, отправляет команды и получает данные.
GATT (Generic Attribute Profile) — это структура, по которой BLE-устройства обмениваются данными. GATT описывает, какие “поля” доступны, что можно прочитать, записать или подписаться на уведомление.
Services и Characteristics — данные внутри BLE-соединения структурированы в виде сервисов — логических групп и характеристик — конкретных параметров. Например, у фитнес-браслета может быть сервис Heart Rate, в котором есть характеристика Heart Rate Measurement (текущий пульс).
В нашем случае, iPhone управляет столиком через встроенный BLE модуль, который распознается как Peripheral с кастомным GATT-сервисом и выполняет 2 задачи:
передавать контроллеру команды перемещения по (осям XY) и фокусировки (ось Z)
получал данные от контроллера (статус, позиция)
К слову о тепловой нагрузке, BLE-соединение не должно вносить заметного вклада. Если верить данным Silicon Labs в их доке [15] по энергопотреблению BLE, передача команд или получение статуса с частотой 20 Гц (интервал 50 мс):
даёт прирост <1 мВт. У iPhone XR типичная нагрузка в режиме ожидания ~50–100 мВт. Добавка <1 мВт — почти незаметна, особенно по сравнению с нейронками, GPU и экраном
радиоканал включается всего на ~2% времени, остальное время спит
В блоке “Работа с моторизованным столиком”, где чуть подробнее погрузимся в детали работы приложения с BLE модулем и контроллером.
Теперь немного про камеру. Используем основную (широкоугольную заднюю) камеру: получаем видео H.264 с разрешением 1280×720 и битрейтом около 40 Мбит/с.
Чем выше битрейт, тем больше данных на единицу времени → выше качество изображения. 40 Мбит/с — достаточно высокий для разрешения 1280×720 (HD). Более чем хватает для изображения для анализа клеток.
H.264 — это международный стандарт видеокодирования, также известный как AVC — Advanced Video Coding или MPEG-4 Part 10. Он убирает избыточные данные (межкадровое и внутрикадровое сжатие), снижая битрейт и как следствие размер файла. (У нас, кстати, была задача записывать видео всего анализа для отладки и валидации)
Вот и получаем это не просто мобильный UI-клиент, а вполне себе edge-девайс, то есть устройство, которое самостоятельно обрабатывает данные на месте, без постоянной связи с сервером.
Теперь, когда мы разобрались с хардварной частью, посмотрим, как всё это работает на уровне приложения. Начнем сначала с постановки задачи:
На вход приложению с камеры подается поток кадров — движение поля зрения [16] микроскопа по мазку.
На выходе приложение должно:
детектировать лейкоциты (и другие клетки в зависимости от анализа)
отображать BBox’ами детектируемых объектов
делать их подсчет
отсылать в фоне данные на бекенд (изображения клеток, скана, отдельных кадров)
Как видно на схеме выше, всё крутится вокруг кадра с камеры — на нём держится и детекция, и навигация по стеклу, и какие артефакты из него нужно отправлять в облако. Поэтому в центре всего — потоковая обработка кадров, опишем ее основные этапы и важные моменты.
1) Преобработать кадры. Сюда входит коррекция дисторсии, удаление артефактов, определение уровня размытия (блюр), световая и цветовая коррекция
Например, в каждой лаборатории или микроскопе специфичное освещение, из-за чего нейронка может сбоить. Здесь нужно сделать нормировку белого цвета — навести поле зрения [17] на пустую область и запустить баланс белого цвета на камере.
Еще у нас был такой баг: клетки на портал приходили без калибровки цвета. Из-за того, что детекция в паралель запускалась раньше применения настроек камеры.
2) Задетектить, классифицировать и подсчитать без дублей клетки.
Например, на фото ниже красным отмечены некоторые дубли в одном из старых анализов:
3) Проконтролировать микроскоп, чтобы он правильно двигался по стеклу, переходил с одного на другого и, самое важное, точно фокусировался на стекле, определял, когда он выходит за границы стекла или попадает на пустые пространства
4) Загружать на облако пачку клеток (снапшоты, метаданные) и не блокировать этим прогон следующего анализа
5) Повторить n раз, так как анализы делаются пачкой
6) и сделать это так, чтобы телефон не взорвался от перегрева
Приложение развивалось как обычно в стартапах: был быстро набросан proof-of-concept, затем доведен до MVP (minimum viable product), чтобы можно было пилотировать в лабах и питчить инвесторам. . В итоге архитектура приложения получилась гибридной: часть экранов реализована на UIKit-овских MVP экранах (model-view-presenter), а новые фичи и интерфейсы пишутся на Swift c MVVM (Model-View-ViewModel).
Используем сервисный слой для изоляции бизнес-логики: CameraService, BluetoothController, AnalysisService. Все зависимости инжектируются через конструкторы или через DI-контейнеры. В плане реактивности и асинхронных цепочек с подписками на события у нас был “эволюционный путь”: сначала завезли RxSwift, потом начали переходить на Combine, а с выходом async/await часть цепочек ушла на них. Получился такой “франкенштейн”, но затем мы изолировали эти куски в отдельные компоненты — чтобы в будущем можно было просто заменить их компонент с новым стеком. Всё приложение прошито подробными логами, а для сложных случаев (особенно связанных с обработкой кадров) используем NSLogger: туда можно логировать не только текст, но и изображения — это не раз спасало при отладке пайплайна обработки клеток перед их отправкой на сервер.
Про тестирование можно было бы написать отдельную статью: от мокирования отдельных частей анализа и быстрой настройки нужных состояний через ProcessInfo (у меня, кстати, есть небольшая тех. заметка [18] на эту тему), до симуляции отдельных шагов анализа и покрытия всего этого интеграционными и юнит-тестами.
Но вернемся к обработке кадров и рассмотрим чуть более подробную, чем выше архитектурную схему:
Analysis Controller — центр принятия решений: получает кадры, запускает обработку в Frame Pipeline.
Camera Service — получает сырой поток кадров с камеры, преобразует их и отправляет дальше
Microscope Controller управляет контроллером микроскопа
Frame Pipeline — цепочка из нескольких стадий:
Preprocessing — коррекция, фильтрация
Detection — поиск объектов/клеток
Counting — подсчет уникальных объектов
Postprocessing — финальная фильтрация и подготовка к визуализации
UI — отвечает за отображение результатов пользователю в реальном времени (bounding boxes, статистика, алерты).
Uploader — синхронизирует артефакты анализа (снапшоты, клетки, конфиг) с бэкэндом.
В плане менеджера зависимостей: использовался CocoaPods (перешёл в режим поддержки и не развивается активно с 2024 года [19]), но затем мы завезли SPM (Swift Package Manager). Часть сервисов (CV, Bluetooth, утилиты) были вынесены в SPM модули, также были попытки вынесения ObjC/C++ кода в отдельные xcframework-ы, но было некогда разгребать, поэтому оставили этот код в основном проекте. ObjC нужен для обертки над C++, чтобы его можно было вызывать из Swift-а. Получались такие ObjC++ классы: интерфейс у них чисто ObjC-шный и с ним может работать Swift, а в реализации перемешан код ObjC и C++. Это было еще до поддержки вызова C++ прямо из Swift [20]. Оговорюсь, что я далеко не гуру C++ и Computer Vision алгоритмов, но в мои задачи входило базовые погружение и портирование алгоритмов, эвристик с Python, на которым у нас был основной R&D. Ниже опишу некоторые из них.
У одного из адаптеров был артефакт оптической дисторсии на изображении. В результате клетка, которая должна быть круглой, кажется вытянутой или кривой, особенно по краям кадра. Мы использовали калибровку на шахматной сетке и OpenCV cv::undistort() [21] для восстановления геометрии кадра:
Калибруем камеру — снимаем шахматную доску/сетку с известной геометрией:
OpenCV вычисляет:
матрицу камеры K (параметры проекции)
коэффициенты дисторсии D = [k1, k2, p1, p2, k3, …]
Применяем cv::undistort() или cv::initUndistortRectifyMap() + remap():
вычисляется, куда реально «должна была попасть» каждая точка
изображение “разгибается” назад
В последствии, адаптер поменяли — этот шаг убрали
Чтобы точно считать клетки, нужно максимально точно знать их координаты. Вот [22] тут на видео видно, что происходит с кривым определением сдвига.
Изначально, мы пытались посчитать относительный сдвиг между двумя кадрами и суммировать абсолютный сдвиг. Перепробовали несколько вариантов:
классический метод регистрации изображений через фазовую корреляцию, основанную на быстром преобразовании Фурье. Реализовали на OpenCV и даже использовали Apple Accelerate [23].
Методы на основе локальных ключевых точек с дескрипторами: SURF, SIFT, ORB и другие.
Optical Flow
встроенный в Apple Vision: VNTranslationalImageRegistrationRequest [24]
С одной стороны, у нас были некоторые допущения:
отсутствовало изменение масштаба, поворотов.
оптические: чистый, несмазанный мазок, без пустых пространств
Но несмотря на это, все равно были проблемы: из-за изменения освещения, фокуса, накопления ошибки [25], резких сдвигов, шумов/артефактов на изображении.
Получилась вот такая вот таблица их сравнения:
|
Метод |
Преимущества |
Недостатки |
Особенности использования |
Скорость |
Комментарий |
|
FFT + кросс-корреляция (OpenCV, Accelerate) |
Очень быстрый, глобальный сдвиг, прост в реализации |
Накапливается ошибка, не устойчив к резким сдвигам. |
Требует одинакового размера изображений, подходит для “чистого” сдвига |
Очень высокая |
Использовался как основной |
|
SIFT |
Высокая точность, масштаб/поворот-инвариантность |
Медленный, раньше был не free |
Отлично для разнообразных сцен, с текстурой и сложными преобразованиями |
Медленный |
Экспериментальный вариант |
|
SURF |
Быстрее SIFT, тоже устойчив к масштабу/повороту |
Проприетарный, не всегда доступен |
Подходит для real-time чуть лучше, но тоже “тяжёлый” |
Средний |
Экспериментальный вариант, тем более что под патентом |
|
ORB |
Быстрый, бесплатный, инвариантен к повороту |
Чувствителен к освещению, неустойчив к масштабу |
Неплохо показывать себя в склейке изображений |
Высокая |
Пока не перенесли склейку в облаке, были версии с ним |
|
Optical Flow (Lucas-Kanade) |
Отслеживает движение точек между кадрами, хорош для видео |
Не работает на глобальных трансформациях, зависит от освещения |
Лучше всего в видео или сериях с малым движением |
Средняя |
Были эксперименты в оцифровке (склейке) изображений |
|
Optical Flow (Farneback) |
Плотная карта движения, применим к целому изображению |
Медленный, чувствителен к шуму |
Хорош для анализа локальных движений в кадре |
Медленный |
Были эксперименты в оцифровке (склейке) изображений |
|
Apple Vision (VNTranslationalImageRegistrationRequest) |
Очень удобный API, быстрый, железо-оптимизирован |
В нашем случае точность была слабая. |
Отлично подходит для простых кейсов на iOS/macOS |
Очень высокая |
Попробовали и закопали |
Для каждой варианта мы пытались найти оптимальную по точности/производительности конфигурацию сравнения с эталонным сдвигом: меняли разрешения изображения, параметры алгоритмы, разные настройки камеры и оптики микроскопа. Ниже пару графиков из такого рода экспериментов
А вот как выглядела отладка поиска ключевых точек, по которым мы в дальнейшем хотели строить сдвиг.
В итоге, когда у нас в системе появился роботизированный столик, мы начали использовать координаты его контроллера, которые мы уже уточняли с помощью CV-эвристик.
По сути, задача подсчета клеток — это частный случай object tracking & deduplication: “увидеть, что за клетка, не посчитать дважды, не пересчитать лишнее, и не пропустить нужное — всё это за доли секунды, в онлайне через камеру и на железе телефона. Как мы это решали:
Обнаружение объектов. Используем нейронки для детекции объектов на кадре (Bounding Box, BB). Каждый BB имеет свой “confidence score” (доверие сети) и класс клетки.
Для борьбы с фоном и ложными срабатываниями применяем быструю фильтрацию:
цветовая: например, по интенсивности или цветовому диапазону. Например، здесь слева красным подсвечен эритроцит — но нейронка приняла его за лейкоцит
Однако дальше в дело вступили цветовые фильтры и он был отсеян.
геометрическая: отбрасываем объекты, размер которых выходит за рамки типичных клеток.
также отбрасываем клетки, которые частично выходят за пределы кадра — нам такие не интересны
Подсчет уникальных объектов. Некоторые BB могут посчитаться более одного раза для одной и той же клетки, нужно уметь ловить такие срабатывания и учитывать лишь один раз. В свое время мы вдохновлялись гайдом от MTurk [26], где описаны 2 варианта:
Вариант 1: Сравниваем расстояния между центрами BBs — если новый BB находится слишком близко к уже прочитанному, это скорее “та же” клетка.
Вариант 2: Считаем IoU (intersection over union, Jaccard Index) — метрику пересечения прямоугольников. Если новый BB сильно пересекается с существующим, учитываем его только один раз.
В целом нужно поддерживать трекинг объектов и между кадрами, если мы возвращаемся на уже пройденные куски мазка. Тут снова архиважно корректно определять позицию на стекле — иначе весь подсчет уйдет коту под хвост.
Одной из задач была оцифровка скана, своего рода программный гистологический сканер [27] для мазка. Фото ниже как он выглядит: стрелочками отмечено движение для построения скана, собираем кадры и сшиваем их в одну большую картинку.
Здесь снова критично важным было определение позиции, а далее бесшовная склейка.
Надо сказать, что изначально у нас не было моторизированного столика и мы полагались на ручную навигацию. Представьте, что вы клеите мозаику из сотен осколков. Промахнетесь с координатами — и мозаика поплыла.
Вот как выглядели первые эксперименты: прыгающие поля зрения, швы, разница в освещении, пустые пространства.
Или вот пользователь делает скан мазка, резко двигаясь по мазку – некоторые области получаются смазанными (Motion Blur). Мы пробовали дропать такие кадры, если они не прошли допустимый порог блюра или для них не посчитался сдвиг.
Постепенно продвинулись к такому варианту:
Итераций было много: склейка на девайсе, через разные методы, на разных разрешениях кадров и конфигах камеры. Пришли к тому, что скан собирается в облаке, а мобилка отсылает кадры с калибровкой баланса белого и экспозиции.
Ниже пример, как мы замеряли скорость отдельных кусков обработки кадров в зависимости от конфига: настройка камеры, выбранные алгоритмы их параметры.
Теперь — подробности про связку iPhone и моторизованного столика: как общаемся по BLE, какие команды шлем и как настраивали автофокус. Мобилка связывается через Bluetooth с контроллером на столике и двигается по XYZ координатам. Точнее, двигается сам столик, но для с точки зрения изображения с объектива, которое видит мобилка: движение происходит визуально по стеклу.
Столик у нас тоже самодельный — не потому что «хотим всё своё», а потому что рыночные решения стоят от $10k, и это не шутка. Мы наняли конструкторское бюро и собрали свою версию за ~$800. Получилось сильно дешевле, потому что один из инженеров вовремя заметил: конструкция моторизованного микроскопного столика подозрительно напоминает 3D-принтер. Та же кинематика по XYZ, те же шаговики, те же рельсы. В итоге используем массовые и дешёвые компоненты, но под наши задачи. Конструктивно столик состоит из трёх частей: сама платформа XY, блок фокусировки (ось Z, мотор крепится к ручке тонкой фокусировки) и управляющий блок — контроллер, который принимает команды по Bluetooth и отдает их на шаговики. Всё это работает в связке с мобилкой.
Для ручного перемещения столика используем виртуальный джойстик (отображаем пользователю на экране кнопки перемещения) — он используется в сценариях калибровок и настройки системы. Во время же анализа всегда автоматическое управление. Вот [28] как работал джойстик в первых версиях — потом мы уже докрутили и звук и задержку.
В качестве блютуз интерфейса используется плата HC-08 [29]. BLE модуль работает по умолчанию в режиме текстового терминала, то есть запросы/ответы просто проходят туда-сюда. Для конфигурации и системных задач (смена имени, скорости обмена) используются at-команды.
Сам контроллер работает на прошивке GRBL, через g-команды [30]. Основные сценарии здесь:
инициализация подключения (телефон должен понять, что столик подключен)
сканирование стекла (перемещение столика по всем осям)
остановка/возобновление сканирования
обработка исключительных ситуаций: дошли до концевика, прерываем перемещение, переполняется буфер команд. По ошибкам есть отдельная дока [31]
GRBL обладает собственным набором команд [32], который начинается с символа $, например:
$H – хоминг или калибровка и поиск аппаратного нуля по концевика. Обычно выполняется при первом старте и далее по необходимости в случае большой накопленной ошибки в процессе движения.
$J=<команда> – режим Jogging, то есть имитация управления джойстиком. Сама команда должна описывать относительное перемещение по всем осям. Пример такой команды: $J=G21G91Y1F10
G21 – режим расстояния в миллиметрах
G91 – относительное смещение
Y1 – перемещение по оси Y на 1 миллиметр
F10 – скорость перемещения.
? запрос состояния grbl. Возвращает строку с основными параметрами машины. Пример выполнения: <Alarm|MPos:0.000,0.000,0.000|FS:0,0|Pn:XYZ|WCO:-5.300,0.000,-2.062>
Нам нужны первые два параметра:
состояние. Может быть “Idle”, “Running”, “Alarm”
MPos – текущая позиция столика
В детали GRBL и протоколов управления столиком сильно уходить не буду — это тянет на отдельную статью. Если коротко: GRBL — это опенсорсная прошивка из мира ЧПУ, отлично подходит для управления трех осевыми системами (XY+Z) через простые G-коды. BLE-модуль взяли максимально простой — HC-08, чтобы не возиться с MFi и iAP. Нам было важно, чтобы iPhone мог надежно отдавать команды и получать статус с минимальной задержкой и стоимость комплекта сильно не поменялась.
Выше я упоминал про фокусирование [33]. Этот процесс выполняется с периодичностью во время сканирования мазка, так как препарат нанесен неравномерно, особенно это заметно на больших увеличениях. Приходится мониторить уровень размытия и своевременно обновлять фокус. Выглядит это так [34].
На графике ниже показана зависимость уровня фокусировки от времени. Начинаем с размытого изображения, постепенно устанавливая столик в положение оптимального фокуса

Я уже упоминал про оцифровку скана со стороны мобилки. Тут полезно упомянуть, что оцифровка может происходить на разных увеличениях: от 5x до 40x. На маленьком зуме — проще ориентироваться и искать границы пятна, на большом — видно детали клеток.
В нашем случае мы работаем с 2-мя уровнями:
Поиск границ на увеличении 4x. Алгоритм проходит по всему стеклу, определяет область пятна и выдает карту границ на следующий этап. На выходе получается что-то вроде тепловой карты. Например, из изображения на низком увеличении слева мы получим матрицу, по которым уже построим шаги, чтобы двигаться на высоком увеличении:

Сканирование пятна на увеличении 20x (или другом). Алгоритм сканирует и сохраняет изображения для последующего построения в единую карту. Сканирование идет построчно, в рамках границ пятна. Фото для склейки берется когда:
изображение сфокусировано
контроллер в состоянии idle, т.е не двигается
Чтоб пользователю не менять объектив каждый раз, мы делаем поиск границ и сканирование сразу по всем стеклам в батче, параллельно загружая предыдущий батч на облако. На нем уже происходит стичинг или сшивка изображения, но это тема для отдельной статьи.
Этот проект показал: даже смартфон из 2018 года может тянуть задачи, которые раньше решались десктопами, серверами и дорогими автоматическими микроскопами. Конечно, за кадром осталось много всего: от сбора датасета до тонкой настройки экспозиции. Если интересно — могу отдельно разобрать это. Задавайте вопросы, делитесь своим опытом, и, возможно, вместе соберём продолжение или разберём отдельные аспекты глубже. Спасибо, что дочитали!
Автор: amin_benarieb
Источник [35]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/15871
URLs in this post:
[1] опытом: http://www.braintools.ru/article/6952
[2] внимания: http://www.braintools.ru/article/7595
[3] @ansaril3: https://habr.com/ru/users/ansaril3/
[4] видео: https://www.youtube.com/watch?v=SrEqkjHpq04
[5] боль: http://www.braintools.ru/article/9901
[6] реакция: http://www.braintools.ru/article/1549
[7] 5+ трлн операций в секунду на NPU: https://web.archive.org/web/20180913121011/https://www.apple.com/ru/iphone-xs/a12-bionic/
[8] 35 TOPS.: https://techcrunch.com/2024/09/09/apple-announces-its-new-a18-iphone-chip/
[9] логику: http://www.braintools.ru/article/7640
[10] ProcessInfo.processInfo.thermalState: https://developer.apple.com/documentation/foundation/processinfo/3131922-thermalstate
[11] памяти: http://www.braintools.ru/article/4140
[12] открытым источникам: https://www.notebookcheck.net/Apple-iPhone-XR-Smartphone-Review.346971.0.html
[13] Bluetooth Low Energy: https://en.wikipedia.org/wiki/Bluetooth_Low_Energy
[14] MFI: https://mfi.apple.com/en/how-it-works.html
[15] доке: https://docs.silabs.com/bluetooth/8.0.0/bluetooth-fundamentals-system-performance/current-consumption
[16] поля зрения: http://www.braintools.ru/article/9711
[17] зрения: http://www.braintools.ru/article/6238
[18] небольшая тех. заметка: https://amin.benarieb.com/posts/how-to-debug-faster-with-processinfo/
[19] перешёл в режим поддержки и не развивается активно с 2024 года: https://blog.cocoapods.org/CocoaPods-Support-Plans/
[20] поддержки вызова C++ прямо из Swift: https://www.swift.org/documentation/cxx-interop/
[21] OpenCV cv::undistort(): https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html
[22] Вот: https://youtu.be/vSxwkZj4S0E
[23] Accelerate: https://developer.apple.com/documentation/accelerate
[24] VNTranslationalImageRegistrationRequest: https://developer.apple.com/documentation/vision/vntranslationalimageregistrationrequest?language=objc
[25] ошибки: http://www.braintools.ru/article/4192
[26] гайдом от MTurk: https://blog.mturk.com/tutorial-measuring-the-accuracy-of-bounding-box-image-annotations-from-mturk-ad3dfcdf8aa0
[27] гистологический сканер: https://www.youtube.com/watch?v=5VgCWMzlKhY&ab_channel=%D0%9E%D0%9E%D0%9E%D0%9C%D0%95%D0%9A%D0%9E%D0%A1
[28] Вот: https://www.youtube.com/watch?v=KmmW5KqskN8&ab_channel=AminBenarieb
[29] HC-08: https://compacttool.ru/modul-hc-08-bluetooth-40-ble-chip-cc2540-masterslave-rezhimy
[30] g-команды: https://howtomechatronics.com/tutorials/g-code-explained-list-of-most-important-g-code-commands/
[31] отдельная дока: https://docs.sainsmart.com/article/dm4jbd5jce-grbl-error-codes
[32] набором команд: https://github.com/gnea/grbl/blob/master/doc/markdown/commands.md
[33] фокусирование: http://www.braintools.ru/article/7085
[34] так: https://youtu.be/UtZCxi4Q1Bg
[35] Источник: https://habr.com/ru/articles/915372/?utm_campaign=915372&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.