YeonUk-Kim0120/smartfarm_all_unify

smartfarm_all_unify

Python

0

1 commits

updated Sep 23, 2026

See the code

README

Harvest Pipeline (Pedicel fork)

이 폴더는 Harvest_Pipeline의 복사본에 RADIO-DPT-Pose 꼭지 백엔드를 추가한 버전이다 → PEDICEL_DPT_INTEGRATION.md. third_party/는 원본 폴더로의 symlink다.

ZED 2i 스테레오 기반 과일(구형 물체) 인지 파이프라인. 같은 프레임에서 깊이 추정 + 추적/세그멘테이션 + amodal + 3D 구 피팅을 락스텝으로 수행하고, 결과를 Rerun / ROS 2로 시각화·발행한다.

ZED 2i (HD1080 stereo)
   ├─ FoundationStereo ─────────────► depth
   └─ EdgeTAM(track) ─ Ellipse R-CNN ─► visible mask + amodal ellipse
                                         │
                                         ▼
                              SphereFitter (RANSAC 3D sphere
                              + 정확한 conic 재투영 vs amodal)
                                         │
                   ┌─────────────────────┴─────────────────────┐
                   ▼                                           ▼
       3D bounding box / pointcloud              [Pose 버튼 이벤트]
                                                  center + pedicel YOLO-Pose
                                                  → tomato local +X axis
                   └─────────────────────┬─────────────────────┘
                                         ▼
                                      Rerun · ROS 2

구성 요소

  • FoundationStereo (third_party/Fast-FoundationStereo) — 스테레오 깊이 (선택: TensorRT fp16, --ffs-trt, ~1.5배)
  • EdgeTAM (third_party/EdgeTAM) — 프롬프트 1개로 시작하는 온라인 추적/세그멘테이션
  • Ellipse R-CNN (third_party/ellipse-rcnn) — 가려진 부분까지의 amodal 타원
  • SphereFitter — 깊이+마스크로 3D 구를 맞추고 이미지로 정확 재투영
  • Tomato Pose — 같은 cycle의 RGB·FFS depth·visible mask·추적 bbox로 중심과 중심→꼭지 축을 이벤트 방식으로 계산 (weights/tomato_pose/pedicel_pose.pt 상주 로드)
  • ★ 이 fork (Harvest_Pipeline_Pedicel): 꼭지 모델을 pedicel-2d의 RADIO-DPT-Pose(base+tip 2 keypoint, weights/tomato_pose/radio_dpt_pose_v4_ih2_on_s0.pt)로 교체. base·tip을 FFS depth로 3D 승격해 base→tip 축을 Rerun/ROS(/tomato/pedicel_points, /tomato/pedicel_axis)로 실시간 확인하고, 그리퍼에 가려 중심을 못 구해도 꼭지 pose를 발행한다. 사용법·검증: PEDICEL_DPT_INTEGRATION.md. 원래 모델은 트래킹 모드 기본값(--tracking-pedicel-backend yolo).

2026-09-22 변경: 로봇이 파지하기 전(트래킹 모드 Pose 버튼)은 원래 1-keypoint YOLO 모델(--tracking-pedicel-backend yolo, 중심→꼭지 축)만 쓰고, RADIO-DPT(base+tip)는 그리퍼 파지 신호 후 grasp 모드에서만 실행된다. /tomato/pedicel_points·/tomato/pedicel_axis도 grasp 모드에서만 발행. --pedicel-backend는 이제 grasp 모드 모델 (radio_dpt | yolo_pose). 트래킹에서도 RADIO-DPT를 쓰려면 --tracking-pedicel-backend radio_dpt.

설치

자세한 버전·패치는 requirements.txt 참고. 요약:

# 시스템: CUDA 12.6, ROS 2 Humble, ZED SDK 5.2.3 (pyzed)
source /opt/ros/humble/setup.bash

# PyTorch (CUDA 12.8 wheels)
pip install torch==2.10.0 torchvision==0.25.0 xformers==0.0.35 \
    --index-url https://download.pytorch.org/whl/cu128
pip install -r requirements.txt

# vendored 모델 (requirements.txt의 주석대로 clone + 패치)
#   Fast-FoundationStereo / EdgeTAM(pip install -e .) / ellipse-rcnn(PYTHONPATH)

uv venv (이 폴더의 .venv, 2026-09-14 구성·검증)

시스템 python3.10 + ROS 2 Humble rclpy를 그대로 쓰고, ~/.local의 user-site(충돌하는 sam2 1.1.0 등)와는 격리된다 → PYTHONNOUSERSITE=1 불필요. third_party/는 symlink이므로 EdgeTAM editable 설치는 절대경로로.

cd Harvest_Pipeline_Pedicel
uv venv --python /usr/bin/python3.10 .venv
uv pip install --python .venv/bin/python torch==2.10.0 torchvision==0.25.0 xformers==0.0.35 \
    --index-url https://download.pytorch.org/whl/cu128
uv pip install --python .venv/bin/python -r requirements.txt \
    --extra-index-url https://download.pytorch.org/whl/cu128 --index-strategy unsafe-best-match
uv pip install --python .venv/bin/python setuptools wheel pytest lark
(cd third_party/EdgeTAM && SAM2_BUILD_CUDA=0 uv pip install --python "$OLDPWD/.venv/bin/python" -e . --no-build-isolation --no-deps)

