Files
isaac/control_test/.memory.md
T
dasha_f 0d32f32db0 Сортировочная ячейка Isaac Sim: CV-пайплайн и меши товаров
Замкнутый контур "поток -> CV -> механика": товары идут по конвейеру с шагом 700 мм,
класс определяется стереопайплайном во время движения, пушер и плуг реагируют физически.

Состав:
* control_test/ - ячейка и CV. run_sorting_cv.py + cv_worker.py (два процесса, потому что
  torch внутри Isaac роняет сцену), cell.py (физика лент, плуга, пушера), measure_plane.py
  (замер габаритов), README.md и .memory.md с замерами, проблемами и ловушками
* robozon_sorter/ - модули симуляции, scripts/ - утилиты, scene/ - сцены
* assets/ - меши товаров, плуг, объекты Objaverse

Бейзлайн CV: DEFOM-Stereo vitl, вход 480, iters 24, кроп зоны осмотра, без сегментации.
На потоке 700 мм - классы 8/9, габариты MAE 32.8 мм, 469 мс на товар при такте 700 мс.

Веса моделей (4.5 ГБ) и пропсы конвейера NVIDIA (274 МБ) не включены - источники и
команды скачивания в MODELS.md. Выход прогонов (captures/, runtime/) не включён:
воспроизводится.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 13:07:24 +00:00

20 KiB
Raw Blame History

.memory — состояние пайплайна control_test

Живая справка по замкнутому контуру «поток → CV → механика». Обновлять при изменении конфигурации или при появлении нового замеренного факта. Здесь только то, что измерено; предположения помечены отдельно.

Последнее обновление: 2026-08-01.


1. Что сейчас работает

Полный прогон с кинематикой и CV — run_sorting_cv.py в паре с cv_worker.py.

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

Прежний run_pipeline.py (классы из labels.json, без CV) остаётся рабочим и нужен как контроль: он разделяет ошибки механики и ошибки распознавания.

Почему два процесса

torch внутри Isaac роняет процесс. Поэтому CV живёт отдельно, обмен через каталог:

run_sorting_cv.py  (в Isaac, без torch)        cv_worker.py  (отдельный процесс, torch+GPU)
   поток 700 мм при 1 м/с
   ворота x = -0.750 -> 6 кадров ──заявка──>   runtime/req/<товар>.json
                                                  DEFOM vitl / вход 480 / iters 24
                                                  облако -> габариты -> k -> класс
   класс  <──ответ──  runtime/res/<товар>.json
   пушер: класс D -> Cell.stroke()
   плуг:  класс B/C -> Plow.target(-16 / +16)

Запуск

# 1. работник CV (прогрев ~90 с: грузится vitl-энкодер)
cd /home/dasha/robozon-sorter/control_test
nohup /home/whatevenif/isaacsim/python.sh cv_worker.py > /tmp/cvworker.log 2>&1 &

# 2. УБЕДИТЬСЯ ПО PID, а не по файлу runtime/worker_ready
pgrep -af "python.*cv_worker.py"

# 3. открыть сцену заново (cell.prepare меняет её состояние), затем прогон
cd /home/dasha/robozon-sorter
python3 isaacsim_send.py --context cvsort --timeout 2580 --execution-timeout 2560 \
    --file control_test/run_sorting_cv.py

2. Зафиксированный бейзлайн CV

В measure_plane.py значения по умолчанию:

параметр значение
стереодвижок DEFOM-Stereo vitl (STEREO=defom)
вход сети 480 px по ширине (SW=480)
итерации 24 + scale_iters 8
окно кроп зоны осмотра (CROP=1)
сегментация не используется

Товар отделяется от полотна превышением над плоскостью (порог 20 мм) плюс отсев по плотности; сегментация не участвует вовсе.

Замер на статичных кадрах потока 700 мм (9 товаров): классы 8/9 = 89 %, габариты MAE медиана 32.8 мм, 469 мс на товар при такте 700 мс.

