2022年5月16日月曜日

PiCar-Xでlane detection(06) 取得した道路情報から位置情報の計算

 「PiCar-Xでlane detection(02) 自動運転のための車線検出」で下図のように車線を検出することが出来ました。今回はここから車の進むべき方向を決める、道路の真ん中=道路の向きの中心を求める方法について考えてみます。


一般的にはここから左側の複数の線分(赤い領域)の平均を取って左車線、同じく右側の複数の線分(青い領域)の平均を取って右車線を求めて、その両線の中央値を計算して道路の中央を求めます。


ただ、これが意外とややっこしい(-.-;)

真ん中から左の線分を左車線、同じく右側の線分を右車線としたいところですが、例えば下の写真のように必ずしも車は常に道路の中央からPiCameraで写真を撮っているわけではなく、カーブなどでは右に左にステアリングをとっているので、道路に対して斜めからの写真も多くなります。下の図のように片方の車線が画面からはみ出しそうになることも再々です。


この場合、右側車線の上端も写真中央から左側に位置しており、真ん中で左右を分けようとすると全て左側車線と判断されてします。または、道路は上図のように「ハの字」に映っているから、右に傾いている線分を左側車線、左に傾いている線分を右側車線と考えたい所ですが、カーブなどではすべて右に傾いたり、全て左に傾いたりすることも良くあります。
とにかく左側車線と右側車線の区分をすることが出来ず、どのように左右を分けたら良いのか悩むところです。

そこで参考とした資料で行っていた左右の車線を検出することを諦めて、「PiCar-Xでlane detection(01)」では道路の中央に黄色い線を追加し、この黄色い線を追随するようプログラムに変更しました。

以下「PiCar-Xでlane detection(01)」に掲載したプログラムに沿って、今までの説明と話がダブってしまいますが、その動きを見てみたいと思います。デモ画像として「PiCar-Xでlane detection(01)」で走らせたモデルコースと同じ配色の

を使って、処理の流れを見ていきます。

1.PiCameraから画像の取り込み

「PiCar-Xでlane detection(04) PiCameraから画像の取り込み」で説明したように、imutilsのVideoStreamを使って動画撮影➡画像の切取りを行っています。

# 画像の高さ 幅を取得
Image_height, Image_width, c = image.shape

    Image_height:  480
    Image_width:  640


2.オリジナル画像から作業領域をクロップ

cropped_image = image[int(Image_height / 2):int(Image_height / 2 + 100), 0:int(Image_width)]

画像の高さは、中央から下へ幅100pix、横幅は画像の幅そのままでクロップ


3.画像をBGRからHSVへ変換

hsv_image = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2HSV)


4.OpenCV – inRangeで画像を2値化

lower = np.array([20, 0, 0])
upper = np.array([40, 255, 255])
masked_image = cv2.inRange(hsv_image, lower, upper)


5.OpenCV – cannyによるエッジ検出

canny_conversion = cv2.Canny(masked_image, 50, 155)


6.region of interest(注目する領域)を設定

ROI_image = reg_of_interest(canny_conversion)

見た目は変化ありませんが、「PiCar-Xでlane detection(05) PiCameraで撮影した画像の切り取りについて」で説明したように左右を台形状にカットしています。


7.OpenCV – HoughLinesPによる直線の検出

Hough_lines = cv2.HoughLinesP(ROI_image, 2, np.pi / 180, 50, np.array([]), minLineLength=20, maxLineGap=20)

# Hough_linesをcropped_imageと同じ大きさの黒色のキャンパスに描画
lanelines_image = show_lines(cropped_image, Hough_lines)


【それに続く処理】

黄色い線の左右両端を検出しているので、この例では左右2本の線分が検出されています。ただこの例でも左側がやや短く検出されています。また撮影条件によっては線分が途中で途切れたりして2本以上の線分として検出されるケースもあります。このブログページの最初の画像がそのよい例だと思います。
これらの複数の線分から左端・右端の線分を求めます。

left_right_lines = line_select(lanelines_image, Hough_lines)

