2022年3月22日火曜日

PiCar-Xでlane detection(02) 自動運転のための車線検出

 車(PiCar-X)を決められたコース(道路)に沿って走らせるためには、当然PiCameraで写した写真からコースの向かっている方向を読み取り、進むべき方法をコンピュータ(RaspberryPi)に教えなければなりません。その為にはまず車線を線分として検出する必要があります。

【道路の画像】




その手順が前回書いた

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

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

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

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

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

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

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

です。

まず順番は無視して、Canny法でエッジ検出をしてHough変換で直線を見つけてみます。
Canny法を使うために、はじめに画像をグレー変換し、ノイズ除去のために平滑化を行いました。

道路の画像として下記の絵に描いた図を使用しました。黄色と白色の車線があります。

グレー変換から
#画像の読み込み
image_path = 'road_image.png'
image = cv2.imread(image_path)

#gray scale
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

つづいてノイズ除去のための平滑化
# Gaussian blur
# 画像のぼかして平滑化することで、画像中のノイズ除去を行います
image_blur = cv2.GaussianBlur(image_gray, (5,5), 0)

Canny法によるedges検出
#edgesの検出
image_edges = cv2.Canny(image_blur,50,150)

Canny法のパラメーターの調整方法はここが解りやすいと思います。

Hough法による直線の検出
#Hough変換による直線の検出
hough_lines = cv2.HoughLinesP(image_edges, 2, np.pi/180, 200, np.array([]), minLineLength= 10, maxLineGap=50)

取りあえず白線、黄線のラインは緑色の複数の線として検出できています。
白線と黄線を別々に検出できれば応用範囲が広がりそうです。
そのためのステップが今回飛ばした3~4番目です。
今日はここまで、プログラム全体を下記に載せておきます。
import cv2
import numpy as np

def img_show(image, text):
  img_resize = cv2.resize(image, (640, 480))
  cv2.imshow(text, img_resize)
  cv2.waitKey(0)
  cv2.destroyAllWindows()

def show_lines(image, lines):
          lanelines_image = np.zeros_like(image)
          if lines is not None:
            for line in lines:
              X1, Y1, X2, Y2 = line.reshape(4)
              cv2.line(lanelines_image, (X1, Y1), (X2, Y2), (0,255,0), 2)
          return lanelines_image
  
#画像の読み込み
image_path = 'road_image.png'
image = cv2.imread(image_path)
img_show(image, 'original')

#gray scale
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
img_show(image_gray, 'gray')
cv2.imwrite('image_gray.png', image_gray)

# Gaussian blur
# 画像のぼかして平滑化することで、画像中のノイズ除去を行います
image_blur = cv2.GaussianBlur(image_gray, (5,5), 0) 
img_show(image_blur, 'blur')
cv2.imwrite('image_blur.png', image_blur)

#edgesの検出
image_edges = cv2.Canny(image_blur,50,150)
img_show(image_edges, 'edges')
cv2.imwrite('image_edges.png', image_edges)

#Hough変換による直線の検出
hough_lines = cv2.HoughLinesP(image_edges, 2, np.pi/180, 200, np.array([]), minLineLength= 10, maxLineGap=50)

image_lanelines = show_lines(image, hough_lines)

img_show(image_lanelines,'Hough lines')
cv2.imwrite('image_lanelines.png', image_lanelines)

2022年3月13日日曜日

PiCar-Xでlane detection(01) はじまり

 以前にPiCar-Xでライントレースを行いました。これはMindstormsでも行った白線と黒線の境界をセンサーで検知し、ステアリングを左右するものでした。

その後MindstormsではAfrel(株)の教材に則ってDeep Learningを使ってライントレースを行いましたが、PiCar-XでもまずはPiCameraを使ってのlane detectionから行ってみます。
これは、太い白線の道路中央の黄色いラインをPiCameraで捉えてその黄色いラインの位置情報をもとにステアリングを左右に動かすというものです。(下の動画を参照)

下記の記事を参考にしました。

DeepPiCar — Part 1: How to Build a Deep Learning, Self Driving Robotic Car on a Shoestring Budget

また、Packtより
『Applied Deep Learning and Computer Vision for Self-Driving Cars』
が出版されています。ebookで$32ほどですがPacktでは年に何回かセールを行っており、私は$5で買いました。

まずは現状の様子から


下記に画像処理の大まかな流れを示します。

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

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

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

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

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

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

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


プログラム全体のコードはここから

言い訳がましく断わっておきますが、私はプログラムの素人です。無駄なこと、間違っていることが有るかもしれませんが、取りあえず動いたのでお許しください。^^;


 

import time
def make_coordinates(image, line_parameters):
          slope, intercept = line_parameters
          y1 = image.shape[0]
          y2 = int(0)
          x1 = int((y1- intercept)/slope)
          x2 = int((y2 - intercept)/slope)
          return np.array([x1, y1, x2, y2])
        