Веса: /home/dasha/isaac_assets/cv/defom-stereo/checkpoints/defomstereo_vitl_sceneflow.pth (1.53 ГБ), defomstereo_vits_sceneflow.pth (173 МБ), энкодер depth_anything_v2_vitl.pth (1.34 ГБ).

Сравнение движков на том же потоке

конфигурация MAE медиана классы время в такт 700 мс
DEFOM vitl, вход 480, iters 24 32.8 мм 8/9 = 89 % 469 мс да, +231 мс
DEFOM vits, вход 640, iters 24 33.9 мм 7/9 502 мс да
DEFOM vitl, вход 640, iters 12 37.5 мм 6/9 627 мс да
DEFOM vitl, вход 640, iters 24 29.9 мм 7/9 735 мс нет, 35 мс
CRE, кроп, вход 640 23.5 мм 7/9 551 мс да
CRE, кроп, исходный вход 25.6 мм 7/9 1366 мс нет
CRE, вся лента, исходный вход 32.4 мм 7/9 1701 мс нет
FastSAM + CRE 40.0 мм 5/9 753 мс нет
yolo26n-seg + CRE 41.2 мм 2/6 238 мс да
FoundationStereo (только CPU) 152.8 мм 4/9 7159 мс нет

CRE точнее по габаритам (23.5 против 32.8), DEFOM лучше по классам (8/9 против 7/9).


3. Кинематика

Ленты — 1.0 м/с

Семь дорожек, привод через PhysxSurfaceVelocityAPI. Скорость задаётся в локальной системе тела, а дорожки уложены по-разному: у _04 и _06 локальный +X смотрит в мировой −X. Направление выводится из мировой цели, величина делится на то, сколько мирового стоит одна локальная единица (у _06 масштаб 0.5).

ConveyorTrack_06криволинейный угол на 90°, четверть кольца с центром (−8.005, +1.042), радиусы 0.517…1.018. Линейный привод уводил товар в пустую середину кольца; направление задаётся хордой, сохраняющей радиус, и в мировых координатах.

Проверено: товар 160 мм проходит 7.93 м за 7.9 с ровно на 1.00 м/с без замедлений; поток из 9 товаров проходит всю линию 10.9 м.

Пушер — класс D

Нож двигается записью трансформа (Cell.blade_to), его призматический сустав выключен: иначе сустав тянет нож к своей цели, пока скрипт пишет его в другое место, и нож дрожит весь прогон. Ход 720 мм, срабатывание у PUSH_X = -3.900.

Плуг — классы B и C

Лезвие кинематическое, шарнир выключен (physics:jointEnabled=False), угол пишется напрямую (Plow.target). Силовой привод перенастраивали трижды и он не держал: звенел на ±21.4° быстрее, чем его успевала вести команда.

Углы: PLOW_PRESET = {"B": -16.0, "C": +16.0, "D": 0.0}. Положительный поворот отклоняет в −Y. Лезвие ставится заранее, до подхода товара. Скольжение товара вдоль кромки измерено: 315–350 мм, то есть товар ведётся, а не отбрасывается ударом.

Камеры — E60

Шесть камер, три стереопары, все на 700 мм над лентой, возвышение 60°, дистанция 808 мм, азимуты 90° / 208.3° / 330°. Точка осмотра (0.750, 0.0, 1.781).

Базы: D435 73.5 мм, Gemini305 26.5 мм, Gemini345 129.4 мм. Цена глубины соответственно 13.2 / 37.2 / 8.1 мм на пиксель диспаратности.

Расхождение с реальным стендом: у настоящего D435 база 50.0 мм (прочитано из прошивки), а в симуляции 73.5 — в 1.47 раза больше. Значит результаты симуляции для этого рига оптимистичнее реальности примерно в полтора раза. Не исправлено сознательно.


4. Замер последнего прогона замкнутого контура

Классов получено 7 из 9, верно 5 из 7. Верно: bag (D), backpack (C), lunchbox (B), detergent (B), box_400x400x300 (C).

