"""
line_detector
カメラ画像から黒線の位置を検出するモジュール
近方・遠方の2領域で重心 x 座標を算出する
"""
from dataclasses import dataclass
import cv2
import numpy as np
from common import config
# ガウシアンブラーのカーネルサイズ
BLUR_KERNEL_SIZE: int = 5
# 二値化の閾値(黒線検出用,この値以下を黒とみなす)
BINARY_THRESHOLD: int = 80
# 近方領域の y 範囲(画像下部)
NEAR_Y_START: int = int(config.FRAME_HEIGHT * 0.7)
NEAR_Y_END: int = config.FRAME_HEIGHT
# 遠方領域の y 範囲(画像上部)
FAR_Y_START: int = int(config.FRAME_HEIGHT * 0.3)
FAR_Y_END: int = int(config.FRAME_HEIGHT * 0.5)
@dataclass
class LineDetectResult:
"""線検出の結果を格納するデータクラス
Attributes:
near_x: 近方領域の線の重心 x 座標(未検出時は None)
far_x: 遠方領域の線の重心 x 座標(未検出時は None)
near_error: 近方偏差(画像中心からのずれ)
far_error: 遠方偏差(画像中心からのずれ)
binary_image: 二値化後の画像(デバッグ用)
"""
near_x: float | None
far_x: float | None
near_error: float
far_error: float
binary_image: np.ndarray | None
def detect_line(frame: np.ndarray) -> LineDetectResult:
"""画像から黒線の位置を検出する
Args:
frame: BGR 形式のカメラ画像
Returns:
線検出の結果
"""
# グレースケール変換
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# ガウシアンブラー
blurred = cv2.GaussianBlur(
gray,
(BLUR_KERNEL_SIZE, BLUR_KERNEL_SIZE),
0,
)
# 二値化(黒線を白,背景を黒に反転)
_, binary = cv2.threshold(
blurred, BINARY_THRESHOLD, 255,
cv2.THRESH_BINARY_INV,
)
# 画像の中心 x 座標
center_x = config.FRAME_WIDTH / 2.0
# 近方領域の重心算出
near_roi = binary[NEAR_Y_START:NEAR_Y_END, :]
near_x = _calc_centroid_x(near_roi)
# 遠方領域の重心算出
far_roi = binary[FAR_Y_START:FAR_Y_END, :]
far_x = _calc_centroid_x(far_roi)
# 偏差の計算
near_error = 0.0
if near_x is not None:
near_error = (center_x - near_x) / center_x
far_error = 0.0
if far_x is not None:
far_error = (center_x - far_x) / center_x
return LineDetectResult(
near_x=near_x,
far_x=far_x,
near_error=near_error,
far_error=far_error,
binary_image=binary,
)
def _calc_centroid_x(
roi: np.ndarray,
) -> float | None:
"""ROI 内の白ピクセルの重心 x 座標を算出する
Args:
roi: 二値化された領域画像
Returns:
重心の x 座標,白ピクセルがない場合は None
"""
moments = cv2.moments(roi)
if moments["m00"] == 0:
return None
return moments["m10"] / moments["m00"]