def line_select(image, lines):
          data = []

          left_parameter = []
          right_parameter = []

          #-------temp----------
          x1, y1, x2, y2 =0, 0, 0, 0
          xt1, yt1, xt2, yt2 =0, 0, 0, 0
          #-------temp----------

          for line in lines:
              xt1, yt1, xt2, yt2 = line.reshape(4)
                            
              #垂直線への対応
              if xt1 == xt2:
                  xt2 = xt2 + 2

              if yt1 > yt2:
                  x1,y1,x2,y2 =  xt2, yt2, xt1, yt1
              else:
                  x1,y1,x2,y2 =  xt1, yt1, xt2, yt2
              
              #イレギュラーな不要な線を検出しないように直線の下端が30pixに満たない直線は除く ➡ この操作が必要かは再度検討
              if y2 > 30:
                  data.append([x1,y1,x2,y2])

          line_data = np.array(data)

          #col_numで指定した列を基準にソートする為のindexを取得
          col_num = 2
          sort_index = np.argsort(line_data[:, col_num])

          #sort_indexを元にline_dataをソート
          sorted_line_data = line_data[sort_index,:]

          #left_line
          x1, y1, x2, y2 = sorted_line_data[0]
          left_parameter = np.polyfit((x1, x2), (y1, y2), 1)

          #right_line
          x1, y1, x2, y2 = sorted_line_data[-1]
          right_parameter = np.polyfit((x1, x2), (y1, y2), 1)
         
          left_line_coordinates = make_coordinates(image, left_parameter)
          right_line_coordinates = make_coordinates(image, right_parameter)
 
          return np.array([left_line_coordinates, right_line_coordinates])
        
def show_lines(image, lines):
          lanelines_image = np.zeros_like(image)
          if lines is not None:
            for line in lines:
              X1, Y1, X2, Y2 = line.reshape(4)
              cv2.line(lanelines_image, (X1, Y1), (X2, Y2), (0,250,0), 10)
          return lanelines_image

def show_lines_result(image, lines):
          lanelines_image = np.zeros_like(image)
          if lines is not None:
            for line in lines:
              X1, Y1, X2, Y2 = line.reshape(4)
              cv2.line(lanelines_image, (X1, Y1), (X2, Y2), (0,0,255), 2)
          return lanelines_image

def reg_of_interest(image):
          Image_height = image.shape[0]
          Image_width = image.shape[1]
#          polygons = np.array([[(0, Image_height*(1/2)),(0, Image_height), (Image_width,Image_height), (Image_width, Image_height*(1/2))]])
          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

# ---------------------------------------

import cv2
import numpy as np
import os
from imutils.video import VideoStream
from imutils import resize
import time
from datetime import datetime

import sys
sys.path.append(r'/home/pi/picar-x/lib')
from utils import reset_mcu
reset_mcu()
from picarx import Picarx

px = Picarx()
px_power = 1

#
px.set_camera_servo1_angle(-5)

# px.set_camera_servo2_angle(-20)
servo2 = -25

# 写真の保存場所
CAM_DIR = "/home/pi/picar-x/Picamera/Result/"

vs = VideoStream(src=0).start()

while True:
    # ファイルの取込     
    img = vs.read()
    image = resize(img, width=640)
        
    # 画像の高さ 幅を取得
    Image_height, Image_width, c = image.shape
        
    try:
        # オリジナル画像から作業領域をクロップ
        cropped_image = image[int(Image_height/2):int(Image_height/2+100), 0:int(Image_width)]

        # HSV color space
        hsv_image = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2HSV)
        
        # binary conversion 
        lower = np.array([20, 0, 0])
        upper = np.array([40, 255, 255])
        
        masked_image = cv2.inRange(hsv_image, lower, upper)
        
        # Canny edge detection
        canny_conversion = cv2.Canny(masked_image, 50,155)
        
        # region of interest
        ROI_image = reg_of_interest(canny_conversion)
        
        # Hough変換による直線の検出
        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)
        
        # left_lineとright_lineの選択
        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_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
        
        px.set_camera_servo2_angle(servo2)
        px.set_dir_servo_angle(steer)
        px.forward(px_power)
        time.sleep(0.1)
        px.forward(0)
        
        #
        text = 'left:' + str(left_line_top) + ' ' + 'right:' + str(right_line_top)
        
        cv2.putText(combine_image,
            text=text,
            org=(10, 30),
            fontFace=cv2.FONT_HERSHEY_SIMPLEX,
            fontScale=0.7,
            color=(0, 255, 0),
            thickness=1,
            lineType=cv2.LINE_4)
        
        filename = time.strftime("%Y%m%d%H%M%S") + ".jpg"
        save_dir_filename = CAM_DIR + filename
        cv2.imwrite(save_dir_filename,combine_image)

    except:
        #print('Exception!')
        #print('photo No', filename)
        pass