|
|
# ============================================================
|
|
|
# HOMOGRAPHY_INTEGRATION.py
|
|
|
# Как подключить homography-enabled motion saliency в main.py
|
|
|
# ============================================================
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
# ИМПОРТЫ
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
"""
|
|
|
from camera_motion import (
|
|
|
estimate_ego_motion_adaptive,
|
|
|
estimate_global_homography_ex,
|
|
|
estimate_global_affine_ex,
|
|
|
homography_is_plausible,
|
|
|
affine_is_plausible,
|
|
|
warp_is_plausible,
|
|
|
)
|
|
|
from motion_saliency import MotionSaliency
|
|
|
"""
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
# ВАРИАНТ A (простой, рекомендуется начать с него):
|
|
|
# adaptive — homography с автоматическим fallback на affine
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
"""
|
|
|
motion_sal = MotionSaliency()
|
|
|
prev_gray_for_ms = None
|
|
|
ms_kind_stats = {"homography": 0, "affine": 0, "none": 0}
|
|
|
|
|
|
# В главном цикле:
|
|
|
gray_eff = cv2.cvtColor(frame_eff_bgr, cv2.COLOR_BGR2GRAY)
|
|
|
|
|
|
M, kind, ego_inliers, ego_outliers = estimate_ego_motion_adaptive(
|
|
|
prev_gray_for_ms, gray_eff
|
|
|
)
|
|
|
ms_kind_stats[kind] = ms_kind_stats.get(kind, 0) + 1
|
|
|
|
|
|
if not warp_is_plausible(M, kind):
|
|
|
M = None
|
|
|
kind = "none"
|
|
|
ego_outliers = np.zeros((0, 2), dtype=np.float32)
|
|
|
|
|
|
# В терминальной фазе отключаем — слишком большой parallax
|
|
|
ms_active = True
|
|
|
if intercept_params is not None:
|
|
|
phase = intercept_params.get("phase", "")
|
|
|
if phase in ("terminal", "critical"):
|
|
|
ms_active = False
|
|
|
|
|
|
if ms_active:
|
|
|
motion_sal.update(gray_eff, prev_gray_for_ms, M, kind=kind,
|
|
|
outlier_pts=ego_outliers)
|
|
|
else:
|
|
|
motion_sal.reset()
|
|
|
|
|
|
prev_gray_for_ms = gray_eff
|
|
|
"""
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
# ВАРИАНТ B (dual residual, максимально устойчивый):
|
|
|
# считаем обе модели, берём min residual. Дороже по CPU но
|
|
|
# устойчивее на сценах с несколькими плоскостями (горизонт+земля).
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
"""
|
|
|
# Считаем обе модели
|
|
|
H, h_in, h_out, h_meta = estimate_global_homography_ex(
|
|
|
prev_gray_for_ms, gray_eff
|
|
|
)
|
|
|
A, a_in, a_out = estimate_global_affine_ex(
|
|
|
prev_gray_for_ms, gray_eff
|
|
|
)
|
|
|
|
|
|
# Фильтруем по качеству
|
|
|
H_use = H if homography_is_plausible(H, h_meta) else None
|
|
|
A_use = A if affine_is_plausible(A) else None
|
|
|
|
|
|
# Объединяем outliers обеих моделей (точки, не объяснённые НИ одной)
|
|
|
# — пересечение дает самый чистый сигнал, но можно и объединение
|
|
|
if len(h_out) > 0 and len(a_out) > 0:
|
|
|
# Простое объединение обычно достаточно
|
|
|
ego_outliers = np.concatenate([h_out, a_out], axis=0)
|
|
|
elif len(h_out) > 0:
|
|
|
ego_outliers = h_out
|
|
|
elif len(a_out) > 0:
|
|
|
ego_outliers = a_out
|
|
|
else:
|
|
|
ego_outliers = np.zeros((0, 2), dtype=np.float32)
|
|
|
|
|
|
motion_sal.update_dual(
|
|
|
gray_eff, prev_gray_for_ms,
|
|
|
H=H_use, A=A_use,
|
|
|
outlier_pts=ego_outliers,
|
|
|
)
|
|
|
|
|
|
prev_gray_for_ms = gray_eff
|
|
|
"""
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
# ТЮНИНГ ПО СЦЕНАМ
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
#
|
|
|
# 1) Если в логах ms_kind_stats почти всегда "affine" — значит
|
|
|
# homography не проходит пороги качества. Варианты:
|
|
|
# - понизить HOMO_MIN_INLIERS до 35
|
|
|
# - понизить HOMO_MIN_FILLED_CELLS до 3
|
|
|
# - увеличить CAM_MOTION_MAX_CORNERS чтобы было больше точек
|
|
|
#
|
|
|
# 2) Если homography выбирается часто но daёт странные residual
|
|
|
# (вся земля горит) — это признак того, что она фитится в
|
|
|
# одну плоскость (небо), а земля как вторая плоскость даёт
|
|
|
# fake residual. Решение — переключиться на Variant B (dual),
|
|
|
# min-из-двух карт это исправляет.
|
|
|
#
|
|
|
# 3) Для сложных сцен с ЛЭП и лесом рекомендую сразу Variant B.
|
|
|
# Overhead ~2x по CPU для ego-motion, но motion_saliency
|
|
|
# всё равно дешёвый относительно YOLO, общий FPS просядет
|
|
|
# минимально.
|
|
|
#
|
|
|
# 4) Если летите очень низко над лесом (parallax огромный, никакая
|
|
|
# модель не помогает) — тогда motion saliency просто отключайте
|
|
|
# на такие периоды по высоте/скорости носителя, а полагайтесь
|
|
|
# на KLT+IMM которые у вас уже есть.
|
|
|
#
|
|
|
# 5) Если homography выдаёт вырожденные матрицы часто — уменьшите
|
|
|
# HOMO_MAX_CORNER_SHIFT до 80, это агрессивнее отсеивает плохие
|
|
|
# фиты (ценой иногда пропустить валидный резкий манёвр камеры).
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
# ДИАГНОСТИКА
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
|
# В конце сессии напечатайте статистику:
|
|
|
"""
|
|
|
total = sum(ms_kind_stats.values())
|
|
|
if total > 0:
|
|
|
print(f"Ego motion stats: "
|
|
|
f"homography={ms_kind_stats['homography']/total*100:.1f}% "
|
|
|
f"affine={ms_kind_stats['affine']/total*100:.1f}% "
|
|
|
f"none={ms_kind_stats['none']/total*100:.1f}%")
|
|
|
"""
|
|
|
# Здоровая статистика на вашем типе данных:
|
|
|
# homography: 50-80% (когда хватает текстуры)
|
|
|
# affine: 15-40% (чистое небо, слабая текстура)
|
|
|
# none: < 5% (если больше — проблема с LK/feature detection)
|