Newer
Older
RobotCar / tests / test_line_detector.py
"""line_detector モジュールのテスト"""

import numpy as np
import pytest

from common import config
from common.vision.line_detector import (
    ImageParams,
    LineDetectResult,
    build_result,
    detect_line,
    fit_row_centers,
    no_detection,
)


class TestNoDetection:
    """no_detection のテスト"""

    def test_returns_not_detected(
        self, blank_image: np.ndarray,
    ) -> None:
        """detected=False で全フィールドがデフォルト値"""
        result = no_detection(blank_image)
        assert result.detected is False
        assert result.position_error == 0.0
        assert result.heading == 0.0
        assert result.curvature == 0.0
        assert result.poly_coeffs is None
        assert result.row_centers is None
        assert result.binary_image is not None


class TestBuildResult:
    """build_result のテスト"""

    def test_straight_center_line(self) -> None:
        """画像中央の直線は position_error ≈ 0"""
        h = config.FRAME_HEIGHT
        w = config.FRAME_WIDTH
        center_x = w / 2.0
        # x = center_x (定数) → coeffs = [0, 0, center_x]
        coeffs = np.array([0.0, 0.0, center_x])
        binary = np.zeros((h, w), dtype=np.uint8)
        result = build_result(coeffs, binary)
        assert result.detected is True
        assert result.position_error == pytest.approx(
            0.0, abs=0.01,
        )
        assert result.heading == pytest.approx(
            0.0, abs=0.01,
        )
        assert result.curvature == pytest.approx(
            0.0, abs=0.01,
        )

    def test_offset_line(self) -> None:
        """左にオフセットした直線は position_error > 0"""
        h = config.FRAME_HEIGHT
        w = config.FRAME_WIDTH
        # 左寄りの直線
        offset_x = w / 4.0
        coeffs = np.array([0.0, 0.0, offset_x])
        binary = np.zeros((h, w), dtype=np.uint8)
        result = build_result(coeffs, binary)
        assert result.position_error > 0  # 中心より左


class TestDetectLine:
    """detect_line のテスト"""

    def test_current_detects_straight_line(
        self, straight_line_image: np.ndarray,
    ) -> None:
        """現行手法で中央の直線を検出できる"""
        # 小さいテスト画像用にパラメータを調整
        params = ImageParams(
            method="current",
            clahe_grid=2, blur_size=3,
            open_size=1, close_width=3,
            close_height=1,
        )
        result = detect_line(
            straight_line_image, params,
        )
        assert result.detected is True
        assert abs(result.position_error) < 0.5

    def test_current_no_line(
        self, blank_image: np.ndarray,
    ) -> None:
        """均一画像では線を検出しない"""
        params = ImageParams(method="current")
        result = detect_line(blank_image, params)
        assert result.detected is False

    def test_blackhat_detects_straight_line(
        self, straight_line_image: np.ndarray,
    ) -> None:
        """案A で中央の直線を検出できる"""
        params = ImageParams(
            method="blackhat",
            blackhat_ksize=15,
            binary_thresh=30,
            blur_size=3,
            iso_close_size=1,
            dist_thresh=0.0,
            min_line_width=1,
            median_ksize=0,
            neighbor_thresh=0.0,
            residual_thresh=0.0,
        )
        result = detect_line(
            straight_line_image, params,
        )
        assert result.detected is True

    def test_dual_norm_detects_straight_line(
        self, straight_line_image: np.ndarray,
    ) -> None:
        """案B で中央の直線を検出できる"""
        params = ImageParams(
            method="dual_norm",
            bg_blur_ksize=21,
            adaptive_block=11, adaptive_c=5,
            iso_close_size=1,
            dist_thresh=0.0,
            min_line_width=1,
            median_ksize=0,
            neighbor_thresh=0.0,
            residual_thresh=0.0,
        )
        result = detect_line(
            straight_line_image, params,
        )
        assert result.detected is True

    def test_robust_detects_straight_line(
        self, straight_line_image: np.ndarray,
    ) -> None:
        """案C で中央の直線を検出できる"""
        params = ImageParams(
            method="robust",
            blackhat_ksize=15,
            adaptive_block=11, adaptive_c=5,
            iso_close_size=1,
            dist_thresh=0.0,
            min_line_width=1,
            median_ksize=0,
            neighbor_thresh=0.0,
            residual_thresh=0.0,
        )
        result = detect_line(
            straight_line_image, params,
        )
        assert result.detected is True

    def test_valley_detects_straight_line(
        self, straight_line_image: np.ndarray,
    ) -> None:
        """案D で中央の直線を検出できる"""
        params = ImageParams(method="valley")
        result = detect_line(
            straight_line_image, params,
        )
        assert result.detected is True

    def test_default_params(
        self, straight_line_image: np.ndarray,
    ) -> None:
        """params=None でもデフォルトで動作する"""
        result = detect_line(straight_line_image)
        assert isinstance(result, LineDetectResult)

    def test_result_has_binary_image(
        self, straight_line_image: np.ndarray,
    ) -> None:
        """結果に二値化画像が含まれる"""
        result = detect_line(straight_line_image)
        assert result.binary_image is not None
        assert result.binary_image.shape == (
            config.FRAME_HEIGHT, config.FRAME_WIDTH,
        )


class TestFitRowCenters:
    """fit_row_centers のテスト"""

    def test_detects_binary_line(
        self, binary_line: np.ndarray,
    ) -> None:
        """二値画像の白線から中心をフィッティングできる"""
        result = fit_row_centers(
            binary_line, min_width=1,
        )
        assert result.detected is True
        assert abs(result.position_error) < 0.3

    def test_empty_binary(self) -> None:
        """白ピクセルがない二値画像では検出しない"""
        h, w = config.FRAME_HEIGHT, config.FRAME_WIDTH
        empty = np.zeros((h, w), dtype=np.uint8)
        result = fit_row_centers(empty, min_width=1)
        assert result.detected is False