# 실행 (매 셸)
source /opt/ros/humble/setup.bash
source .venv/bin/activate
HF_HUB_OFFLINE=1 python perception/pedicel_offline_check.py --out data/pedicel_check   # 모델·환경 확인
python perception/web_gui.py --crop 230 --ros --tomato-pose
# 테스트: ROS의 launch_testing pytest 플러그인이 최신 pytest와 안 맞으므로 autoload 끔
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest perception --ignore=perception/test_real.py
  • pyzed(ZED SDK)는 venv에 없다. 라이브 카메라를 쓰려면 ZED SDK 설치 후 .venv/bin/python /usr/local/zed/get_python_api.py. 리플레이·오프라인 검증에는 불필요.
  • 이 PC(RTX 3090 ×2, driver 570 / CUDA 12.8, transformers 5.3.0)에서 오프라인 검증 18/18 valid, 중앙값 596 ms.
  • 가중치 상태(2026-09-18): 한때 이 PC 사본이 52 MiB에서 잘려 있던 weights/ellipse_rcnn/best.pt(159 MB), weights/ffs_trt/*.onnx(136 MB), tomato-vlm safetensors는 원본 Harvest_Pipeline/에서 다시 받아 복사·검증 완료(Ellipse R-CNN 학습 가중치 로드 OK). 따라서 --ellipse-ckpt 더미 옵션은 더 이상 필요 없다. third_party/는 symlink라 VLM 가중치는 자동으로 공유된다.

ZED SDK + FFS-only 라이브 뷰어 (EdgeTAM/Ellipse 없이 꼭지만)

perception/live_pedicel_rerun.py: ZED → FoundationStereo depth → 고정 target zone(기본, 화면 중앙 정사각형) → RADIO-DPT base/tip → Rerun. 그리퍼가 토마토를 zone 안에 두는 시나리오라 검출기가 필요 없다(tomato-vlm YOLO는 파지 상태에서 박스가 틀려 --detector yolo는 보조용). 세그멘테이션이 없으므로 중심은 unavailable, pose 위치는 3D base 점.

# 1) ZED SDK 5.2.3 (cu12.8) — 설치 파일은 ~/Downloads/zed_sdk/ 에 받아 둠. sudo 필요, 1회
sudo ~/Downloads/zed_sdk/ZED_SDK_Ubuntu22_cuda12.8_tensorrt10.9_v5.2.3.run -- silent skip_cuda skip_tools
sudo cp ~/Downloads/zed_sdk/99-slabs.rules /etc/udev/rules.d/ && sudo udevadm control --reload-rules && sudo udevadm trigger
sudo chmod -R o+rX /usr/local/zed && sudo chmod -R a+rwX /usr/local/zed/settings   # silent 설치가 root:zed 770으로 만듦
# 2) pyzed를 venv에 (get_python_api.py가 python 버전에 맞는 wheel을 받아 pip 설치; venv에 pip 설치돼 있음)
.venv/bin/python /usr/local/zed/get_python_api.py
.venv/bin/python -c "import pyzed.sl as sl; print(sl.Camera.get_sdk_version())"
# 3) 실행 (카메라 연결: lsusb | grep 2b03 에 f880+f881 둘 다 보여야 함)
source .venv/bin/activate
python perception/live_pedicel_rerun.py --source zed --crop 230 --tta none                       # 중앙 zone
python perception/live_pedicel_rerun.py --source zed --crop 230 --box 380,120,680,420 --zone-depth 0.30,0.45
python perception/live_pedicel_rerun.py --source npz --save data/pedicel_live/x.rrd              # 카메라 없이 npz 리플레이
  • 화면: 초록/빨강 = target zone(안에 --zone-depth 범위의 depth 픽셀이 --zone-fill 이상이면 초록 "TOMATO IN ZONE", 아니면 빨강 "PLACE TOMATO IN GREEN ZONE"), 노랑 = 꼭지 모델이 실제로 보는 1.4× 정사각 crop. 3D 뷰에는 zone×깊이 범위의 frustum(world/target_zone)이 같은 색으로 그려진다. zone이 비어 있으면 꼭지 모델을 돌리지 않는다(--always-run으로 강제).

  • --zone-depth는 그리퍼 작업 거리 ±수 cm로 좁힐 것. 넓게 두면 책상·배경 depth가 zone을 채워 항상 초록이 된다.

  • crop 고해상도 depth(--refine-scale 2, 기본 켜짐): 가는 꼭지의 tip depth가 배경으로 새서 tip_on_base_plane (정면 평행 가정 → 축이 누움)으로 떨어지는 문제 대책. 꼭지 모델 crop(1.4× 정사각 +--refine-pad)만 좌·우에서 잘라 --refine-scale배 업샘플해 FFS를 한 번 더 돌리고, 그 depth로 base/tip을 3D 승격한다. 우측 crop은 --zone-depth의 zmax에서의 시차만큼 왼쪽으로 옮겨 자르므로 zmax보다 가까운 물체는 crop 안에서 항상 양의 시차를 가진다(zmax보다 먼 배경은 무효). 전체 프레임 pass는 그대로 pointcloud·zone 판정에 쓴다. Rerun world/cam/refine, refine_depth에 crop 결과 표시. --full-scale 0.5 --res HD1080로 전체 pass를 줄이고 crop은 실제 1080p 픽셀을 쓰는 조합도 가능. 합성 스테레오로 depth 복원 검증.

  • Rerun 레이아웃은 blueprint로 고정: 왼쪽 3D, 오른쪽 위 전체 이미지(파란 박스 = refine crop 영역), 오른쪽 아래 refine/image(업샘플 crop + 오버레이) / refine/depth. --dump DIR로 프레임별 main/refine/refine_depth PNG 저장. 실카메라(ZED 2i HD720, 2026-09-14)에서 refine ×2 crop 1184×932 포함 프레임당 약 600 ms 확인.

  • 배율/zone 크기와 비용 (ZED 2i HD720 crop 230, zone 0.30–0.45 m, RTX 3090; 전체 pass + refine + 꼭지 모델 포함):

    --center-frac--refine-scalerefine crop프레임 시간FFS 피크 VRAM
    0.4 (288 px)21184×932≈600 ms1.7 GB
    0.3 (216 px, 기본)3 (기본)1323×1098≈690 ms2.5 GB
    0.341644×1464≈1020 ms5.0 GB

    crop 안 시차 범위 (d(zmin) − d(zmax)) × scale이 체크포인트 한계(max_disp 416)를 넘으면 안 된다. 시작 로그에 in-crop range ... <= max_disp limit 416로 출력되며, 필요 시 해당 호출만 max_disp를 자동으로 올린다(기본 192). 넘으면 --zone-depth를 좁히거나 배율을 낮춘다. zone 깊이 범위를 좁힐수록 crop도 작아져 더 높은 배율이 가능하다.

  • VRAM (2026-09-14 실측, HD720, zone 0.3, refine ×3): 뷰어 프로세스 5.9 GB(스크립트가 PYTORCH_CUDA_ALLOC_CONF= expandable_segments:True를 설정; 없으면 7.8 GB) + Rerun 뷰어 0.7 GB. 내역: CUDA 컨텍스트 0.3, RADIO-DPT 가중치 1.8(fp16), FFS 가중치 0.07, ZED SDK 0.1, FFS 전체 pass 피크 3.4, refine ×3 피크 5.2(일시적). 12 GB GPU에 여유 있음. nvidia-smi 총합에는 데스크톱 등 다른 프로세스(이 PC ~1.3 GB)가 포함되니 프로세스별로 볼 것.

  • 옵션: --refine-scale 3 --refine-pad 32 --full-scale 1 --dump DIR (뷰어는 전체 pass를 항상 돌림; grasp 모드는 crop만) --detector {center,box,yolo,stored} --center-frac 0.4 --box --zone-depth --zone-fill 0.25 --hold 10(yolo 박스 유지) --tta --pedicel-device --hz --stride --zmax --save --no-spawn.

Grasp 모드 (web_gui.py, 2026-09-17) — 그리퍼 파지 신호 → 꼭지 모델

--tomato-pose로 띄우면 기존 버튼 줄 아래에 🤖 그리퍼 파지 신호 → 꼭지 모델 / 파지 해제 행이 생긴다. 실기에서는 --ros일 때 --grasp-topic(기본 /harvest/grasped, std_msgs/Bool)의 True/False가 같은 일을 한다.

  • 파지 신호: EdgeTAM 추적 루프를 멈추고(/stop과 동일) perception/grasp_pedicel.py의 루프를 돌린다: ZED → FFS 전체 pass(포인트클라우드·zone 판정) → crop 업샘플 FFS(refine) → RADIO-DPT base/tip → PoseSolution → Rerun(추적 모드와 같은 엔티티 world/tomato_pose/*, diagnostics/tomato_pose/image, world/target_zone)

    • ROS /tomato/{pose,stem_direction,pedicel_points,pedicel_axis}(--pose-publish-rate로 반복), /tomato/pose_status(mode: grasp), /harvest/{points,rgb}.
  • 모델 선택(2026-09-18): 행의 셀렉트 박스로 우리 모델 (RADIO-DPT-Pose) ↔ YOLO26s-pose (fine-tuned)를 고른다. 시작 시에는 --pedicel-backend(기본 radio_dpt) 하나만 로드하고, 다른 쪽은 그 모델을 고른 파지 신호가 처음 왔을 때 로드해(YOLO 0.2 s, RADIO ~3 s) 이후 상주한다. grasp 상태줄에 model … (loaded: …)로 표시. yolo_pose = pedicel-2d runs/y26s-ih2-fx7/s0/best.pt (in-hand 학습, 2 keypoint base/tip, 640², ~11 ms)를 weights/tomato_pose/yolo26s_pose_ih2_fx7_s0.pt로 복사한 것. perception/pedicel_yolo.py가 RADIO 래퍼와 같은 입력 규약(1.4× 정사각 crop → 1024 letterbox → 640)으로 감싸 estimator의 radio_dpt 경로를 공유한다(TomatoPoseEstimator(backend='yolo_pose')). 오프라인 검증(18 npz, tta none): valid 14/18, base 위치는 RADIO와 대체로 5 px 안, 미검출 1장·tip conf<0.5 3장(YOLO는 예측을 포기하는 경우가 있음; MODEL_SELECTION.md 참고). python perception/pedicel_offline_check.py --backend yolo_pose --tta none --out data/pedicel_check_yolo

  • zone: 항상 화면 중앙 고정 정사각형(--grasp-zone-frac, 기본 0.3). 로봇 팔이 토마토를 카메라 앞으로 가져오는 시나리오라 캔버스/VLM/추적 박스는 zone에 쓰지 않는다(2026-09-18 수정; 이전에는 캔버스 1번 박스가 있으면 그 박스를 썼음). depth 범위·refine 배율· "zone 비어도 실행"은 행의 입력칸으로 파지 시점마다 바꿀 수 있다. zone이 비면(depth 범위 안 픽셀 < --grasp-zone-fill) 모델을 돌리지 않고 이전 pose 반복 발행도 멈춘다.

  • 브라우저에 오버레이(zone·crop·base/tip)가 실시간으로 뜨고, grasp 상태줄에 state·fill·conf·axis·길이·추론시간이 나온다.

  • 파지 시 ZED 재오픈(2026-09-18): 파지 행의 해상도 셀렉트(2K / 1080p / 720p=전환 없음, 기본은 --grasp-res=HD2K)로 세션마다 고른다 — 파지 신호가 오면 카메라를 닫고 이 해상도로 다시 열어(약 2 s) grasp 루프를 돌리고, 해제 시 --res(HD720)로 복귀한다. 캔버스 zone과 --crop은 폭 비율(×1.725)로 자동 스케일(--grasp-crop으로 지정 가능). 720p 대비 crop에 실제 픽셀이 1.7배 들어간다. 재오픈한 소스는 이전 프레임 번호를 이어받는다(ZedLatestSource(idx_offset=…)) — 번호가 0부터 다시 시작하면 Rerun의 "frame" 타임라인이 과거로 돌아가 3D 뷰가 멈춘 것처럼 보였다(2026-09-18 수정, 라이브 검증: 720p 700 → 2K 725… → 720p 1138). 전제 조건 수정: ZED SDK가 연 /dev/video* fd를 이후 spawn되는 자식(Rerun 뷰어, VLM 워커)이 상속하면 close 후 재오픈이 CAMERA STREAM FAILED TO START로 실패한다 → orchestrator.ZedLatestSource가 open 직후 장치 fd에 FD_CLOEXEC를 건다.

  • 스테레오 탐색 범위 ≠ zone 범위(2026-09-18 원인 규명): 같은 장면(모니터 0.95 m)으로 비교한 결과 crop 자체는 문제가 없다 (전체 프레임 0.954 m = 같은 좌표 crop 0.953 m = ×1.5 업샘플 crop 0.954 m). 문제는 우측 crop을 zone의 zmax(0.45 m) 시차만큼 옮긴 것으로, 그 범위 밖 배경이 0.40 m로 접혀 나왔다. 그래서 --grasp-stereo-far(기본 1.5 m)를 두어 shift는 이 거리의 시차로 잡고, 배율은 (d(zmin)−d(far))×scale ≤ 416에 맞춰 자동 결정한다(2K에서 ×1.1, 720p에서 ×2.2). 결과: 배경이 LR 검사 없이도 0.954 m로 제자리에 복원된다. 배율을 더 올리려면 zmin을 올리거나 far를 낮춘다.

  • 좌우 일관성 검사(기본 켜짐, --grasp-no-lr-check로 해제): 우측 crop을 zmax 시차만큼 옮겨 자르므로 zmax보다 먼 배경은 crop 안에서 음의 시차여야 하는데 FFS는 양의 시차만 내서, 배경이 zone 깊이 범위 안으로 접혀 "앞으로 튀어나온" 점이 생겼다. 거울 쌍(flip(R), flip(L))을 같은 배치로 한 번 더 풀어 disp_R(x−d) ≈ d가 아닌 픽셀(+300 px 미만 연결 성분)을 무효 처리한다. far=1.5 m 이후에는 1.5 m 밖 배경에만 관여한다. 비용은 refine pass 2배(2K ×1.1: 214 → 413 ms, 피크 2.6 → 5.1 GB; 프로세스 총 8.4 GB). 12 GB에 여유가 필요하면 끄고 zone 판정에 맡겨도 된다. 배경 위 keypoint는 depth가 없어 depth_unavailable로 실패하는 것이 정상.

  • crop만 복원(기본): grasp 모드는 전체 프레임 FFS pass를 돌리지 않고 crop만 스테레오 복원한다(--grasp-full-depth로 전체 pass 추가). zone 판정과 Rerun 포인트클라우드(crop 영역, dense, stride 2)가 모두 crop depth에서 나온다. --grasp-refine-iters 16 (crop pass GRU 반복; 전체 pass는 8). crop 시차 범위 (d(zmin)−d(zmax))×scale이 416을 넘으면 세션 배율을 자동으로 낮춘다. 실측(RTX 3090, 실제 HD2K 쌍, zone 0.30–0.45 m, crop 731×584@×1):

    카메라refinecropFFS 시간피크 VRAM
    HD720×3 (iters 16)1323×1098394 ms3.5 GB
    HD2K×1731×584101 ms0.9 GB
    HD2K×1.5 (기본)1096×876247 ms2.4 GB
    HD2K×21462×1168490 ms5.0 GB
    HD2K×2.51828×1460859 ms9.3 GB

    --grasp-refine-scale 기본값: HD1080/HD2K 재오픈 시 1.5, HD720이면 3. 라이브 검증: 파지→HD2K→유효 pose(0.84 s/프레임)→ 해제→HD720 복귀를 2회 반복 성공.

  • 모드 전환 시 Rerun 정리(2026-09-18): 추적 루프가 멈추면(Stop/파지) world/box, world/cam/image/{mask,tracked}를 마지막 프레임+1에 tombstone하고, 파지 세션 시작 프레임에서 world/tomato_pose·진단 이미지도 지운다. 파지 종료 시에는 grasp 엔티티(world/target_zone, world/cam/image/target, world/tomato_pose)를 같은 방식으로 지운다. (Clear는 스레드별 타임라인에 놓이므로 시각을 지정하지 않으면 적용되지 않던 문제 수정.) 이전 추적 결과나 720p 좌표의 옛 박스가 2K 파지 화면에 남던 현상이 이것이었다.

  • 파지 해제 → idle. 다시 추적하려면 Start를 누른다. 세그멘테이션이 없으므로 pose 위치는 3D base 점(center_source=pedicel_base_point).

  • 연속 실행이므로 --pedicel-tta none(0.15 s) 권장. --ffs-trt면 엔진이 고정 크기라 refine은 자동으로 꺼진다.

  • (2026-09-18 이후 best.pt가 복구되어 --ellipse-ckpt 더미는 불필요.) 검증(2026-09-17, replay 합성 스테레오): 버튼·ROS 토픽 시작/해제, /tomato/pedicel_points (base,tip 2행), /tomato/pose_status 발행 확인.

source /opt/ros/humble/setup.bash && source .venv/bin/activate
python perception/web_gui.py                     # 2026-09-18부터 기본값: --crop 230 --ros(소스돼 있으면) --vlm --tomato-pose --pedicel-tta none
python perception/web_gui.py --source replay --seq data/pedicel_live/synth_seq   # 카메라 없이 (crop 자동 0)
python perception/web_gui.py --no-vlm --no-ros   # 끄기
ros2 topic pub --once /harvest/grasped std_msgs/msg/Bool "data: true"     # 로봇 쪽 신호 시뮬레이션

실행

0. 브라우저 GUI — 2D 박스 지정 + Start/Stop ★★ 추천

모델과 카메라를 한 번만 로드해 유지하고, 브라우저에서 첫 프레임에 2D 박스를 그린 뒤 Start/Stop으로 3D 검출을 제어한다. 3D 결과(포인트클라우드 + 3D bbox + 카메라)는 Rerun 창에 표시.

source /opt/ros/humble/setup.bash
python3 perception/web_gui.py --crop 300                # 라이브 ZED (기본)
python3 perception/web_gui.py --crop 230 --vlm --ros --tomato-pose  # 자동검출+Pose 통합
python3 perception/web_gui.py --source replay # 녹화 시퀀스 (기본 data/tomato_seq)
# 다른 녹화 + 프레임 범위 + 루프:
python3 perception/web_gui.py --source replay --seq data/line_5_r/seq --start 500 --end 610 --loop --crop 230
# 브라우저에서 http://localhost:8000  →  물체에 박스 드래그  →  ▶ Start / ■ Stop

브라우저 = 첫 프레임에 박스 0~3개 드로잉(번호·색, "박스 지우기") + Start/Stop, Rerun 창 = 3D 결과. 박스 여러 개면 객체별 mask/2D·3D박스/Kalman이 각각, 0개면 검출 없이 장면만. 물체가 화면에서 벗어나 트래킹을 놓치면 그 객체 오버레이가 사라지고 장면(포인트클라우드+카메라)은 남는다.

replay 옵션 (--source replay):

  • --seq <dir> : 녹화 시퀀스 폴더 (left/, right/, K.npy, 있으면 pose/)
  • --start N --end M : 사용할 프레임 범위 (inclusive)
  • --loop : 끝나면 처음으로 되돌아가 반복 (루프마다 박스로 검출 재초기화)
  • --crop N : 왼쪽 N px 잘라냄 (스테레오 invalid strip 제거; HD720이면 ~230, HD1080이면 ~300). FFS는 full 쌍으로 depth를 만든 뒤 crop→speckle 제거(경계 보존), EdgeTAM/박스/intrinsics(cx)도 동일 crop.
  • --ema N : depth 시간 EMA의 span(프레임, 기본 5). 실제 켜고/끄기는 GUI의 "Depth EMA" 버튼. ⚠️ 카메라 정지일 때만 켜세요 (움직이면 픽셀이 다른 3D점을 봐서 잔상). 정지 씬에서 depth 잔떨림 감소.
  • world 3D 박스는 항상 Kalman filter로 평활됨 (중심=등속 모델, 반경=random-walk; world 프레임이라 카메라가 움직여도 유효, 루프/재획득 시 리셋). orchestrator.KalmanBox의 q/r 노이즈로 강도 조절.
  • --vlm : 토마토 VLM 자동검출 활성화 (YOLO+Qwen3-VL 4bit, 코드·weight third_party/tomato-vlm/). 브라우저 "VLM 자동검출" 버튼 → 현재 프레임에 YOLO→verifier→ripeness→scoring 실행 → 익은 토마토 박스(최대 3개) 자동 채움(수동 드로잉 대신, 0~3개). 프로세스 격리 방식: VLM은 vlm_worker.py라는 별도 자식 프로세스에서 돌아요(터미널은 그대로 1개). 첫 검출 때 워커 기동(VLM 로드 ~4GB), 이후 검출은 워커 재사용(빠름), Start/Stop 이후에도 유지되어 다음 자동검출에서 로드된 모델을 재사용한다. 왜 이렇게? 한 프로세스에서 4bit VLM과 FFS를 섞으면, VLM이 남긴 파편화로 트래킹 중 FFS의 큰 임시 블록이 재사용 안 돼 reserved VRAM이 ~2.8GB → ~11GB로 폭증(실사용은 0.5GB뿐). 별도 프로세스로 격리하면 트래킹 프로세스는 애초에 VLM을 안 봐서 깨끗한 ~2.8GB로 평탄하게 유지된다 (empty_cache 불필요). (web_gui가 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True도 설정) 검출 시 GUI 하단에 파이프라인 4단계 시각화 이미지(1.YOLO / 2.필터링 / 3.scoring / 4.최종선정)가 뜨고 ◀▶ 버튼으로 단계 전환(데모 노트북 render_stage 이식). conf·NMS iou 슬라이더로 threshold 조절.
  • --tomato-pose : GUI의 Tomato Pose 계산 버튼을 활성화한다.
    • 모델은 시작할 때 한 번 load/warmup하고 프로세스 종료까지 GPU에 유지한다. 이벤트마다 올리고 내리지 않는다.
    • Start 때 VLM top-3의 초기 score/rank를 tracker obj_id에 보존한다. Pose 요청 시 그중 현재 visible mask가 있는 가장 높은 초기 score 대상을 선택한다. 수동 박스는 번호 순서다. retry 도중에는 ID를 바꾸지 않는다.
    • 버튼 클릭 즉시 이전 pose 발행을 중단하고 다음 완성 CycleResult를 사용한다. left/depth/mask/obj.box/K는 모두 동일 crop·동일 cycle 좌표다. 별도 ROS 토픽들을 다시 동기화해 조합하지 않는다.
    • 중심 기본값은 estimate_tomato_center; 실패하면 같은 cycle의 SphereFitter 중심을 사용한다. --center-method sphere는 순서를 반대로 한다.
    • pedicel 실패 시 같은 target의 다음 cycle에서 한 번 더 시도한다. 두 번째도 실패하지만 중심이 유효하면 screen-up fallback을 발행하고 status에 stem_source=fallback_screen_up으로 표시한다. 중심 계산 두 방식이 모두 실패하면 다음 cycle 한 번 재시도하고, 다시 실패하면 pose를 발행하지 않는다.
    • 성공 결과는 기본 10 Hz(--pose-publish-rate)로 다음 요청/Stop까지 반복 발행한다. header stamp는 계산에 사용한 capture stamp로 고정된다.
    • --calib은 4×4 .npy뿐 아니라 Hand-Eye 결과 YAML도 읽는다. --base-frame 기본값은 base_link이며 ur5e_base_link 등으로 바꿀 때 반드시 그 frame을 기준으로 구한 calibration을 함께 사용해야 한다.
  • --ros : Rerun(world)에 더해 ROS2 토픽 동시 발행. eye-to-hand 캘리브 T_base_camera(--calib, 기본 calib/T_base_camera.npy, 현재 항등 placeholder)로 pointcloud·3D박스를 --base-frame 좌표로 변환해 발행(/harvest/points, /harvest/box_markers), RGB/mask는 --camera-frame(/harvest/rgb, /harvest/mask). 객체별 visible 마스크는 /harvest/masks(std_msgs/UInt8MultiArray, (N,H,W) 스택 — 각 레이어 foreground=obj_id, 겹침 보존)로도 발행(/harvest/mask은 한 장 라벨 이미지라 겹침 손실). VLM 자동검출 시엔 익은 토마토 2D bbox를 /harvest/vlm_boxes_2d 로도 발행 — std_msgs/Float32MultiArray, N×5 [x1,y1,x2,y2,score]를 score 내림차순 정렬(크롭된 좌표=/harvest/rgb와 동일). 일회성 검출이라 latched(TRANSIENT_LOCAL) QoS라 나중에 구독해도 마지막 결과를 받음 (ros2 topic echo /harvest/vlm_boxes_2d std_msgs/msg/Float32MultiArray --qos-durability transient_local). 캘리브 값 나오면 calib/T_base_camera.npy 교체. static TF --base-frame→--camera-frame(=T_base_camera) 도 발행하므로, rviz2에서 Fixed Frame = 설정한 base frame으로 두면 pointcloud·box·이미지가 정합돼 보인다 (기본 Fixed Frame=map이면 TF가 없어 안 보임). --tomato-pose도 함께 주면 표준 메시지로 아래를 추가 발행한다(커스텀 메시지/colcon build 불필요).
    • /tomato/pose (geometry_msgs/PoseStamped) — position=토마토 중심, local +X=중심→꼭지, base frame
    • /tomato/stem_direction (geometry_msgs/Vector3Stamped) — 위 +X 단위벡터, base frame
    • /tomato/pose_status (std_msgs/String) — JSON 상태(requested/computing/retrying/valid/failed/stale), target ID/초기 score/rank, center/stem source, retry 수, calibration/frame, pose_active
  • 기본은 순차 모드 — Start 순간 첫 프레임부터 모든 프레임을 순서대로 처리. --realtime 을 주면 latest-only(최신 프레임만, 스킵 = 라이브 시뮬레이션)로 동작.
  • pose/가 있으면 world 좌표, 없으면 camera 좌표로 표시.
  • --ffs-trt [engine] : FoundationStereo를 TensorRT fp16 엔진으로 실행 (PyTorch 대비 ~1.5배 빠름: 254ms→168ms @ 736×1280 RTX 4080 Laptop). 인자 생략 시 weights/ffs_trt/fast_foundationstereo.engine 사용. 엔진은 고정 해상도(736×1280 = HD720을 32의 배수로 패딩한 크기)로 빌드됨 — 다른 해상도면 재빌드 필요. 정확도: 토마토 작동거리(0.2–1.5m)에서 PyTorch 대비 depth median 차 ~9mm(sphere fitting의 median-band ±3cm 안), 경계/원거리 tail은 RANSAC+band가 흡수. 엔진 메모리는 TRT 자체 관리(torch 풀 외부).

FoundationStereo TensorRT 엔진 빌드 (1회, --ffs-trt 용):

pip install onnx onnxscript                                   # export 의존성 (tensorrt/trtexec는 이미 설치돼 있어야 함)
# 1) ONNX export — CPU에서 (GPU로 트레이싱하면 OOM: export는 fp32+비최적 cost-volume+그래프 전체 보존)
CUDA_VISIBLE_DEVICES="" python3 third_party/Fast-FoundationStereo/scripts/make_single_onnx_cpu.py \
    --model_dir third_party/Fast-FoundationStereo/weights/weights/23-36-37/model_best_bp2_serialize.pth \
    --save_path weights/ffs_trt --height 736 --width 1280 --valid_iters 8 --max_disp 192
# (make_single_onnx_cpu.py = 벤더 스크립트에 dynamo=False[레거시 exporter, adaptive_max_pool2d 지원] + CPU 트레이싱 추가한 사본)
# 2) 엔진 빌드 — GPU에서 (~16분; 빌드 중 .engine 파일 절대 건드리지 말 것)
trtexec --onnx=weights/ffs_trt/fast_foundationstereo.onnx \
    --saveEngine=weights/ffs_trt/fast_foundationstereo.engine --fp16 --timingCacheFile=weights/ffs_trt/ffs_timing.cache
# 3) 사용
python3 perception/web_gui.py --crop 230 --vlm --ffs-trt

SVO(.svo2) → 시퀀스 추출 (rectified left/right + pose + K.npy):

python3 perception/extract_svo.py --svo data/line_5_r/line_5_r.svo2 --out data/line_5_r/seq
# VIEW.LEFT/RIGHT는 재생 시 SDK가 rectify → FoundationStereo에 바로 사용 가능
# positional tracking으로 프레임별 pose(world) 복원 → world 좌표 시각화 가능

1. 라이브 (ZED + Rerun) ★ 메인 데모 (박스 하드코딩/중앙)

모델을 먼저 모두 로드한 뒤 Rerun 뷰어를 띄우고 실시간 처리.

source /opt/ros/humble/setup.bash
python3 perception/live_rerun.py --source zed      # 시작 시 물체를 화면 중앙에 두면 추적 시작
python3 perception/live_rerun.py --source replay   # 카메라 없이 녹화 시퀀스로 재생

Rerun 창: 3D 포인트클라우드 + 노란 3D bbox + 이미지(초록 init박스 / 마젠타 추적박스). Ctrl-C 종료.

2. ROS 2 발행 + Rerun (녹화 시퀀스)

source /opt/ros/humble/setup.bash
python3 perception/precompute_results.py           # 프레임별 구 결과 캐시 (1회)
python3 perception/ros2_rerun_publisher.py         # /harvest/points, /harvest/box_markers 발행 + .rrd 저장
# 시각화:  rerun data/tomato_seq/out/harvest.rrd
#          또는 rviz2 (Fixed Frame = camera_optical)

3. 오프라인 처리 (녹화 스테레오 → 결과 영상)

python3 perception/precompute_depth.py             # 패스1: FFS 깊이 → data/tomato_seq/depth/
python3 perception/test_real.py                    # 패스2: 추적+amodal+구fit (프레임별 지표 출력)
python3 perception/make_viz.py                     # result.mp4 + montage.png

무거운 FFS를 별도 프로세스로 분리(2-패스)해 단일 프로세스 과부하를 피한다.

4. 오케스트레이터 단독 검증 (latest-only 락스텝)

python3 perception/test_orchestrator.py            # 최신 프레임만 처리(스킵) 동작 확인

데이터

  • data/tomato_seq/ — ZED 스테레오 시퀀스 (left/, right/, depth/, K.npy, 결과 out/)
  • 새로 캡처하려면 ZED 연결 후 pyzed로 left/right + K.npy 저장 (실행 전 lsusb | grep 2b03:f880 로 카메라 확인).

참고

  • EdgeTAM은 첫 프레임에 프롬프트 박스 1개가 필요하고 이후 자동 추적한다.
  • 단일 GPU라 "병렬" 브랜치는 실제로는 대부분 순차 실행(정확성·배리어는 보장). 실시간 향상은 FFS TensorRT 가속으로.
  • ZED USB는 autosuspend로 종종 끊긴다(사용 전 재연결 확인).

YeonUk-Kim0120/smartfarm_all_unify

smartfarm_all_unify

Python

0

1 commits

updated Sep 23, 2026

See the code

README

Harvest Pipeline (Pedicel fork)

이 폴더는 Harvest_Pipeline의 복사본에 RADIO-DPT-Pose 꼭지 백엔드를 추가한 버전이다 → PEDICEL_DPT_INTEGRATION.md. third_party/는 원본 폴더로의 symlink다.

ZED 2i 스테레오 기반 과일(구형 물체) 인지 파이프라인. 같은 프레임에서 깊이 추정 + 추적/세그멘테이션 + amodal + 3D 구 피팅을 락스텝으로 수행하고, 결과를 Rerun / ROS 2로 시각화·발행한다.

ZED 2i (HD1080 stereo)
   ├─ FoundationStereo ─────────────► depth
   └─ EdgeTAM(track) ─ Ellipse R-CNN ─► visible mask + amodal ellipse
                                         │
                                         ▼
                              SphereFitter (RANSAC 3D sphere
                              + 정확한 conic 재투영 vs amodal)
                                         │
                   ┌─────────────────────┴─────────────────────┐
                   ▼                                           ▼
       3D bounding box / pointcloud              [Pose 버튼 이벤트]
                                                  center + pedicel YOLO-Pose
                                                  → tomato local +X axis
                   └─────────────────────┬─────────────────────┘
                                         ▼
                                      Rerun · ROS 2

구성 요소

  • FoundationStereo (third_party/Fast-FoundationStereo) — 스테레오 깊이 (선택: TensorRT fp16, --ffs-trt, ~1.5배)
  • EdgeTAM (third_party/EdgeTAM) — 프롬프트 1개로 시작하는 온라인 추적/세그멘테이션
  • Ellipse R-CNN (third_party/ellipse-rcnn) — 가려진 부분까지의 amodal 타원
  • SphereFitter — 깊이+마스크로 3D 구를 맞추고 이미지로 정확 재투영
  • Tomato Pose — 같은 cycle의 RGB·FFS depth·visible mask·추적 bbox로 중심과 중심→꼭지 축을 이벤트 방식으로 계산 (weights/tomato_pose/pedicel_pose.pt 상주 로드)
  • ★ 이 fork (Harvest_Pipeline_Pedicel): 꼭지 모델을 pedicel-2d의 RADIO-DPT-Pose(base+tip 2 keypoint, weights/tomato_pose/radio_dpt_pose_v4_ih2_on_s0.pt)로 교체. base·tip을 FFS depth로 3D 승격해 base→tip 축을 Rerun/ROS(/tomato/pedicel_points, /tomato/pedicel_axis)로 실시간 확인하고, 그리퍼에 가려 중심을 못 구해도 꼭지 pose를 발행한다. 사용법·검증: PEDICEL_DPT_INTEGRATION.md. 원래 모델은 트래킹 모드 기본값(--tracking-pedicel-backend yolo).

2026-09-22 변경: 로봇이 파지하기 전(트래킹 모드 Pose 버튼)은 원래 1-keypoint YOLO 모델(--tracking-pedicel-backend yolo, 중심→꼭지 축)만 쓰고, RADIO-DPT(base+tip)는 그리퍼 파지 신호 후 grasp 모드에서만 실행된다. /tomato/pedicel_points·/tomato/pedicel_axis도 grasp 모드에서만 발행. --pedicel-backend는 이제 grasp 모드 모델 (radio_dpt | yolo_pose). 트래킹에서도 RADIO-DPT를 쓰려면 --tracking-pedicel-backend radio_dpt.

설치

자세한 버전·패치는 requirements.txt 참고. 요약:

# 시스템: CUDA 12.6, ROS 2 Humble, ZED SDK 5.2.3 (pyzed)
source /opt/ros/humble/setup.bash

# PyTorch (CUDA 12.8 wheels)
pip install torch==2.10.0 torchvision==0.25.0 xformers==0.0.35 \
    --index-url https://download.pytorch.org/whl/cu128
pip install -r requirements.txt

# vendored 모델 (requirements.txt의 주석대로 clone + 패치)
#   Fast-FoundationStereo / EdgeTAM(pip install -e .) / ellipse-rcnn(PYTHONPATH)

uv venv (이 폴더의 .venv, 2026-09-14 구성·검증)

시스템 python3.10 + ROS 2 Humble rclpy를 그대로 쓰고, ~/.local의 user-site(충돌하는 sam2 1.1.0 등)와는 격리된다 → PYTHONNOUSERSITE=1 불필요. third_party/는 symlink이므로 EdgeTAM editable 설치는 절대경로로.

cd Harvest_Pipeline_Pedicel
uv venv --python /usr/bin/python3.10 .venv
uv pip install --python .venv/bin/python torch==2.10.0 torchvision==0.25.0 xformers==0.0.35 \
    --index-url https://download.pytorch.org/whl/cu128
uv pip install --python .venv/bin/python -r requirements.txt \
    --extra-index-url https://download.pytorch.org/whl/cu128 --index-strategy unsafe-best-match
uv pip install --python .venv/bin/python setuptools wheel pytest lark
(cd third_party/EdgeTAM && SAM2_BUILD_CUDA=0 uv pip install --python "$OLDPWD/.venv/bin/python" -e . --no-build-isolation --no-deps)

# 실행 (매 셸)
source /opt/ros/humble/setup.bash
source .venv/bin/activate
HF_HUB_OFFLINE=1 python perception/pedicel_offline_check.py --out data/pedicel_check   # 모델·환경 확인
python perception/web_gui.py --crop 230 --ros --tomato-pose
# 테스트: ROS의 launch_testing pytest 플러그인이 최신 pytest와 안 맞으므로 autoload 끔
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest perception --ignore=perception/test_real.py
  • pyzed(ZED SDK)는 venv에 없다. 라이브 카메라를 쓰려면 ZED SDK 설치 후 .venv/bin/python /usr/local/zed/get_python_api.py. 리플레이·오프라인 검증에는 불필요.
  • 이 PC(RTX 3090 ×2, driver 570 / CUDA 12.8, transformers 5.3.0)에서 오프라인 검증 18/18 valid, 중앙값 596 ms.
  • 가중치 상태(2026-09-18): 한때 이 PC 사본이 52 MiB에서 잘려 있던 weights/ellipse_rcnn/best.pt(159 MB), weights/ffs_trt/*.onnx(136 MB), tomato-vlm safetensors는 원본 Harvest_Pipeline/에서 다시 받아 복사·검증 완료(Ellipse R-CNN 학습 가중치 로드 OK). 따라서 --ellipse-ckpt 더미 옵션은 더 이상 필요 없다. third_party/는 symlink라 VLM 가중치는 자동으로 공유된다.

ZED SDK + FFS-only 라이브 뷰어 (EdgeTAM/Ellipse 없이 꼭지만)

perception/live_pedicel_rerun.py: ZED → FoundationStereo depth → 고정 target zone(기본, 화면 중앙 정사각형) → RADIO-DPT base/tip → Rerun. 그리퍼가 토마토를 zone 안에 두는 시나리오라 검출기가 필요 없다(tomato-vlm YOLO는 파지 상태에서 박스가 틀려 --detector yolo는 보조용). 세그멘테이션이 없으므로 중심은 unavailable, pose 위치는 3D base 점.

# 1) ZED SDK 5.2.3 (cu12.8) — 설치 파일은 ~/Downloads/zed_sdk/ 에 받아 둠. sudo 필요, 1회
sudo ~/Downloads/zed_sdk/ZED_SDK_Ubuntu22_cuda12.8_tensorrt10.9_v5.2.3.run -- silent skip_cuda skip_tools
sudo cp ~/Downloads/zed_sdk/99-slabs.rules /etc/udev/rules.d/ && sudo udevadm control --reload-rules && sudo udevadm trigger
sudo chmod -R o+rX /usr/local/zed && sudo chmod -R a+rwX /usr/local/zed/settings   # silent 설치가 root:zed 770으로 만듦
# 2) pyzed를 venv에 (get_python_api.py가 python 버전에 맞는 wheel을 받아 pip 설치; venv에 pip 설치돼 있음)
.venv/bin/python /usr/local/zed/get_python_api.py
.venv/bin/python -c "import pyzed.sl as sl; print(sl.Camera.get_sdk_version())"
# 3) 실행 (카메라 연결: lsusb | grep 2b03 에 f880+f881 둘 다 보여야 함)
source .venv/bin/activate
python perception/live_pedicel_rerun.py --source zed --crop 230 --tta none                       # 중앙 zone
python perception/live_pedicel_rerun.py --source zed --crop 230 --box 380,120,680,420 --zone-depth 0.30,0.45
python perception/live_pedicel_rerun.py --source npz --save data/pedicel_live/x.rrd              # 카메라 없이 npz 리플레이
  • 화면: 초록/빨강 = target zone(안에 --zone-depth 범위의 depth 픽셀이 --zone-fill 이상이면 초록 "TOMATO IN ZONE", 아니면 빨강 "PLACE TOMATO IN GREEN ZONE"), 노랑 = 꼭지 모델이 실제로 보는 1.4× 정사각 crop. 3D 뷰에는 zone×깊이 범위의 frustum(world/target_zone)이 같은 색으로 그려진다. zone이 비어 있으면 꼭지 모델을 돌리지 않는다(--always-run으로 강제).

  • --zone-depth는 그리퍼 작업 거리 ±수 cm로 좁힐 것. 넓게 두면 책상·배경 depth가 zone을 채워 항상 초록이 된다.

  • crop 고해상도 depth(--refine-scale 2, 기본 켜짐): 가는 꼭지의 tip depth가 배경으로 새서 tip_on_base_plane (정면 평행 가정 → 축이 누움)으로 떨어지는 문제 대책. 꼭지 모델 crop(1.4× 정사각 +--refine-pad)만 좌·우에서 잘라 --refine-scale배 업샘플해 FFS를 한 번 더 돌리고, 그 depth로 base/tip을 3D 승격한다. 우측 crop은 --zone-depth의 zmax에서의 시차만큼 왼쪽으로 옮겨 자르므로 zmax보다 가까운 물체는 crop 안에서 항상 양의 시차를 가진다(zmax보다 먼 배경은 무효). 전체 프레임 pass는 그대로 pointcloud·zone 판정에 쓴다. Rerun world/cam/refine, refine_depth에 crop 결과 표시. --full-scale 0.5 --res HD1080로 전체 pass를 줄이고 crop은 실제 1080p 픽셀을 쓰는 조합도 가능. 합성 스테레오로 depth 복원 검증.

  • Rerun 레이아웃은 blueprint로 고정: 왼쪽 3D, 오른쪽 위 전체 이미지(파란 박스 = refine crop 영역), 오른쪽 아래 refine/image(업샘플 crop + 오버레이) / refine/depth. --dump DIR로 프레임별 main/refine/refine_depth PNG 저장. 실카메라(ZED 2i HD720, 2026-09-14)에서 refine ×2 crop 1184×932 포함 프레임당 약 600 ms 확인.

  • 배율/zone 크기와 비용 (ZED 2i HD720 crop 230, zone 0.30–0.45 m, RTX 3090; 전체 pass + refine + 꼭지 모델 포함):

    --center-frac--refine-scalerefine crop프레임 시간FFS 피크 VRAM
    0.4 (288 px)21184×932≈600 ms1.7 GB
    0.3 (216 px, 기본)3 (기본)1323×1098≈690 ms2.5 GB
    0.341644×1464≈1020 ms5.0 GB

    crop 안 시차 범위 (d(zmin) − d(zmax)) × scale이 체크포인트 한계(max_disp 416)를 넘으면 안 된다. 시작 로그에 in-crop range ... <= max_disp limit 416로 출력되며, 필요 시 해당 호출만 max_disp를 자동으로 올린다(기본 192). 넘으면 --zone-depth를 좁히거나 배율을 낮춘다. zone 깊이 범위를 좁힐수록 crop도 작아져 더 높은 배율이 가능하다.

  • VRAM (2026-09-14 실측, HD720, zone 0.3, refine ×3): 뷰어 프로세스 5.9 GB(스크립트가 PYTORCH_CUDA_ALLOC_CONF= expandable_segments:True를 설정; 없으면 7.8 GB) + Rerun 뷰어 0.7 GB. 내역: CUDA 컨텍스트 0.3, RADIO-DPT 가중치 1.8(fp16), FFS 가중치 0.07, ZED SDK 0.1, FFS 전체 pass 피크 3.4, refine ×3 피크 5.2(일시적). 12 GB GPU에 여유 있음. nvidia-smi 총합에는 데스크톱 등 다른 프로세스(이 PC ~1.3 GB)가 포함되니 프로세스별로 볼 것.

  • 옵션: --refine-scale 3 --refine-pad 32 --full-scale 1 --dump DIR (뷰어는 전체 pass를 항상 돌림; grasp 모드는 crop만) --detector {center,box,yolo,stored} --center-frac 0.4 --box --zone-depth --zone-fill 0.25 --hold 10(yolo 박스 유지) --tta --pedicel-device --hz --stride --zmax --save --no-spawn.

Grasp 모드 (web_gui.py, 2026-09-17) — 그리퍼 파지 신호 → 꼭지 모델

--tomato-pose로 띄우면 기존 버튼 줄 아래에 🤖 그리퍼 파지 신호 → 꼭지 모델 / 파지 해제 행이 생긴다. 실기에서는 --ros일 때 --grasp-topic(기본 /harvest/grasped, std_msgs/Bool)의 True/False가 같은 일을 한다.

  • 파지 신호: EdgeTAM 추적 루프를 멈추고(/stop과 동일) perception/grasp_pedicel.py의 루프를 돌린다: ZED → FFS 전체 pass(포인트클라우드·zone 판정) → crop 업샘플 FFS(refine) → RADIO-DPT base/tip → PoseSolution → Rerun(추적 모드와 같은 엔티티 world/tomato_pose/*, diagnostics/tomato_pose/image, world/target_zone)

    • ROS /tomato/{pose,stem_direction,pedicel_points,pedicel_axis}(--pose-publish-rate로 반복), /tomato/pose_status(mode: grasp), /harvest/{points,rgb}.
  • 모델 선택(2026-09-18): 행의 셀렉트 박스로 우리 모델 (RADIO-DPT-Pose) ↔ YOLO26s-pose (fine-tuned)를 고른다. 시작 시에는 --pedicel-backend(기본 radio_dpt) 하나만 로드하고, 다른 쪽은 그 모델을 고른 파지 신호가 처음 왔을 때 로드해(YOLO 0.2 s, RADIO ~3 s) 이후 상주한다. grasp 상태줄에 model … (loaded: …)로 표시. yolo_pose = pedicel-2d runs/y26s-ih2-fx7/s0/best.pt (in-hand 학습, 2 keypoint base/tip, 640², ~11 ms)를 weights/tomato_pose/yolo26s_pose_ih2_fx7_s0.pt로 복사한 것. perception/pedicel_yolo.py가 RADIO 래퍼와 같은 입력 규약(1.4× 정사각 crop → 1024 letterbox → 640)으로 감싸 estimator의 radio_dpt 경로를 공유한다(TomatoPoseEstimator(backend='yolo_pose')). 오프라인 검증(18 npz, tta none): valid 14/18, base 위치는 RADIO와 대체로 5 px 안, 미검출 1장·tip conf<0.5 3장(YOLO는 예측을 포기하는 경우가 있음; MODEL_SELECTION.md 참고). python perception/pedicel_offline_check.py --backend yolo_pose --tta none --out data/pedicel_check_yolo

  • zone: 항상 화면 중앙 고정 정사각형(--grasp-zone-frac, 기본 0.3). 로봇 팔이 토마토를 카메라 앞으로 가져오는 시나리오라 캔버스/VLM/추적 박스는 zone에 쓰지 않는다(2026-09-18 수정; 이전에는 캔버스 1번 박스가 있으면 그 박스를 썼음). depth 범위·refine 배율· "zone 비어도 실행"은 행의 입력칸으로 파지 시점마다 바꿀 수 있다. zone이 비면(depth 범위 안 픽셀 < --grasp-zone-fill) 모델을 돌리지 않고 이전 pose 반복 발행도 멈춘다.

  • 브라우저에 오버레이(zone·crop·base/tip)가 실시간으로 뜨고, grasp 상태줄에 state·fill·conf·axis·길이·추론시간이 나온다.

  • 파지 시 ZED 재오픈(2026-09-18): 파지 행의 해상도 셀렉트(2K / 1080p / 720p=전환 없음, 기본은 --grasp-res=HD2K)로 세션마다 고른다 — 파지 신호가 오면 카메라를 닫고 이 해상도로 다시 열어(약 2 s) grasp 루프를 돌리고, 해제 시 --res(HD720)로 복귀한다. 캔버스 zone과 --crop은 폭 비율(×1.725)로 자동 스케일(--grasp-crop으로 지정 가능). 720p 대비 crop에 실제 픽셀이 1.7배 들어간다. 재오픈한 소스는 이전 프레임 번호를 이어받는다(ZedLatestSource(idx_offset=…)) — 번호가 0부터 다시 시작하면 Rerun의 "frame" 타임라인이 과거로 돌아가 3D 뷰가 멈춘 것처럼 보였다(2026-09-18 수정, 라이브 검증: 720p 700 → 2K 725… → 720p 1138). 전제 조건 수정: ZED SDK가 연 /dev/video* fd를 이후 spawn되는 자식(Rerun 뷰어, VLM 워커)이 상속하면 close 후 재오픈이 CAMERA STREAM FAILED TO START로 실패한다 → orchestrator.ZedLatestSource가 open 직후 장치 fd에 FD_CLOEXEC를 건다.

  • 스테레오 탐색 범위 ≠ zone 범위(2026-09-18 원인 규명): 같은 장면(모니터 0.95 m)으로 비교한 결과 crop 자체는 문제가 없다 (전체 프레임 0.954 m = 같은 좌표 crop 0.953 m = ×1.5 업샘플 crop 0.954 m). 문제는 우측 crop을 zone의 zmax(0.45 m) 시차만큼 옮긴 것으로, 그 범위 밖 배경이 0.40 m로 접혀 나왔다. 그래서 --grasp-stereo-far(기본 1.5 m)를 두어 shift는 이 거리의 시차로 잡고, 배율은 (d(zmin)−d(far))×scale ≤ 416에 맞춰 자동 결정한다(2K에서 ×1.1, 720p에서 ×2.2). 결과: 배경이 LR 검사 없이도 0.954 m로 제자리에 복원된다. 배율을 더 올리려면 zmin을 올리거나 far를 낮춘다.

  • 좌우 일관성 검사(기본 켜짐, --grasp-no-lr-check로 해제): 우측 crop을 zmax 시차만큼 옮겨 자르므로 zmax보다 먼 배경은 crop 안에서 음의 시차여야 하는데 FFS는 양의 시차만 내서, 배경이 zone 깊이 범위 안으로 접혀 "앞으로 튀어나온" 점이 생겼다. 거울 쌍(flip(R), flip(L))을 같은 배치로 한 번 더 풀어 disp_R(x−d) ≈ d가 아닌 픽셀(+300 px 미만 연결 성분)을 무효 처리한다. far=1.5 m 이후에는 1.5 m 밖 배경에만 관여한다. 비용은 refine pass 2배(2K ×1.1: 214 → 413 ms, 피크 2.6 → 5.1 GB; 프로세스 총 8.4 GB). 12 GB에 여유가 필요하면 끄고 zone 판정에 맡겨도 된다. 배경 위 keypoint는 depth가 없어 depth_unavailable로 실패하는 것이 정상.

  • crop만 복원(기본): grasp 모드는 전체 프레임 FFS pass를 돌리지 않고 crop만 스테레오 복원한다(--grasp-full-depth로 전체 pass 추가). zone 판정과 Rerun 포인트클라우드(crop 영역, dense, stride 2)가 모두 crop depth에서 나온다. --grasp-refine-iters 16 (crop pass GRU 반복; 전체 pass는 8). crop 시차 범위 (d(zmin)−d(zmax))×scale이 416을 넘으면 세션 배율을 자동으로 낮춘다. 실측(RTX 3090, 실제 HD2K 쌍, zone 0.30–0.45 m, crop 731×584@×1):

    카메라refinecropFFS 시간피크 VRAM
    HD720×3 (iters 16)1323×1098394 ms3.5 GB
    HD2K×1731×584101 ms0.9 GB
    HD2K×1.5 (기본)1096×876247 ms2.4 GB
    HD2K×21462×1168490 ms5.0 GB
    HD2K×2.51828×1460859 ms9.3 GB

    --grasp-refine-scale 기본값: HD1080/HD2K 재오픈 시 1.5, HD720이면 3. 라이브 검증: 파지→HD2K→유효 pose(0.84 s/프레임)→ 해제→HD720 복귀를 2회 반복 성공.

  • 모드 전환 시 Rerun 정리(2026-09-18): 추적 루프가 멈추면(Stop/파지) world/box, world/cam/image/{mask,tracked}를 마지막 프레임+1에 tombstone하고, 파지 세션 시작 프레임에서 world/tomato_pose·진단 이미지도 지운다. 파지 종료 시에는 grasp 엔티티(world/target_zone, world/cam/image/target, world/tomato_pose)를 같은 방식으로 지운다. (Clear는 스레드별 타임라인에 놓이므로 시각을 지정하지 않으면 적용되지 않던 문제 수정.) 이전 추적 결과나 720p 좌표의 옛 박스가 2K 파지 화면에 남던 현상이 이것이었다.

  • 파지 해제 → idle. 다시 추적하려면 Start를 누른다. 세그멘테이션이 없으므로 pose 위치는 3D base 점(center_source=pedicel_base_point).

  • 연속 실행이므로 --pedicel-tta none(0.15 s) 권장. --ffs-trt면 엔진이 고정 크기라 refine은 자동으로 꺼진다.

  • (2026-09-18 이후 best.pt가 복구되어 --ellipse-ckpt 더미는 불필요.) 검증(2026-09-17, replay 합성 스테레오): 버튼·ROS 토픽 시작/해제, /tomato/pedicel_points (base,tip 2행), /tomato/pose_status 발행 확인.

source /opt/ros/humble/setup.bash && source .venv/bin/activate
python perception/web_gui.py                     # 2026-09-18부터 기본값: --crop 230 --ros(소스돼 있으면) --vlm --tomato-pose --pedicel-tta none
python perception/web_gui.py --source replay --seq data/pedicel_live/synth_seq   # 카메라 없이 (crop 자동 0)
python perception/web_gui.py --no-vlm --no-ros   # 끄기
ros2 topic pub --once /harvest/grasped std_msgs/msg/Bool "data: true"     # 로봇 쪽 신호 시뮬레이션

실행

0. 브라우저 GUI — 2D 박스 지정 + Start/Stop ★★ 추천

모델과 카메라를 한 번만 로드해 유지하고, 브라우저에서 첫 프레임에 2D 박스를 그린 뒤 Start/Stop으로 3D 검출을 제어한다. 3D 결과(포인트클라우드 + 3D bbox + 카메라)는 Rerun 창에 표시.

source /opt/ros/humble/setup.bash
python3 perception/web_gui.py --crop 300                # 라이브 ZED (기본)
python3 perception/web_gui.py --crop 230 --vlm --ros --tomato-pose  # 자동검출+Pose 통합
python3 perception/web_gui.py --source replay # 녹화 시퀀스 (기본 data/tomato_seq)
# 다른 녹화 + 프레임 범위 + 루프:
python3 perception/web_gui.py --source replay --seq data/line_5_r/seq --start 500 --end 610 --loop --crop 230
# 브라우저에서 http://localhost:8000  →  물체에 박스 드래그  →  ▶ Start / ■ Stop

브라우저 = 첫 프레임에 박스 0~3개 드로잉(번호·색, "박스 지우기") + Start/Stop, Rerun 창 = 3D 결과. 박스 여러 개면 객체별 mask/2D·3D박스/Kalman이 각각, 0개면 검출 없이 장면만. 물체가 화면에서 벗어나 트래킹을 놓치면 그 객체 오버레이가 사라지고 장면(포인트클라우드+카메라)은 남는다.

replay 옵션 (--source replay):

  • --seq <dir> : 녹화 시퀀스 폴더 (left/, right/, K.npy, 있으면 pose/)
  • --start N --end M : 사용할 프레임 범위 (inclusive)
  • --loop : 끝나면 처음으로 되돌아가 반복 (루프마다 박스로 검출 재초기화)
  • --crop N : 왼쪽 N px 잘라냄 (스테레오 invalid strip 제거; HD720이면 ~230, HD1080이면 ~300). FFS는 full 쌍으로 depth를 만든 뒤 crop→speckle 제거(경계 보존), EdgeTAM/박스/intrinsics(cx)도 동일 crop.
  • --ema N : depth 시간 EMA의 span(프레임, 기본 5). 실제 켜고/끄기는 GUI의 "Depth EMA" 버튼. ⚠️ 카메라 정지일 때만 켜세요 (움직이면 픽셀이 다른 3D점을 봐서 잔상). 정지 씬에서 depth 잔떨림 감소.
  • world 3D 박스는 항상 Kalman filter로 평활됨 (중심=등속 모델, 반경=random-walk; world 프레임이라 카메라가 움직여도 유효, 루프/재획득 시 리셋). orchestrator.KalmanBox의 q/r 노이즈로 강도 조절.
  • --vlm : 토마토 VLM 자동검출 활성화 (YOLO+Qwen3-VL 4bit, 코드·weight third_party/tomato-vlm/). 브라우저 "VLM 자동검출" 버튼 → 현재 프레임에 YOLO→verifier→ripeness→scoring 실행 → 익은 토마토 박스(최대 3개) 자동 채움(수동 드로잉 대신, 0~3개). 프로세스 격리 방식: VLM은 vlm_worker.py라는 별도 자식 프로세스에서 돌아요(터미널은 그대로 1개). 첫 검출 때 워커 기동(VLM 로드 ~4GB), 이후 검출은 워커 재사용(빠름), Start/Stop 이후에도 유지되어 다음 자동검출에서 로드된 모델을 재사용한다. 왜 이렇게? 한 프로세스에서 4bit VLM과 FFS를 섞으면, VLM이 남긴 파편화로 트래킹 중 FFS의 큰 임시 블록이 재사용 안 돼 reserved VRAM이 ~2.8GB → ~11GB로 폭증(실사용은 0.5GB뿐). 별도 프로세스로 격리하면 트래킹 프로세스는 애초에 VLM을 안 봐서 깨끗한 ~2.8GB로 평탄하게 유지된다 (empty_cache 불필요). (web_gui가 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True도 설정) 검출 시 GUI 하단에 파이프라인 4단계 시각화 이미지(1.YOLO / 2.필터링 / 3.scoring / 4.최종선정)가 뜨고 ◀▶ 버튼으로 단계 전환(데모 노트북 render_stage 이식). conf·NMS iou 슬라이더로 threshold 조절.
  • --tomato-pose : GUI의 Tomato Pose 계산 버튼을 활성화한다.
    • 모델은 시작할 때 한 번 load/warmup하고 프로세스 종료까지 GPU에 유지한다. 이벤트마다 올리고 내리지 않는다.
    • Start 때 VLM top-3의 초기 score/rank를 tracker obj_id에 보존한다. Pose 요청 시 그중 현재 visible mask가 있는 가장 높은 초기 score 대상을 선택한다. 수동 박스는 번호 순서다. retry 도중에는 ID를 바꾸지 않는다.
    • 버튼 클릭 즉시 이전 pose 발행을 중단하고 다음 완성 CycleResult를 사용한다. left/depth/mask/obj.box/K는 모두 동일 crop·동일 cycle 좌표다. 별도 ROS 토픽들을 다시 동기화해 조합하지 않는다.
    • 중심 기본값은 estimate_tomato_center; 실패하면 같은 cycle의 SphereFitter 중심을 사용한다. --center-method sphere는 순서를 반대로 한다.
    • pedicel 실패 시 같은 target의 다음 cycle에서 한 번 더 시도한다. 두 번째도 실패하지만 중심이 유효하면 screen-up fallback을 발행하고 status에 stem_source=fallback_screen_up으로 표시한다. 중심 계산 두 방식이 모두 실패하면 다음 cycle 한 번 재시도하고, 다시 실패하면 pose를 발행하지 않는다.
    • 성공 결과는 기본 10 Hz(--pose-publish-rate)로 다음 요청/Stop까지 반복 발행한다. header stamp는 계산에 사용한 capture stamp로 고정된다.
    • --calib은 4×4 .npy뿐 아니라 Hand-Eye 결과 YAML도 읽는다. --base-frame 기본값은 base_link이며 ur5e_base_link 등으로 바꿀 때 반드시 그 frame을 기준으로 구한 calibration을 함께 사용해야 한다.
  • --ros : Rerun(world)에 더해 ROS2 토픽 동시 발행. eye-to-hand 캘리브 T_base_camera(--calib, 기본 calib/T_base_camera.npy, 현재 항등 placeholder)로 pointcloud·3D박스를 --base-frame 좌표로 변환해 발행(/harvest/points, /harvest/box_markers), RGB/mask는 --camera-frame(/harvest/rgb, /harvest/mask). 객체별 visible 마스크는 /harvest/masks(std_msgs/UInt8MultiArray, (N,H,W) 스택 — 각 레이어 foreground=obj_id, 겹침 보존)로도 발행(/harvest/mask은 한 장 라벨 이미지라 겹침 손실). VLM 자동검출 시엔 익은 토마토 2D bbox를 /harvest/vlm_boxes_2d 로도 발행 — std_msgs/Float32MultiArray, N×5 [x1,y1,x2,y2,score]를 score 내림차순 정렬(크롭된 좌표=/harvest/rgb와 동일). 일회성 검출이라 latched(TRANSIENT_LOCAL) QoS라 나중에 구독해도 마지막 결과를 받음 (ros2 topic echo /harvest/vlm_boxes_2d std_msgs/msg/Float32MultiArray --qos-durability transient_local). 캘리브 값 나오면 calib/T_base_camera.npy 교체. static TF --base-frame→--camera-frame(=T_base_camera) 도 발행하므로, rviz2에서 Fixed Frame = 설정한 base frame으로 두면 pointcloud·box·이미지가 정합돼 보인다 (기본 Fixed Frame=map이면 TF가 없어 안 보임). --tomato-pose도 함께 주면 표준 메시지로 아래를 추가 발행한다(커스텀 메시지/colcon build 불필요).
    • /tomato/pose (geometry_msgs/PoseStamped) — position=토마토 중심, local +X=중심→꼭지, base frame
    • /tomato/stem_direction (geometry_msgs/Vector3Stamped) — 위 +X 단위벡터, base frame
    • /tomato/pose_status (std_msgs/String) — JSON 상태(requested/computing/retrying/valid/failed/stale), target ID/초기 score/rank, center/stem source, retry 수, calibration/frame, pose_active
  • 기본은 순차 모드 — Start 순간 첫 프레임부터 모든 프레임을 순서대로 처리. --realtime 을 주면 latest-only(최신 프레임만, 스킵 = 라이브 시뮬레이션)로 동작.
  • pose/가 있으면 world 좌표, 없으면 camera 좌표로 표시.
  • --ffs-trt [engine] : FoundationStereo를 TensorRT fp16 엔진으로 실행 (PyTorch 대비 ~1.5배 빠름: 254ms→168ms @ 736×1280 RTX 4080 Laptop). 인자 생략 시 weights/ffs_trt/fast_foundationstereo.engine 사용. 엔진은 고정 해상도(736×1280 = HD720을 32의 배수로 패딩한 크기)로 빌드됨 — 다른 해상도면 재빌드 필요. 정확도: 토마토 작동거리(0.2–1.5m)에서 PyTorch 대비 depth median 차 ~9mm(sphere fitting의 median-band ±3cm 안), 경계/원거리 tail은 RANSAC+band가 흡수. 엔진 메모리는 TRT 자체 관리(torch 풀 외부).

FoundationStereo TensorRT 엔진 빌드 (1회, --ffs-trt 용):

pip install onnx onnxscript                                   # export 의존성 (tensorrt/trtexec는 이미 설치돼 있어야 함)
# 1) ONNX export — CPU에서 (GPU로 트레이싱하면 OOM: export는 fp32+비최적 cost-volume+그래프 전체 보존)
CUDA_VISIBLE_DEVICES="" python3 third_party/Fast-FoundationStereo/scripts/make_single_onnx_cpu.py \
    --model_dir third_party/Fast-FoundationStereo/weights/weights/23-36-37/model_best_bp2_serialize.pth \
    --save_path weights/ffs_trt --height 736 --width 1280 --valid_iters 8 --max_disp 192
# (make_single_onnx_cpu.py = 벤더 스크립트에 dynamo=False[레거시 exporter, adaptive_max_pool2d 지원] + CPU 트레이싱 추가한 사본)
# 2) 엔진 빌드 — GPU에서 (~16분; 빌드 중 .engine 파일 절대 건드리지 말 것)
trtexec --onnx=weights/ffs_trt/fast_foundationstereo.onnx \
    --saveEngine=weights/ffs_trt/fast_foundationstereo.engine --fp16 --timingCacheFile=weights/ffs_trt/ffs_timing.cache
# 3) 사용
python3 perception/web_gui.py --crop 230 --vlm --ffs-trt

SVO(.svo2) → 시퀀스 추출 (rectified left/right + pose + K.npy):

python3 perception/extract_svo.py --svo data/line_5_r/line_5_r.svo2 --out data/line_5_r/seq
# VIEW.LEFT/RIGHT는 재생 시 SDK가 rectify → FoundationStereo에 바로 사용 가능
# positional tracking으로 프레임별 pose(world) 복원 → world 좌표 시각화 가능

1. 라이브 (ZED + Rerun) ★ 메인 데모 (박스 하드코딩/중앙)

모델을 먼저 모두 로드한 뒤 Rerun 뷰어를 띄우고 실시간 처리.

source /opt/ros/humble/setup.bash
python3 perception/live_rerun.py --source zed      # 시작 시 물체를 화면 중앙에 두면 추적 시작
python3 perception/live_rerun.py --source replay   # 카메라 없이 녹화 시퀀스로 재생

Rerun 창: 3D 포인트클라우드 + 노란 3D bbox + 이미지(초록 init박스 / 마젠타 추적박스). Ctrl-C 종료.

2. ROS 2 발행 + Rerun (녹화 시퀀스)

source /opt/ros/humble/setup.bash
python3 perception/precompute_results.py           # 프레임별 구 결과 캐시 (1회)
python3 perception/ros2_rerun_publisher.py         # /harvest/points, /harvest/box_markers 발행 + .rrd 저장
# 시각화:  rerun data/tomato_seq/out/harvest.rrd
#          또는 rviz2 (Fixed Frame = camera_optical)

3. 오프라인 처리 (녹화 스테레오 → 결과 영상)

python3 perception/precompute_depth.py             # 패스1: FFS 깊이 → data/tomato_seq/depth/
python3 perception/test_real.py                    # 패스2: 추적+amodal+구fit (프레임별 지표 출력)
python3 perception/make_viz.py                     # result.mp4 + montage.png

무거운 FFS를 별도 프로세스로 분리(2-패스)해 단일 프로세스 과부하를 피한다.

4. 오케스트레이터 단독 검증 (latest-only 락스텝)

python3 perception/test_orchestrator.py            # 최신 프레임만 처리(스킵) 동작 확인

데이터

  • data/tomato_seq/ — ZED 스테레오 시퀀스 (left/, right/, depth/, K.npy, 결과 out/)
  • 새로 캡처하려면 ZED 연결 후 pyzed로 left/right + K.npy 저장 (실행 전 lsusb | grep 2b03:f880 로 카메라 확인).

참고

  • EdgeTAM은 첫 프레임에 프롬프트 박스 1개가 필요하고 이후 자동 추적한다.
  • 단일 GPU라 "병렬" 브랜치는 실제로는 대부분 순차 실행(정확성·배리어는 보장). 실시간 향상은 FFS TensorRT 가속으로.
  • ZED USB는 autosuspend로 종종 끊긴다(사용 전 재연결 확인).