Сортировочная ячейка 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>
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
# .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)
|
||||
```
|
||||
|
||||
### Запуск
|
||||
|
||||
```bash
|
||||
# 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).
|
||||
Reference in New Issue
Block a user