Задержка от ворот до класса — медиана 0.70 с при 3.15 с до пушера и 7.10 с до плуга. Инференс 631–1284 мс. Запас четырёх- и десятикратный.

Пушер сработал по классу D от камер. Плуг предпозиционировался шесть раз.

Сквозная доставка: в контейнеры попало 5 из 9, в СВОЙ контейнер - 3 из 9.

причина потери сколько что именно
ошибка CV 2 bucket (D->C), box_300x200x200 (B->C)
класс верный, механика не довела 1 bag - пушер сработал, товар остался в лотке B вместо BinD
столкновение на входе 2 helmet и pillow, выпущены подряд
бросок пушера 1 detergent улетел на (+262, +2085)
плуг сдвинул недостаточно 1 box_400x400x300 на y = -1.49, за краем лотка C

Из четырёх потерь по механике ни одна не связана с распознаванием. Подробная таблица с координатами и зонами лотков - в README, раздел 10.6.

Сквозную доставку осмысленно мерить на шаге 1.4 м (там прежний прогон давал 9/9), а шаг 0.7 м использовать для замера классификации и габаритов.


5. Проблемы, которые сейчас есть

Не решены

  1. Класс D берётся неустойчиво. bucket (истинный k = 0.995) не определяется ни одной конфигурацией. На эталонной геометрии меша та же функция даёт 0.934, на нашем облаке 0.66 — разрыв целиком в качестве облака, не в метрике. Габарит ведра выходит вытянутым (333 × 239 при истинных 287 × 287), а вытянутое сечение высокого k дать не может.

  2. Габариты в движении хуже статичных. box_300x200x200 в потоке дал 513 × 452 против 311 × 218 на статичных кадрах и из-за этого ушёл в C вместо B. Причина видна в логе: detergent попал на ворота уже на x = −1.708, то есть мимо точки осмотра, а кроп привязан к неподвижной точке (−0.750). Товар в кадре смещён, в кроп попадает соседний.

  3. Два товара не доехали до ворот. helmet встал на x = +4.02, pillow на +1.15. Оба выпускались подряд (2.15 и 2.85 с); при их габаритах (354 и 455 мм) шаг 700 мм оставляет мало зазора, и они, судя по позициям, столкнулись у входа.

  4. Пушер выбрасывает товар. detergent закончил на (+262, +2085) — улетел на километры. Скорость ножа на пределе: PUSHER_MAX_SAFE = 2.5 м/с с пометкой «выше ~2.5 м/с кинематический нож сбрасывает товар с линии».

  5. Шаг 700 мм механически не даёт B/C. Лезвие плуга 0.63 м, на смену угла остаётся 0.07 м (10 % шага). Замерено: при 1.4 м — 9/9, при 0.7 м — 5/9. От скорости ленты не зависит: доля занятости лезвия = 0.63/0.70 = 90 %. Нужен шаг > ~0.95 м либо другой отводящий орган.

  6. Очень тонкие товары проходят под лезвием плуга. watch (4.2 мм) класса C проехал мимо: класс определяется верно, механика — нет.

Проверено и НЕ помогло

Каждый пункт — отдельный замер, все ухудшили результат:

попытка результат
выбор маски по плоскости ленты вместо воротного пикселя MAE 39.9 (было 40.0), классы 4/9 (было 5/9)
k подгонкой окружности P10/P90 подняло k без разбора формы: коробки пошли в D
k по трём мировым сечениям классы 6/9 (было 7/9)
сглаживание контура по угловым секторам k макс 0.67 (было 0.85), классы 6/9
проверка лево-право 0.5 / 1.0 / 1.5 px отсеивает 28–52 % пикселей, MAE 34.6 (было 32.4), время ×2
подгонка цилиндра RANSAC не сработала ни на одном товаре: доля точек в допуске < 60 %
отбраковка ракурса по центроиду, порог 60 мм MAE 65.4 (было 49.5) — откидывала два вида из трёх
ICP/RANSAC-совмещение облаков 11.9 → 40.5 мм, три ракурса видят разные поверхности

