電腦視覺 · Selected Work · 2026

OpVerse — Lung SLAM

NTU imLab OpVerse 專案的一環: 實驗室已有「把虛擬器官疊合到病人身上」的 AR 技術,但必須戴著 VR 頭盔操作。 這個子專案要做的是把 DROID-SLAM 應用在胸腔內視鏡影片上, 抽換掉官方的 keyframe 篩選機制、改用自訂的高反光/動態模糊偵測, 從單眼內視鏡影片重建相機軌跡與稀疏 3D 點雲, 未來可以不戴頭盔、直接在外接螢幕的內視鏡畫面上標出虛擬腫瘤位置。

電腦視覺 / SLAM / 醫療 AR NTU imLab · OpVerse 2026 github.com/huan131513/lung-slam
手術導航:經過影像疊合,醫師未來可以在內視鏡映像中及時知道tumer的位置
手術導航:經過影像疊合,醫師未來可以在內視鏡映像中及時知道target的位置

製作背景

實驗室在 OpVerse 專案中已經有一套「把虛擬器官疊合到真實病人身上」的技術, 能讓醫師在術前戴著 VR 頭盔「透視」病人體內的腫瘤位置。但這套技術必須戴著頭盔操作, 不方便在手術過程中持續使用。如果能把虛擬器官疊合直接接到內視鏡影像上, 就能不靠頭盔、直接在外接螢幕上看到虛擬器官與腫瘤位置—— 而要做到這件事,第一步得先解決「內視鏡在病人體內的即時位置」怎麼算出來,這正是這個子專案的範圍。

目標與技術選型

第一階段的目標很明確:從單眼胸腔內視鏡影片恢復相機在世界座標下的 6-DoF 軌跡與場景的稀疏 3D 點雲,不做稠密 mesh、不做呼吸造成的 4D 形變建模、也不做跟術前 CT 的配準——那些都是後續階段的範圍。 SLAM backbone 選用 DROID-SLAM:它是學習型(learning-based)方法, 對內視鏡這種低紋理(textureless)、光源隨鏡頭移動的場景比傳統幾何法穩健, 代價是整條 pipeline 都要 CUDA,只能在有 GPU 的機器上跑最後這一步。

1. Preprocess FOV 裁切 + calib.txt 2. 自訂 Filter brightness specular lap_var(模糊) 取代官方 keyframe 篩選 3. DROID-SLAM optical flow + bundle adjustment CUDA · RTX 3090 4. Trajectory + sparse points · Open3D
Pipeline 總覽:preprocess(FOV 裁切+校正)→ 自訂 bad-frame filter → DROID-SLAM → 相機軌跡+稀疏點雲

1. Preprocess:圓形視野偵測與去畸變裁切

內視鏡影像是一個圓形視野(circular FOV)嵌在黑色畫面中央,邊緣扭曲嚴重。 Preprocess 階段對前 30 張取樣影格做圓形偵測(灰階門檻 + 最大連通元件 + minEnclosingCircle), 取中位數當作穩健估計,再把半徑往內縮一個比例(預設 15%)丟掉扭曲最嚴重的邊緣, 最後裁切成內接正方形——這樣輸出就是 100% 相容官方 DROID-SLAM demo.py 的矩形影格,不需要額外的 mask 輸入。

Camera Calibration

用印出來的棋盤格(10×22 內角點)在內視鏡下多角度拍攝約 20 張照片, 用 OpenCV 分別跑 5-param(k1,k2,p1,p2,k3 全自由)與 4-param(固定 k3=0)兩組模型比較:內視鏡圓形視野的徑向資料範圍不夠大, k3 通常在擬合雜訊而非真實畸變,兩組 RMS 誤差相差在 5% 以內時就選 4-param。 這批資料的結果是 RMS ≈ 0.52 px(5-param)/ 0.53 px(4-param), 差距 3.3%,確認可以用較簡單的 4-param 模型。

動畫:內視鏡拍攝的棋盤格校正圖在原始畫面與 cv2.findChessboardCorners 偵測結果之間切換,偵測到的每個內角點標上彩色圓圈與連線,依照棋盤格的行與列分別著色。
cv2.findChessboardCorners 偵測到的角點(依行列上色),確認排序正確才會拿去解相機內參

2. 自訂 Bad-frame Filter:符合胸腔內視鏡的特性

原版 DROID-SLAM 是為一般場景設計的動作篩選(只看光流大小決定 keyframe), 但胸腔內視鏡影片有兩個一般場景少見的特性:組織表面濕潤反光造成大片高光過曝、 以及器械或組織快速移動造成動態模糊——這兩種爛幀送進 SLAM 容易產生錯誤的特徵匹配, 拖累重建品質。這個專案在 DROID 的動作篩選之外,另外刻了一層依畫質篩選的 filter, 對每一幀在 FOV 範圍內算三個指標:

門檻是絕對數值(預設過曝 >140、太暗 <50、高光反射比例 >5%), 但同一台內視鏡不同片段的亮度中位數可以差到 30 以上,固定門檻某些片段會濾過頭、某些又濾不到, 所以實務上會先跑一次不濾的 filter 只為了拿 frame_metrics.csv, 看這支影片自己的分佈(亮度/高光抓 p90~p95,模糊度抓 p5~p10 當門檻)再決定真正要用的參數。 濾出來的乾淨幀之後可以全部當 keyframe(適合動作幅度小、怕篩過頭的片段), 或是疊加回 DROID 原生的動作篩選(適合動作幅度夠大的片段)——兩種模式都支援。

胸腔內視鏡下拍攝的組織畫面,圓形視野中可見黃色脂肪組織、紅色血管化肌肉組織與帶血管紋理的淡色組織,右側有明顯的鏡面反光亮點。
真實胸腔內視鏡影格:圓形視野、組織表面濕潤造成的鏡面高光,正是 filter 要抓的兩種爛幀特徵之一

成果與現況

篩選後的影格丟進 DROID-SLAM 做學習型光流估計與 bundle adjustment,重建出相機軌跡與場景的稀疏彩色點雲:

Endoscope reconstruction — live replay:左側是 Open3D 即時重建的彩色點雲與相機軌跡(藍色線框為逐幀相機視錐),右側是對應的內視鏡原始畫面

Pipeline 的 preprocess/calibration/filter 三個階段已經在真實內視鏡素材上跑通並驗證: 圓形視野偵測穩定、真實 checkerboard 校正 RMS < 0.6 px、自訂 filter 能量化區分正常與過曝/高光影格。 因為沒有 ground truth 軌跡(沒有光學追蹤或機械臂座標可比對),評估先採計畫書訂的代用指標:

  • Frame registration rate:成功 register 的影格數 / 全片有效影格數,目標 ≥ 80%
  • Trajectory smoothness:相鄰姿態間的速度/角速度是否有異常跳躍
  • Loop closure consistency:鏡頭來回掃過同一區域時,點雲是否對齊
  • Visual sanity check:把稀疏點雲投影回影像,檢查是否落在合理的組織表面上

學到的事:把一套為一般場景設計的 learning-based SLAM 套用到醫療影像上, 真正的工程量不是換掉演算法,而是正確理解資料的物理特性——內視鏡的圓形視野、 邊緣扭曲、組織反光、呼吸造成的形變,每一個都會讓「直接套官方 demo.py」的結果看起來能跑, 實際上軌跡是錯的。自訂 filter 的門檻也再次印證:固定的絕對閾值在跨片段時不可靠, 得先看過資料自己的分佈,再決定要濾多嚴。

PythonDROID-SLAMOpenCV Open3DDockerCUDA