# left-right_linesをcropped_imageと同じ大きさの黒色のキャンパスに描画
line_image = show_lines_result(cropped_image, left_right_lines)


# 上のline_imageとcropped_imageとを合成
combine_image = cv2.addWeighted(cropped_image, 0.8, line_image, 1, 1)



left_right_linesの中身は、

    array([[313, 100, 307,   0], 
              [341, 100, 335,   0]])

2本の線分のデータとなっています。


プログラムでは、最終的に上のcombine_imageに左右両線の上端の数値データを付け加えた画像を保存しています。



line_select関数で、Hough変換で得られた複数の線分の一番左の線分と一番右側の線分を取得していますが、それ以外にも
●垂直な線分はmake_coordinates関数の計算でエラーとなるので、作為的に2pixをプラスしている
●線分の下端位置が30pixに満たない線分は、イレギュラーなデータとして除く
といった処理も行っています。


このleft_right_linesのデータを使って、左右両線分の上端の平均(道路の中央)を求め、この平均(道路の中央)が画面の中央値(Image_width/2)に位置するように舵を切ります。

# left_line_top
left_line_top = left_right_lines[0, 2]

# right_line_top
right_line_top = left_right_lines[1, 2]

center_line = (left_line_top + right_line_top) / 2

# image_center
image_center = Image_width / 2

# -----------------------------------------------------------------------------
# steer
steer = (center_line - image_center) / image_center * 80
sterrは、  
px.set_dir_servo_angle(steer)で使う前輪を何度回転させるかの数値で、  
steer=(center_line-image_center)/image_center×80  
の式で画面の中央から大きくズレているときは大きく、ズレが小さいときは小さく舵を切るように制御しています。
上の式の「80」は、実際に動かしながら試行錯誤で求めた(ハイパー)パラメーターで、モデル条件が変われば改めて試行錯誤で最適解を求め直す必要があります。

2022年5月6日金曜日

PiCar-Xでlane detection(05) PiCameraで撮影した画像の切り取りについて

 今回は、手順の2番目と6番目についてです。

1.PiCameraから画像の取り込み

2.オリジナル画像から作業領域をクロップ

3.画像をBGRからHSVへ変換

4.OpenCV – inRangeで画像を2値化

5.OpenCV – cannyによるエッジ検出

6.region of interest(注目する領域)を設定

7.OpenCV – HoughLinesPによる直線の検出


私の環境でPiCameraで撮影したオリジナルの画像は例えば下の写真のようになっています。




私たちが必要としているのは、これから車が進むであろう写真中央部の道路の位置情報(中央はどのあたりか?)ですから、写真上部はこの後の作業には要らない部分であり、そればかりか後の作業で不要な線分を拾ったりして、多くのノイズを発生させる原因ともなり得ます。また足元に近い部分も不要になりますので、必要と思われる部分だけを切り取ることにしました。これが2番目の操作です。


        # オリジナル画像から作業領域をクロップ
        cropped_image = image[int(Image_height/2):int(Image_height/2+100), 0:int(Image_width)]

画面中央の高さから100ピクセルほどの高さの画像を切り取っています。

さらに少しでもノイズを拾わないために、下の画像のピンク色の台形で囲った部分だけを以下の処理に使うことにしました。これが6番目の操作です。


def reg_of_interest(image):
          Image_height = image.shape[0]
          Image_width = image.shape[1]
          polygons = np.array([[(100, 0),(0, Image_height), (Image_width-40,Image_height), (Image_width-100, 0)]])
          image_mask = np.zeros_like(image)
          cv2.fillPoly(image_mask, np.int32([polygons]), 255)
          masking_image = cv2.bitwise_and(image,image_mask)
          return masking_image

切り取る台形の形は適当に決めています。

これで画像データの前処理は終了です。次回は最終回、その画像から中央の位置を数値化します。

今回、カメラで撮った写真を元にライントレースして、PiCar-Xを自動走行させていますが参考テキストそのままでは上手くいきません。参考テキストでの走行環境、私の走行環境、その他の環境で条件パラメーター(いわゆるハイパーパラメーター)を変えていかないと上手く動かないようです。