Общий вывод из этой серии: у нас не выбросы, а дырки в диспаратности. Любая правка, которая вычитает точки (сглаживание, лево-право, отбраковка), делает хуже. Помогает то, что повышает плотность или уменьшает область поиска: кроп зоны осмотра (MAE 32.4 → 25.6) и понижение входа сети (25.6 → 23.5).

Недоделанная половина рецепта лево-право: заполнение мелких внутренних дырок и edge-aware фильтр с запретом интерполяции через границу. Сейчас реализовано только удаление.

Установлено, что НЕ виновато

  • Калибровка камер. Восстановленное полотно садится на эталонную плоскость со смещением 0.59 / 0.56 / 1.13 мм и наклоном 0.56° / 0.35° / 1.72° по трём ригам. Ни интринсики, ни боковое расположение, ни положение виртуальных камер не при чём.
  • Стереодвижок как таковой. CRE проверен против штатной глубины RealSense на физическом стенде: отношение 0.998 и 1.000 на 249 тыс. пикселей.
  • Покрытие ракурсами. Дуга сечения у ведра покрыта на 295–360°.

6. Ловушки, на которых уже теряли время

Каждая давала правдоподобный, но неверный результат.

  1. Узлы OmniGraph надо УДАЛЯТЬ, а не деактивировать. SetActive(False) убирает прем из обхода, но собранный граф продолжает работать: ConveyorBeltGraph обнулял surfaceVelocity за 5 шагов после play, DiverterAnimGraph останавливал таймлайн.

  2. Невидимость не убирает коллайдер. capture_roi.py прятал снятый товар через MakeInvisible(), и после двух прогонов захвата в точке осмотра стояло 18 невидимых, но твёрдых предметов. Поток вставал на них «посреди ConveyorTrack_02». Лечится cell.clear_capture_parks(), вызывается в prepare().

  3. BBoxCache во время прогона врёт. Он читает авторские трансформы из слоя USD, а физика пишет в Fabric. Отчёт показывал, что все товары стоят в точках выпуска, хотя таймлайн отработал 26 с. Положения читать через RigidPrim.get_world_poses().

  4. play() после stop() перематывает в начало и сбрасывает физику. Обработчик, «возобновляющий» остановившийся таймлайн, обнуляет весь опыт.

  5. Заданная частота физики не применяется. timeStepsPerSecond=120 не подействовал, фактический шаг 83.33 мс (60 Гц). Скорости выходили ровно вдвое завышенными. Время брать из таймлайна.

  6. Файл runtime/worker_ready остаётся от прошлого запуска и даёт ложную готовность. Проверять работника по PID.

  7. cloud_from_roi возвращает ПАРУ (облако товара, облако полотна). Складывание кортежа целиком роняет np.vstack на разнородных формах.

  8. Меши items_flow/ уже в каталожном масштабе и уже посажены на z = 0, коллайдеры в них уже есть. Домасштабирование и свои коллайдеры ломают спавн — товары не едут.

  9. ArUco-метки не видны в ИК (на физическом стенде): типографская краска на 850 нм в значительной мере прозрачна. Позу брать из цветного кадра с ЦВЕТНЫМИ интринсиками и переводить в систему ИК заводскими экстринсиками.


7. Что делать дальше — по приоритету

  1. Привязать кроп к товару, а не к неподвижной точке осмотра. Это лечит проблему 2 — самую вредную из открытых: из-за неё габариты в движении вдвое хуже статичных.
  2. Снизить скорость ножа пушера — проблема 4, товар улетает.
  3. Разнести выпуск товаров по времени либо увеличить шаг — проблема 3.
  4. Доделать вторую половину фильтрации лево-право (заполнение дырок, edge-aware).
  5. FoundationStereo на GPU: заблокировано внешне — onnxruntime-gpu требует CUDA 13, на сервере 12.8; колёс под CUDA 12 для python 3.12 нет; onnx2torch падает на динамическом Clip; TensorRT не установлен. Нужен либо .pth через код репозитория, либо CUDA 13 (установка требует прав root).