78 lines
2.9 KiB
Python
78 lines
2.9 KiB
Python
|
|
"""声呐视频发布前:在 FishMeasure ``optical_flow/visualize_optical_flow.py`` 中做稠密光流 overlay。"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import importlib.util
|
|||
|
|
import threading
|
|||
|
|
import time
|
|||
|
|
from pathlib import Path
|
|||
|
|
from types import ModuleType
|
|||
|
|
from typing import Optional
|
|||
|
|
|
|||
|
|
from loguru import logger
|
|||
|
|
|
|||
|
|
from app.settings import Settings
|
|||
|
|
|
|||
|
|
_mod_lock = threading.Lock()
|
|||
|
|
_cached_mod: Optional[ModuleType] = None
|
|||
|
|
_cached_mod_path: Optional[str] = None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _load_optical_flow_module(settings: Settings) -> Optional[ModuleType]:
|
|||
|
|
global _cached_mod, _cached_mod_path
|
|||
|
|
path = settings.fish_measure_root / "optical_flow" / "visualize_optical_flow.py"
|
|||
|
|
path_str = str(path)
|
|||
|
|
with _mod_lock:
|
|||
|
|
if _cached_mod is not None and _cached_mod_path == path_str:
|
|||
|
|
return _cached_mod
|
|||
|
|
if not path.is_file():
|
|||
|
|
logger.warning("[sonar-video] optical flow module not found: {}", path)
|
|||
|
|
return None
|
|||
|
|
spec = importlib.util.spec_from_file_location("fishmeasure_optical_flow_viz", path)
|
|||
|
|
if spec is None or spec.loader is None:
|
|||
|
|
return None
|
|||
|
|
mod = importlib.util.module_from_spec(spec)
|
|||
|
|
spec.loader.exec_module(mod)
|
|||
|
|
_cached_mod = mod
|
|||
|
|
_cached_mod_path = path_str
|
|||
|
|
return mod
|
|||
|
|
|
|||
|
|
|
|||
|
|
def run_sonar_optical_flow_overlay(
|
|||
|
|
src: Path,
|
|||
|
|
flow_out: Path,
|
|||
|
|
settings: Settings,
|
|||
|
|
) -> bool:
|
|||
|
|
"""将 ``src`` 处理为 overlay 光流视频写入 ``flow_out``。失败返回 False(调用方应回退直转码)。"""
|
|||
|
|
if not settings.biomass_sonar_optical_flow:
|
|||
|
|
return False
|
|||
|
|
mod = _load_optical_flow_module(settings)
|
|||
|
|
if mod is None:
|
|||
|
|
return False
|
|||
|
|
run_fn = getattr(mod, "run_optical_flow_video", None)
|
|||
|
|
if not callable(run_fn):
|
|||
|
|
logger.warning("[sonar-video] run_optical_flow_video missing in optical flow module")
|
|||
|
|
return False
|
|||
|
|
src_size_mb = src.stat().st_size / (1024 * 1024) if src.is_file() else 0
|
|||
|
|
logger.info("[sonar-video] optical flow starting: {} ({:.1f} MB), resize={}", src.name, src_size_mb, settings.biomass_sonar_optical_flow_resize)
|
|||
|
|
t0 = time.monotonic()
|
|||
|
|
try:
|
|||
|
|
ok = run_fn(
|
|||
|
|
src,
|
|||
|
|
flow_out,
|
|||
|
|
mode="overlay",
|
|||
|
|
resize=float(settings.biomass_sonar_optical_flow_resize),
|
|||
|
|
progress_log=False,
|
|||
|
|
)
|
|||
|
|
elapsed = time.monotonic() - t0
|
|||
|
|
if ok:
|
|||
|
|
out_size_mb = flow_out.stat().st_size / (1024 * 1024) if flow_out.is_file() else 0
|
|||
|
|
logger.info("[sonar-video] optical flow done in {:.1f}s: {} -> {} ({:.1f} MB)", elapsed, src.name, flow_out.name, out_size_mb)
|
|||
|
|
else:
|
|||
|
|
logger.warning("[sonar-video] optical flow returned False after {:.1f}s for {}", elapsed, src.name)
|
|||
|
|
return bool(ok)
|
|||
|
|
except Exception:
|
|||
|
|
elapsed = time.monotonic() - t0
|
|||
|
|
logger.exception("[sonar-video] optical flow raised exception after {:.1f}s for {}", elapsed, src.name)
|
|||
|
|
return False
|