Files
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

75 lines
3.0 KiB
Python

"""Verify the rigs against each other: standoff, pair geometry, and a reprojection test.
The reprojection test is the end-to-end one: take the inspection point, transform it into
each camera's frame with that camera's own extrinsics, project with its intrinsics, and
check where it lands. A correctly aimed camera puts it on the principal point.
"""
import math
import omni.usd
from pxr import Gf, Usd, UsdGeom
stage = omni.usd.get_context().get_stage()
xc = UsdGeom.XformCache()
TARGET = Gf.Vec3d(-0.750, 0.0, 1.781)
def cam(name):
for p in stage.Traverse():
if p.IsA(UsdGeom.Camera) and p.GetName() == name:
return p
raise KeyError(name)
NAMES = ["RealSense_D435_Left", "RealSense_D435_Right",
"Orbbec_Gemini305_Left", "Orbbec_Gemini305_Right",
"Orbbec_Gemini345_Left", "Orbbec_Gemini345_Right"]
RES = {"RealSense_D435": (1280, 720), "Orbbec_Gemini305": (1280, 800),
"Orbbec_Gemini345": (1280, 800)}
print("=== standoff (requested 600 mm) ===")
P, F = {}, {}
for n in NAMES:
M = xc.GetLocalToWorldTransform(cam(n))
p = Gf.Vec3d(M.ExtractTranslation())
f = M.TransformDir(Gf.Vec3d(0, 0, -1)); f = f / (f.GetLength() or 1)
P[n], F[n] = p, f
print(f" {n:>24}: {(TARGET-p).GetLength()*1000:7.1f} mm")
print("\n=== pair geometry ===")
for rig in ("RealSense_D435", "Orbbec_Gemini305", "Orbbec_Gemini345"):
l, r = P[f"{rig}_Left"], P[f"{rig}_Right"]
fl, fr = F[f"{rig}_Left"], F[f"{rig}_Right"]
base = (r - l).GetLength()
ang = math.degrees(math.acos(max(-1, min(1, Gf.Dot(fl, fr)))))
kind = ("OPPOSING - not a stereo pair" if ang > 150 else
"rectified (parallel axes)" if ang < 0.5 else f"verged {ang:.2f} deg")
print(f" {rig:>18}: baseline {base*1000:8.1f} mm axes {ang:6.2f} deg {kind}")
print("\n=== reprojection of the inspection point (should land on the principal point) ===")
for n in NAMES:
prim = cam(n)
c = UsdGeom.Camera(prim)
fl_mm = c.GetFocalLengthAttr().Get()
ha = c.GetHorizontalApertureAttr().Get()
va = c.GetVerticalApertureAttr().Get()
rig = n.rsplit("_", 1)[0]
W, H = RES[rig]
fx = fl_mm / ha * W
fy = fl_mm / va * H
cx, cy = W / 2.0, H / 2.0
M = xc.GetLocalToWorldTransform(prim)
Pcam = M.GetInverse().Transform(TARGET) # world -> camera frame
z = -Pcam[2] # camera looks down -Z
if z <= 1e-6:
print(f" {n:>24}: BEHIND the camera"); continue
u = cx + fx * (Pcam[0] / z)
v = cy - fy * (Pcam[1] / z)
print(f" {n:>24}: depth {z*1000:6.1f} mm pixel ({u:7.1f},{v:7.1f}) "
f"offset from centre ({u-cx:+5.1f},{v-cy:+5.1f}) px")
print("\n=== cross-rig consistency: does every camera see the same point in front of it? ===")
depths = []
for n in NAMES:
M = xc.GetLocalToWorldTransform(cam(n))
depths.append(-M.GetInverse().Transform(TARGET)[2])
print(f" depth spread across all six: {min(depths)*1000:.1f} .. {max(depths)*1000:.1f} mm"
f" (max-min = {(max(depths)-min(depths))*1000:.1f} mm)")