Сортировочная ячейка 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:
dasha_f
2026-08-01 13:07:24 +00:00
parent 6ce460378a
commit 0d32f32db0
342 changed files with 18000 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
"""Какая камера сдвинута: сверка живой сцены с тем, что записано в файле.
В сцене несколько камер разного назначения - шесть стереокамер стенда в /RigRS, корпуса
камер в /World/CameraBodies и служебная перспектива вьюпорта. Сдвиг любой из них
выглядит одинаково, а чинятся они по-разному, поэтому сначала находится ТА САМАЯ.
Живая сцена читается из памяти, эталон - из слоя на диске: несовпадение и есть правка,
сделанная мышью во вьюпорте.
"""
import sys
REPO = "/home/dasha/robozon-sorter"
if REPO not in sys.path:
sys.path.insert(0, REPO)
import omni.usd
from pxr import Usd, UsdGeom, Gf
live = omni.usd.get_context().get_stage()
path = live.GetRootLayer().identifier
print(f"живая сцена: {path}\n")
disk = Usd.Stage.Open(path)
cache_l = UsdGeom.XformCache()
cache_d = UsdGeom.XformCache()
cams = [p for p in live.Traverse() if p.IsA(UsdGeom.Camera)]
print(f"камер в сцене: {len(cams)}\n")
print(f" {'камера':44s} {'положение сейчас':>34s} расхождение с файлом")
print(" " + "-" * 104)
moved = []
for c in cams:
p = str(c.GetPath())
Ml = cache_l.GetLocalToWorldTransform(c)
tl_ = Ml.ExtractTranslation()
d = disk.GetPrimAtPath(p)
if not d or not d.IsValid():
print(f" {p:44s} ({tl_[0]:+7.3f},{tl_[1]:+7.3f},{tl_[2]:+7.3f}) нет в файле")
continue
Md = cache_d.GetLocalToWorldTransform(d)
td = Md.ExtractTranslation()
dt = (tl_ - td).GetLength()
# угловое расхождение по направлению взгляда камеры (-Z в её системе)
vl = Ml.TransformDir(Gf.Vec3d(0, 0, -1)); vd = Md.TransformDir(Gf.Vec3d(0, 0, -1))
vl = vl / (vl.GetLength() or 1); vd = vd / (vd.GetLength() or 1)
import math
ang = math.degrees(math.acos(max(-1.0, min(1.0, vl[0]*vd[0] + vl[1]*vd[1] + vl[2]*vd[2]))))
flag = "СДВИНУТА" if (dt > 0.001 or ang > 0.1) else "совпадает"
print(f" {p:44s} ({tl_[0]:+7.3f},{tl_[1]:+7.3f},{tl_[2]:+7.3f}) "
f"{dt*1000:7.1f} мм / {ang:5.2f}° {flag}")
if flag == "СДВИНУТА":
moved.append((p, td, tl_, dt, ang))
# служебная перспектива вьюпорта - её в файле обычно нет, она хранится в сессии
persp = live.GetPrimAtPath("/OmniverseKit_Persp")
if persp.IsValid():
M = cache_l.GetLocalToWorldTransform(persp)
t = M.ExtractTranslation()
print(f"\n перспектива вьюпорта /OmniverseKit_Persp: "
f"({t[0]:+.2f}, {t[1]:+.2f}, {t[2]:+.2f})")
print()
if moved:
print(f"СДВИНУТО КАМЕР: {len(moved)}")
for p, td, tlv, dt, ang in moved:
print(f" {p}")
print(f" было в файле: ({td[0]:+7.3f},{td[1]:+7.3f},{td[2]:+7.3f})")
print(f" стало сейчас: ({tlv[0]:+7.3f},{tlv[1]:+7.3f},{tlv[2]:+7.3f})")
print(f" расхождение {dt*1000:.1f} мм, поворот {ang:.2f}°")
else:
print("Ни одна камера-прем не сдвинута относительно файла.")
print("Значит двигали перспективу вьюпорта - она в файл не пишется и на прогоны не влияет.")