line_detector

役割

line_detectorLaserScan を 2D 点群へ変換し、RANSAC で直線を検出し、直線上の点列を線分へ分割します。

Interface

種別

名前

型 / 内容

subscribe

scan

sensor_msgs/msg/LaserScan

publish

lines

rogi_msgs/msg/LineArray

publish

line_segments

rogi_msgs/msg/LineSegmentArray

publish

corners

geometry_msgs/msg/PoseArray

parameter

max_iterations, max_lines, min_inliers

RANSAC 条件

parameter

distance_threshold

inlier 距離しきい値 \(d_{\mathrm{th}}\)

parameter

segment_gap_threshold

線分分割しきい値 \(g_{\mathrm{th}}\)

scan 点変換

beam index \(i\) の角度と点は、

\[\theta_i=\theta_{\min}+i\Delta\theta\]
\[\begin{split}p_i= \begin{bmatrix} x_i\\y_i \end{bmatrix} = \begin{bmatrix} r_i\cos\theta_i\\ r_i\sin\theta_i \end{bmatrix}\end{split}\]

です。range と angle filter を通った点だけ使います。

\[r_{\min}^{msg}\le r_i\le r_{\max}^{msg},\quad r_{\min}^{param}<r_i<r_{\max}^{param},\quad \theta_{\min}^{param}\le\theta_i\le\theta_{\max}^{param}\]

RANSAC

2 点 \(p_1=(x_1,y_1)\)\(p_2=(x_2,y_2)\) から直線を作ります。

\[a=y_1-y_2,\qquad b=x_2-x_1,\qquad c=x_1y_2-x_2y_1\]

正規化後の直線は \(ax+by+c=0\)\(a^2+b^2=1\) です。

\[(a,b,c)\leftarrow\frac{(a,b,c)}{\sqrt{a^2+b^2}}\]

\(p_j\) の距離は、

\[d_j=|a x_j+b y_j+c|\]

inlier 集合は、

\[I=\{j\mid d_j<d_{\mathrm{th}}\}\]

です。max_iterations 回の候補から \(|I|\) 最大の直線を選び、\(|I|>\mathrm{min\_inliers}\) の場合に採用します。

直交最小二乗 refine

inlier のサンプル集合 \(I_s\) から平均と共分散を求めます。

\[\mu=\frac{1}{n}\sum_{j\in I_s}p_j\]
\[\begin{split}\Sigma=\sum_{j\in I_s}(p_j-\mu)(p_j-\mu)^T = \begin{bmatrix} \sigma_{xx}&\sigma_{xy}\\ \sigma_{xy}&\sigma_{yy} \end{bmatrix}\end{split}\]

直線方向角は共分散の主軸です。

\[\theta_{\mathrm{line}}= \frac{1}{2}\operatorname{atan2}(2\sigma_{xy},\sigma_{xx}-\sigma_{yy})\]

更新後の直線係数は、

\[a=-\sin\theta_{\mathrm{line}},\qquad b=\cos\theta_{\mathrm{line}},\qquad c=-(a\mu_x+b\mu_y)\]

採用後は inlier を削除して次の線を探します。

線分化

直線方向ベクトルと基準点を、

\[\begin{split}u= \begin{bmatrix} -b\\a \end{bmatrix}, \qquad p_0= \begin{bmatrix} -ac\\-bc \end{bmatrix}\end{split}\]

とします。inlier 点を射影します。

\[t_j=(p_j-p_0)^T u\]

\(t_j\) で sort し、隣接点距離が次を満たす場所で分割します。

\[\|p_{j+1}-p_j\|>g_{\mathrm{th}}\]

各クラスタ端の射影値から線分を作ります。

\[p_s=p_0+t_{\min}u,\qquad p_e=p_0+t_{\max}u\]

corner

2 直線の交点は、

\[D=a_1b_2-a_2b_1\]
\[x=\frac{b_1c_2-b_2c_1}{D},\qquad y=\frac{c_1a_2-c_2a_1}{D}\]

です。\(|D|<10^{-8}\) は平行として無視します。

設定ファイル

sample では example/sample/config/sensing/config.yamlline_detector.ros__parameters を使います。

key

sample 値

数式上の意味

max_iterations

100

RANSAC 試行回数

max_lines

10

検出する最大直線数

min_inliers

10

採用条件 {math}`

refinement_sample_count

30

refine に使う最大 inlier 数

distance_threshold

0.02

inlier 条件 \(d_j<d_{\mathrm{th}}\)

segment_gap_threshold

0.5

線分分割条件 \(g_{\mathrm{th}}\)

min_range, max_range

0.30, 20.0

scan 点 filter

min_angle, max_angle

, π

scan 点 filter

rogi_nav.launch.py は同じ値から refinement_sample_count を抜き出し、launch argument ransac_refinement_sample_count としても渡します。結果として sensing_config_path の値を読み込んだ後、この値だけ明示上書きされます。

\[N_{\mathrm{refine}} \leftarrow \mathrm{sensing.yaml}[\mathrm{line\_detector}][\mathrm{refinement\_sample\_count}]\]

入力 topic は localization/config.yamltopics.localization_scan で決まり、sample では /scan_for_localization です。profile 側では launch.components.sensing.line_detector.enabled が起動可否です。