"""
dual_norm
案B: 二重正規化型の線検出
背景除算で照明勾配を除去し,
適応的閾値で局所ムラにも対応する二重防壁構成
"""
import time
import cv2
import numpy as np
from common.vision.line_detector import (
ImageParams,
LineDetectResult,
fit_row_centers,
)
from common.vision.morphology import (
apply_dist_mask,
apply_iso_closing,
apply_staged_closing,
apply_width_filter,
)
# 内訳計測用の累積値
_profile_count: int = 0
_profile_sums: dict[str, float] = {}
_profile_start: float = 0.0
_PROFILE_INTERVAL: float = 3.0
def _profile_reset() -> None:
"""計測値をリセットする"""
global _profile_count, _profile_sums, _profile_start
_profile_count = 0
_profile_sums = {}
_profile_start = time.time()
def _profile_record(label: str, elapsed: float) -> None:
"""計測値を記録する"""
_profile_sums[label] = (
_profile_sums.get(label, 0.0) + elapsed
)
def _profile_print() -> None:
"""計測結果を出力する"""
global _profile_count, _profile_start
if _profile_count == 0:
return
elapsed = time.time() - _profile_start
if elapsed < _PROFILE_INTERVAL:
return
parts = []
for label, total in _profile_sums.items():
avg = total / _profile_count * 1000.0
parts.append(f"{label}={avg:.1f}ms")
print(f"Pi: dual_norm内訳({_profile_count}f) "
+ " ".join(parts))
_profile_reset()
def detect_dual_norm(
frame: np.ndarray, params: ImageParams,
) -> LineDetectResult:
"""案B: 二重正規化型"""
global _profile_count
if _profile_count == 0 and not _profile_sums:
_profile_reset()
# 背景除算正規化
t0 = time.time()
bg_k = params.bg_blur_ksize | 1
bg = cv2.GaussianBlur(
frame, (bg_k, bg_k), 0,
)
normalized = (
frame.astype(np.float32) * 255.0
/ (bg.astype(np.float32) + 1.0)
)
normalized = np.clip(
normalized, 0, 255,
).astype(np.uint8)
t1 = time.time()
_profile_record("背景除算", t1 - t0)
# 適応的閾値(ガウシアン,BINARY_INV)
block = max(params.adaptive_block | 1, 3)
binary = cv2.adaptiveThreshold(
normalized, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV,
block, params.adaptive_c,
)
# 固定閾値との AND(有効時のみ)
if params.global_thresh > 0:
_, global_mask = cv2.threshold(
normalized, params.global_thresh,
255, cv2.THRESH_BINARY_INV,
)
binary = cv2.bitwise_and(binary, global_mask)
t2 = time.time()
_profile_record("閾値処理", t2 - t1)
# 段階クロージング or 等方クロージング
if params.stage_min_area > 0:
binary = apply_staged_closing(
binary,
params.stage_close_small,
params.stage_min_area,
params.stage_close_large,
)
else:
binary = apply_iso_closing(
binary, params.iso_close_size,
)
# 距離変換マスク + 幅フィルタ
binary = apply_dist_mask(
binary, params.dist_thresh,
)
if params.width_near > 0 and params.width_far > 0:
binary = apply_width_filter(
binary,
params.width_near,
params.width_far,
params.width_tolerance,
)
t3 = time.time()
_profile_record("後処理", t3 - t2)
# 行ごと中心抽出 + フィッティング
result = fit_row_centers(
binary, params.min_line_width,
median_ksize=params.median_ksize,
neighbor_thresh=params.neighbor_thresh,
residual_thresh=params.residual_thresh,
)
t4 = time.time()
_profile_record("fitting", t4 - t3)
_profile_count += 1
_profile_print()
return result