長尺動画を複数セグメントに分割し、各セグメントを個別にCOLMAPで処理した後、結果をマージして大規模な統合メッシュを生成する。
各セグメントのPoisson再構成結果をマージすると、メッシュの裏面に布状のアーチファクト(閉包面)が発生。これにより視覚品質が著しく低下した。
個別セグメントのPoisson再構成が、点群の裏側(地面上)を閉包する面をそれぞれ独立に生成する。これらをマージすると2枚の閉包面が布状の膜として残存する。ポリゴン数の増加にもつながる。
Poisson Surface Reconstructionは与えられた点群から水密(watertight)な閉じたメッシュを生成するアルゴリズムである。点群の向き付き法線から内外を分ける指示関数(indicator function)を計算し、その等値面をメッシュ化する。このときドローン空撮では点群は上空側(撮影側)の表面のみを捉えており、物体の裏面や地面下には点が存在しない。Poissonはメッシュを閉じる必要があるため、点群の存在しない側(通常は地面の下方向)に人工的な蓋を張る。これが閉包面である。
| 要因 | 閉包面が大きくなりやすい | 閉包面が小さくなりやすい |
|---|---|---|
| 点群形状 | 平坦な地形(地面のみ)→ 広い蓋が必要 | 凸構造(建物・樹木)→ 点群自体が蓋を形成 |
| 境界条件 | セグメント端で3方向以上が開放 | 周囲を点群で囲まれている |
| 地面点の有無 | 水面・樹冠など地面なし → 大きな閉包面 | 地面まで点がある → 狭い閉包面 |
| 点群密度 | 低密度 → 補間範囲が広がる | 高密度 → 境界がシャープ |
例えば、橋梁だけを捉えたセグメントでは水面下に点が存在しないため、Poissonは橋の下面から水面位置まで広大な閉包面を張る。一方、建物街区のセグメントでは壁面点群が内側を閉じるため閉包面は小さくなる。また、フライト経路の端のセグメントほど開放境界が多く、中央のセグメントより閉包面が顕著になる。
全セグメントの点群をマージしてから単一のPoisson再構成を実行すると、閉包面は全体の外側境界に1回だけ張られる。各セグメント間の重複領域では実際の点群が蓋の役割を果たすため、余分な閉包面は発生しない。
上記の原理に基づき、点群マージ後に単一のPoisson再構成を実行する。
0156_Dで検証: 11.4M点→voxel 0.02で6M点にダウンサンプル→Poisson depth=10→481K verts, 969K tris。閉包面は完全に消滅。
恒久対策済み
現行の「全点群マージ→単一Poisson」はパラメータフリーで閉包面を解決したが、場合によっては別のアプローチも有効と考えられる。以下は継続検討中の方法。
現在の均等分割(等フレーム数)に代わり、立体物(建物・橋など)を上空から一括で捉えるようにセグメントを切り出す方法も考えられる。セグメント内の点群がZ方向に広がりを持つほど、Poisson再構成時の閉包面は小さくなる。
| パラメータ | 指標 | 取得元 | 判定 |
|---|---|---|---|
| GPS軌道の曲率 | 進行方位角の微分値 | SRT heading | 高曲率 → 構造物周りを旋回 |
| 対地高度変化 | rel_alt の分散/微分 | SRT rel_alt | 急変 → 構造物通過 |
| ジンバルピッチ | gimbal_pitch 角度 | SRT | 斜め下向き → 構造物注視 |
| 画像エントロピー | 輝度ヒストグラムのエントロピー | フレーム画像 | 高 → テクスチャ豊富(構造物) |
| オプティカルフロー | フレーム間の平均flow magnitude | ffmpeg motionフィルタ | 不均一 → 視差大(立体物) |
平直: ───────────────── 曲率≒0 → 平坦地形(ここでカット)
構造物: ╭───╮ 曲率大 → 橋・建物
│ │
╰───╯
| パラメータ | 指標 | 理由 |
|---|---|---|
| フレームあたり登録点数 | 各カメラのobservations数 | 多い → 立体構造が写っている |
| 3D点Z範囲 | 局所領域内の標高幅 | Z幅大 → 立体的(建物・橋) |
| 特徴点マッチ数 | フレーム対のinlier数 | 構造物境界で視差によるマッチ増 |
def detect_structure_boundaries(srt_data, window=30):
# 進行方位角の変化率(曲率)
heading_delta = np.abs(np.diff(srt_data.heading))
heading_delta = np.convolve(heading_delta, np.ones(window)/window, mode='same')
# 高度変化率
alt_delta = np.abs(np.diff(srt_data.rel_alt))
alt_delta = np.convolve(alt_delta, np.ones(window)/window, mode='same')
# 曲率+高度変化が共に低い → 平坦通過中 → ここがカット候補
flat_mask = (heading_delta < threshold_heading) & (alt_delta < threshold_alt)
cut_points = find_longest_flat_regions(flat_mask)
return cut_points