2026-08-17〜18 のディレクションを踏まえた要件定義。方針正本 = raptor memory
project_fullseye_mission_unified_vision_2026_08_18、gap 分析 =EVIS_VISION_OSS_GAP.md。 本書は 何を・なぜ(要件)を定める。どう作るか(実装)は本書合意後の設計/spike で。
Fullseye = あらゆる画像処理/視覚アルゴリズムを『スキル』として保持し、即使える包括的ライブラリ
(=専用 HALCON)。目標は HALCON 級網羅(実測 979/2313 = 42.3%(2026-09-06)、HALCON_COVERAGE.md を伸ばす)。
現状、アルゴリズムは 3 つの別インターフェースに分裂しており、使う側(人間・Studio・evis 視覚・エージェント)から
一貫して呼べない。本件の目的は 使う際のインターフェースを統一し、どのアルゴリズムも同じ自然な作法で
発見・呼び出し・introspection・Studio 露出できるようにすること。
| ユーザー | シーン | 含意 |
|---|---|---|
| 本人(人間) | REPL / スクリプトで手書き、仕事で使う | ★サンプルコードが人間から見て自然であること(最優先) |
| Fullseye Studio | GUI から op を把握・試験・パラメータ調整 | 同じ op を GUI からも一貫操作(introspection/メタが要る) |
| evis 視覚パイプライン | stereo→cloud→6D pose→(MoveIt2)→筋実現 | 知覚 op を統一 I/F で組める |
| エージェント/自動化 | プログラム的に op を列挙・実行 | 発見可能(registry)・型/メタが機械可読 |
In: 画像処理 op(現 registry 654)+ 視覚/知覚 op(現 facade 116)を統一 I/F に載せる。 自作 numpy 実装も OSS アダプタも同一 I/F。Studio 露出。introspection/メタ/honest gate の統一。 Out: 汎用 CS(algo-c: sort/CRC/Huffman/回文 = 39 op。画像/視覚の知見でない=対象外・凍結)。 OSS 内部の再実装(PCL/grid_map/OpenCV/MoveIt2 は薄いアダプタで裏に、再発明しない)。
3 層・3 規約に分裂:
| 層 | 数 | 現 呼び出し | 自然さ |
|---|---|---|---|
| 画像 registry | 654 | apply(image, "gaussian", a=0.5, b=0.5) = 文字列名 + 汎用 2 ノブ a/b |
✗ 最も不自然(進化用エンコード) |
| algo(対象外) | 39 | run_algo("name", seq) = 文字列 dispatch |
✗(だが off-mission) |
| 知覚 facade | 116 | fs.disparity_sgm(left, right, max_disp=16, ...) = 名前付き引数 |
△ 比較的自然だが registry/introspection 無し |
image / point_cloud / pose / grid_map_layer / scalar 等)を持ち、
Studio が RViz2 相当の 3D/2D 描画を自動選択できる。sim.MuJoCo/sim.Gazebo/
sim.IsaacSim)も同一契約で、共通の動詞(.frames()/.depth()/.intrinsics()/.ground_truth())で視覚 op に
入力を供給する(入力元を問わず op が組める。ground_truth は honest 評価の真値源)。spikes/studio_sample_catalog.py(vision: image.chain / cloud.perceive、sim-source: sim.lidar /
sim.to_vision。各 sample が name/domain/summary/code/run を持つ = F3 introspection のミニ版)。apply/facade 関数)を壊さない(統一 I/F は上に薄く載せる)。HALCON_COVERAGE.md)/ ROS2 標準(PCL/grid_map/image_pipeline)を
抜けの地図として使い、統一 I/F 上で網羅を伸ばせる構造。悪例(現状): apply(image, "gaussian", a=0.5, b=0.5) / run_algo("name", seq) = 機械向け string-dispatch。
目指す自然さ(案・§9 で要ユーザー判断):
import fullseye as fs
# core オブジェクトのチェーン(画像 op):文のように読める
edges = fs.Image.load("scene.png").to_gray().gaussian(sigma=1.4).sobel()
# 名前空間モジュール + 設定オブジェクト + 動詞メソッド(知覚 op):Qt ウィジェット風
depth = fs.stereo.SGM(max_disp=128, window=5).compute(left, right)
cloud = fs.camera.Pinhole(K).backproject(depth)
plane = fs.pcseg.PlaneRANSAC(thresh=0.01).fit(cloud)
Qt から借りる: 名前空間モジュール(fs.stereo/fs.camera = QtWidgets 風)・設定オブジェクト+動詞メソッド
(.compute()/.fit()/.apply())・core オブジェクトのチェーン(Image)・sensible defaults・
discoverable。文字列名/生 registry/進化用 a/b は裏に隠す。
fs.Image(...).sobel() のチェーンか、知覚と同じ設定オブジェクト方式に統一か。.run() で確定)か。dyn_threshold 等)か、一般語彙(adaptive_threshold)か。Image チェーン + 知覚 1 モジュール(例 fs.stereo)を既存実装の薄いラッパで。additive・回帰 0。Image().gaussian() チェーン / 視覚=stereo.SGM().compute() 設定オブジェクト)
②eager ③一般語彙を主 + HALCON エイリアス ④Python API 先行(Studio は次段)。ndimage.gaussian_filter(v, sigma)= 進化 op と同じ下層ゆえ drift 無し)。進化 registry の
汎用 a/b ノブ(探索用の正規化・有界エンコード、例 sigma=0.3+2.7*a)は人間 API のパラメータ範囲に漏らさず、
Image.op(name, a, b) エスケープハッチにのみ残して 654 op の長い尾へアクセス。spike 実証済: spikes/unified_api_spike.py(画像チェーン + 視覚設定オブジェクト + 長い尾エスケープ)。
既存 809 op 無変更・additive。pcseg RANSAC が合成平面 400/400 点をインライア検出=実委譲を確認。
unified.py:
本セッションで実装した 600 の HALCON facade op(genuine numpy、data/halcon_facade_map.json)を
単一 registry + introspection メタ + 章別名前空間に載せた。additive・既存 op / 進化 registry /
fullseye パッケージ facade を一切変更せず(F7)。
Registry(ops)が 600 op を層横断で索引。ops.find(q)(名前/doc/章の全文検索)/
ops.list(namespace=…) / ops.stats()。UnifiedOp(name/実装関数の自然シグネチャ(inspect)/主 chapter/
namespace/doc/render_hint(image/region/contour/pose/point_cloud/matches/scalar/matrix)/provenance)。
ops.describe(name) が機械可読 dict を返す(Studio/エージェント共有)。描画ヒントは F6(Studio)の 2D/3D
自動描画選択メタ。contour/calib/recon3d/region/match/transform/
filter/image/tools/object3d/match3d/segment/measure/metrology/morph/matrix/inspection)。
例 u.calib.camera_calibration(obj, views) が Zhang 校正で真値 K を復元。進化用 a/b は露出しない
(各 op は自然な名前付き引数)。fullseye/__init__.py に additive 露出 → fs.vision.<ns>.<op>(...) / fs.vision_ops。
進化 REGISTRY(735)・知覚 facade(fs.stereo 等)と共存。spikes/unified_vision_demo.py(F1/F2/F3/F7 を実走表示)。テスト tests/test_unified.py
9 件 pass。回帰 test_op_contracts 3113 pass 0 fail(F7 確認)。fs.vision_ops)から Studio が 600 op を
自動列挙・パラメータ UI 自動生成・実行・描画自動選択する 2 プリミティブを spikes/studio_ops_browser.py に:
render_by_hint(result, hint, fig) = F3 の render_hint で 2D/3D 描画を自動選択(image=imshow /
region=マスク / contour=線 / point_cloud=3D 散布 / pose=RGB 軸 / matrix・scalar・matches=カード)。8 種全て検証。synthesize_args(op) / scalar_param_specs(op) = F3 の自然な param 名から合成入力と
スライダ spec を作る(param 名が意味を持つ=F1 の設計が効く)。honest 自動実行カバレッジ = 364/600(60%)
が合成入力だけで即実行・描画。残りは create_* が生む model handle 等の専用入力要(209)+ synthesizer の
形状ヒューリスティック外(27)で、いずれも F3 introspection カード(signature/doc/params/render_hint)を
表示=600 全てが「発見+メタ把握」できる。spikes/studio_app.py のツリーに vision-ops (600) を章別名前空間で自動展開、
op 選択で F3 カード + スライダ自動生成 + render_op_into(スライダ override→合成入力→render_hint 描画)。
既存 9 サンプル(vision/sim-source 棲み分け)と共存。smoke = 9/9 サンプル OK + 代表 op 経路 + カバレッジ表示。tests/test_studio_ops_browser.py 7 pass(render_hint 8 種・合成入力・カバレッジ>=300・
override 反映)。ギャラリー spikes/out_gallery/studio_f6_render_hints.png(8 hint の描画一覧)。unified.py が 3 層を単一 registry に統合:
①facade 600(本セッションの genuine HALCON 実装)②進化 registry 729(fs.REGISTRY, a/b ノブ、
自然 caller (image, a=0.5, b=0.5) で長い尾へ、provenance=evolution)③知覚 facade 240(fs.stereo/
pcseg/camera/terrain/… の公開関数、自然シグネチャ、provenance=perception)。計 1569 op / 57 名前空間。
provenance。out_sort(image/region/contour/feature/match/volume/color)→ hint。知覚 op は
モジュール既定 hint。→ Studio(F6)が 3 層すべてを render_hint で自動描画。自動実行カバレッジ 1129/1569(72%)。__getattr__)+ publish-before-load で re-entrancy 安全化。両 import 順で動作。import fullseye/import unified 双方で 1569 op、fs.vision.smooth.<op>(img,a,b)(進化)/
fs.vision.camera.intrinsic_matrix(...)(知覚)/ fs.vision.calib.camera_calibration(...)(facade)が
同じ作法で呼べる。テスト tests/test_unified.py 9 pass(3 層 provenance 検証)+ browser 7 pass、
回帰 test_op_contracts 3113 pass 0 fail(F7)。spikes/viewer3d.py adapter が
統一 registry の op 出力(render_hint)を Open3D geometry へ変換し 3 方式で見せる(再実装せず Open3D を裏に):
to_geometries(result, hint) = point_cloud→PointCloud(z 着色)+ 座標フレーム / pose→座標フレーム
(RViz2 の pose 軸相当) / mesh→TriangleMesh。GL 不要=テスト可。image/region/contour は空(→ 2D 側で描く)。show_interactive(geoms) = Open3D 対話ウィンドウ(mouse ナビ=RViz2 相当)。desktop GL で動く。render_offscreen = numpy 画像化(EGL 可なら)。本 Windows は EGL headless 非対応
([[reference_mujoco_gl_remote_desktop]])→ None を返し Studio は matplotlib 3D にフォールバック(graceful)。export_ply = .ply 書き出し(GL 不要=常に可。外部 Open3D/CloudCompare で開ける)。ground_grid(Grid Display 相当)。studio_app.py に「Open in 3D (Open3D) 🧊」ボタン。registry op が point_cloud/pose を
出すと有効化 → クリックで対話ウィンドウ。inline は matplotlib 3D 継続(EGL 不可のため)。compute_op で結果を保持。tests/test_viewer3d.py 8 pass(geometry/PLY/graceful fallback)、デモ spikes/viewer3d_demo.py
(evis 知覚シーン=点群→物体→6D pose を .ply + プレビュー PNG 生成、--show で対話ウィンドウ)。oss_adapter.py が
OSS(OpenCV 4.11 / scikit-image 0.26)を裏に、不在時 genuine numpy へ graceful フォールバックする
同一 I/F(config オブジェクト + 動詞メソッド)を実装。5 アダプタ:
stereo.BlockMatching / stereo.SGBM(cv2.StereoBM/SGBM ↔ fs.disparity_map/sgm numpy)filter.Bilateral(cv2.bilateralFilter ↔ numpy、フォールバック平均差 0.012)features.ORB(cv2.ORB ↔ Harris numpy)contour.FindContours(cv2.findContours ↔ skimage ↔ numpy、円輪郭 半径 14.3≈15).backend プロパティ(opencv/skimage/numpy(fallback))(4) prefer='numpy' で強制フォールバック、を満たす。unified._load_oss が provenance=oss-adapter で登録 → 計 1574 op / 4 層
(facade 600 / 進化 729 / 知覚 240 / OSS 5)。fs.vision.stereo.SGBM(max_disp=32).compute(l,r)(F1)、
find/describe に backend を出す(F3)。Studio(F6)でも config 生成→動詞メソッドで自動実行。tests/test_oss_adapter.py 7 pass(両 backend / フォールバック近似 / registry 統合)。
回帰 unified 9 + browser 7 + viewer3d 8 + op_contracts 3113 = 全 pass。次(§11-6 網羅拡張・磨き込み): synthesizer per-op 入力ヒント(Studio 自動実行 72%→上げ)/ OSS アダプタ拡充(watershed/SIFT/PnP 等)/ 画像 registry 654 の自然 API 化継続 / Studio 3D を desktop で常用。
unified.py: §7 の 2 形態を単一 registry(F2)+ F3 メタ準拠で実装。
Pipeline(汎用): pipeline('median', ('step_edges', {'min_rise':0.008})) で段を並べ、前段出力を次段の第 1 引数へ流す。
op 名/UnifiedOp/生 callable を _resolve_op で解決。run(x, trace=True) で中間出力も返す。
introspection: steps / render_hint(最終段)/ describe()(段ごとの F3 メタ + 束縛 kwargs)= Studio/エージェント共有。Image(§7 “文のように”): fs.Image(arr).median().sobel_amp().invert() ―― 属性を registry op 名に解決し
現配列へ適用して新 Image を返す(不変)。tuple 出力(elevation_map→(grid,extent))は先頭 ndarray を鎖の値に。history で適用列。Image→AttributeError / Pipeline→KeyError)。fs.Image/fs.Pipeline/fs.pipeline をトップレベル露出(fs.vision.* でも可)。spikes/unified_pipeline_demo.py(①画像チェーン ②知覚段組み ③introspection ④Image==Pipeline 一致)。
テスト tests/test_unified_pipeline.py 9 件 pass。回帰 test_unified+test_op_contracts+test_oss_adapter = 3129 pass 0 fail(F7)。sim_source.py: 物理シミュが視覚 op に入力を供給する同一契約
(config + 動詞 + .backend/.available + 不在時 raise)。MuJoCo は headless で実供給:
.intrinsics()(fovy→K)/.rgb()/.depth()/.ground_truth()(真値源)/.point_cloud()(深度逆投影→world 点群)。
sim→vision の橋を実証: MuJoCo(xml).point_cloud('top') → fs.elevation_map で block 高さを知覚。
Gazebo/IsaacSim は honest scaffold(available=False、動詞は明示 raise)。統一 registry に namespace sim/
provenance sim-source で登録(_load_sim)。テスト tests/test_sim_source.py 8 pass。spikes/studio_ops_browser.py: 合成器を追加(video/cube/volume/mask/
mesh V,F/grid/dst/uv/対応点/行列リスト/scalar)。自動実行カバレッジ 72%→79%(1139→1249/1580, +110 op)。
残 ~331 は model(異種: MjModel vs metrology handle)/ path(IO 境界)/ 単文字曖昧(u/B)で honest に頭打ち(捏造しない)。unified.py: 進化 op(v,a,b)の自然シグネチャを op(image) のみに(探索ノブ
a/b を非露出)。a/b は keyword-only の escape hatch として保持(op(image, a=0.2) で調整可=表現力は不変)。
describe('median')['signature'] = median(image)。test_unified(sim-source を valid provenance に追加)+test_op_contracts = 3122 pass。新機能テスト計 40 pass。spikes/viewer3d.py + viewer3d_launch.py:
現状 _open3d は draw_geometries で Qt をブロック(3D 窓を閉じるまで Studio が固まる)=常用不可だった。
launch_detached(geometries, title): geometry を一時 PLY バンドル(save_scene/load_scene)に
書き出し、viewer3d_launch.py を detached 起動(pythonw + DETACHED|CREATE_NO_WINDOW=console flash なし)。
Studio は即戻り固まらない/GL 落ちは子プロセスに隔離/複数窓可。実測: 親即終了(exit 0)、子 pythonw が GL 窓を所有・生存。show_interactive: Visualizer API に更新(暗背景 / point_size 3 / world 原点軸 / 見やすい初期ビュー front-up-zoom)。scene_title)で渡す。studio_app._open3d を launch_detached に切替(「別プロセスで起動、Studio 操作継続可」)。tests/test_viewer3d.py に save/load 往復・UTF-8 タイトル・empty→False を追加、計 11 pass。viewer3d.ViewerManager: 開いている 3D 窓(別プロセス)を追跡・一覧(死活プルーニング)・個別/全終了。
Studio に窓マネージャ UI(一覧 QListWidget / 選択を閉じる / 全部閉じる / 更新 / 環境状態ラベル)を追加、
_open3d は viewer_mgr.launch で追跡下に起動。複数窓の同時管理を実測(起動→一覧→個別/全終了)。sim_source.MuJoCo.scene_geometries(): MuJoCo の geom を 実形状 Open3D メッシュ化(sphere/box/capsule/
cylinder/ellipsoid/mesh を world 変換・色つき、plane/hfield はスキップ)。evis/ロケット等のモデルを 3D 窓で
そのままの姿で見られる。実測: walker2d 歩行 7 geom / ロケット 7 geom をメッシュ化し別プロセス窓に表示。test_viewer3d(ViewerManager lifecycle・launch 失敗)+ test_sim_source(scene_geometries)追加、計 22 pass。sim_source.save_animation/play_animation/launch_animation
spikes/anim_launch.py。毎フレーム qpos→mj_forward→各 geom の world 変換のみ更新(メッシュ再利用)で
Open3D 窓に再生。ロケット着陸(400 フレーム, soft 0.83m/s)を別プロセス窓で再生 実測。ViewerManager.track
で外部起動プロセスも管理下に。_populate_tree(filter) が名前/doc/namespace(reg.find)で
1580 op を即絞り込み(namespace 展開合計 = 該当数、実測一致)。export_ply/savefig)。問い: Fullseye/sim-source で 3DGS は出来るか。
実測環境: RTX 5090 (32GB) あり / torch=2.11.0+cpu(CPU ビルド)/ CUDA toolkit・gsplat・nerfstudio 未導入。→ 学習(GPU)は現状不可、要スタック整備。
sim-source の優位: 3DGS 最大の前段=多視点画像のカメラ姿勢推定(COLMAP)が、sim では ground-truth 姿勢が直接得られるため不要。
実装(GPU 不要・CPU で検証済): sim_source.MuJoCo
camera_to_world(cam) / extrinsics(cam) — MuJoCo カメラ frame は OpenGL 規約(-Z 前方/+Y 上)= nerfstudio transform_matrix と同一。project(pts, cam) — 姿勢自己検証。top カメラ再投影 diff 0.0000、oblique 0.0034(残差はオクルージョンで説明)。save_gsplat_dataset(out, cams) — named カメラ → transforms.json(PINHOLE)+ images/。capture_orbit(base_xml, out, n_views, radius, elevation_deg, lookat, fovy, ...) — リング多視点を XML 注入し検証済み cam_xpos/cam_xmat 経路で c2w 化。全 12 ビューで lookat 再投影の中心ズレ 0.000px、実描画確認、depth 同梱可。test_camera_pose_reprojection_exact / test_capture_orbit_dataset(test_sim_source 13 passed)。残(GPU 半分・要判断): 学習スタック整備。Windows は gsplat の CUDA ビルド摩擦あり(VS build tools+CUDA toolkit)。候補=(A) 専用 venv に torch cu128+gsplat / (B) WSL2 経由 / (C) exporter のみ維持し外部 trainer(ns-train splatfacto)に transforms.json を渡す。共有 py -3.11 env への影響回避のため専用環境推奨。
gsplat ネイティブ判定: torch 2.11.0+cu128 で GPU 実働(RTX 5090 / capability(12,0)=sm_120)。gsplat 1.5.3 は import 可だが CUDA kernel は初回 JIT ビルドで “No CUDA toolkit found. gsplat will be disabled”。nvcc/cl 不在、cu128 のプリビルド Windows wheel も無し(pt2.7/2.8/2.9/2.11 全滅)。→ gsplat ネイティブは CUDA Toolkit 12.8+VS Build Tools 必須(未導入)。
回避=純 PyTorch 3DGS(gsplat_torch.py): コンパイラ不要の参照 splatter(quat→R / 3D共分散→2D Jacobian投影 / 大域深度ソート alpha 合成)。OpenGL c2w を F=diag(1,-1,-1) で CV カメラへ。
tests/test_gsplat_torch.py(4 passed、数式のみ)。GPU 学習は venv .venv-gsplat(共有 py -3.11 は非変更)。残: gsplat 高速 backend 化(CUDA Toolkit+VS Build Tools 導入 or WSL2)/ densify・prune / SH 色 / SSIM 損失。純 torch は PoC 用(非 tiled で大規模は遅い)。
gsplat_train.py: SSIM+L1 損失、適応的 densify(高勾配 clone)/prune(低不透明度+巨大 floater)、hold-out best の early-stopping、ターンテーブル描画。CPU 回帰 tests/test_gsplat_train.py 4 passed。sim_source.orbit_scene/capture_orbit_scene(MjSpec でカメラ注入=assets 付き実 MJCF 対応)。go2(Menagerie)を 新規視点 PSNR 26.49dB(train 26.99 追随=過学習解消)/7978 gaussians/61.7s on RTX 5090。gsplat_cli.py: <scene.xml|builtin> <out> で capture→train→turntable.gif/novelview.png/gaussians.npz/report.json。builtin: go2/cassie/apollo。end-to-end 実測(exit 0)。docs/articles/qiita_3dgs_sim_native.md(glossary-first/honest/失敗掲載)+ psnr_curve.svg(過学習 vs 修正)。