OPENCV画像処理、カメラのキャリブレーション、パースペクティブトランス変換、カラーマスク、すすり泣き、多項式フィットを使用して、道路の曲率と車線の車両オフセットを計算するコンピュータービジョンアルゴリズム。
Advanced Lane Findingプロジェクトは、先の道路のジオメトリを識別する際の[レーンライン検出]からさらに一歩です。
高速道路の運転のビデオ録画を使用して、このプロジェクトの目標は、道路の曲率の半径を計算することです。湾曲した道路は、まっすぐな道路よりも挑戦的な作業です。曲率を正しく計算するには、車線線を識別する必要がありますが、それに加えて、画像を歪み式にする必要があります。画像変換は、カメラのキャリブレーションと視点変換に必要です。
このプロジェクトはPythonで実装され、OpenCVイメージ処理ライブラリを使用します。ソースコードは、 AdvancedLaneFinding.ipynbにあります
[光学歪み]は、画像記録で発生する物理的な現象であり、カメラレンズを介して知覚されるときに直線がわずかに湾曲したものとして投影されます。高速道路の運転ビデオは、車の前面カメラを使用して記録され、画像は歪んでいます。歪み係数は各カメラに固有であり、既知の幾何学的形式を使用して計算できます。
埋め込まれたカメラでキャプチャされたチェスボード画像は、 camera_calフォルダーに提供されています。これらの画像の利点は、それらが高いコントラストと既知のジオメトリを持っていることです。存在する画像は、作業するために9 * 6の角に存在します。
# Object points are real world points, here a 3D coordinates matrix is generated
# z coordinates are 0 and x, y are equidistant as it is known that the chessboard is made of identical squares
objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)
オブジェクトポイントは、チェスボードパターンではすべての正方形が等しいという一般的な理解に基づいて設定されます。これは、オブジェクトポイントがグリッドインデックスから生成されたxおよびy座標を持ち、zは常に0であることを意味します。画像ポイントは、OpenCVの関数findChessboardCornersを使用して画像で見つかった対応するオブジェクトポイントを表します。
# Convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the chessboard corners
nx = 9
ny = 6
ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)
すべての画像をスキャンした後、画像ポイントリストには、カメラマトリックスと歪み係数を計算するためにオブジェクトポイントと比較するのに十分なデータがあります。これにより、「Calibratecamera」関数を使用して、正確なカメラマトリックスと歪み係数の識別につながります。
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
undist = cv2.undistort(img, mtx, dist, None, mtx)
OpenCV undistort関数は、カメラマトリックスと歪み係数を使用して画像を変換するために使用されます。
これらの写真を比較すると、カメラのキャリブレーション手法の結果が表示されます。チェスボードの絵では、歪みはより明白ですが、道路像ではより微妙です。それにもかかわらず、歪みのない画像は、道路の曲率の計算が誤っています。
曲率をカルクレートするために、理想的な視点は鳥瞰図です。これは、車両のフロントガラスを通る斜めではなく、上から道路が知覚されることを意味します。
このパースペクティブ変換は、ストレートレーンシナリオと、レーンラインが実際に並行しているという以前の一般的な知識を使用して計算されます。ソースポイントと目的地は、視点変換のために画像から直接識別されます。
#Source points taken from images with straight lane lines, these are to become parallel after the warp transform
src = np.float32([
(190, 720), # bottom-left corner
(596, 447), # top-left corner
(685, 447), # top-right corner
(1125, 720) # bottom-right corner
])
# Destination points are to be parallel, taking into account the image size
dst = np.float32([
[offset, img_size[1]], # bottom-left corner
[offset, 0], # top-left corner
[img_size[0]-offset, 0], # top-right corner
[img_size[0]-offset, img_size[1]] # bottom-right corner
])
OpenCVは、ソースポイントと目的地が与えられた画像の変換マトリックスを計算するためのパースペクティブ変換関数を提供します。 warpPerspective関数を使用して、鳥瞰図の視点変換が実行されます。
# Calculate the transformation matrix and it's inverse transformation
M = cv2.getPerspectiveTransform(src, dst)
M_inv = cv2.getPerspectiveTransform(dst, src)
warped = cv2.warpPerspective(undist, M, img_size)
目的は、レーンラインピクセルが保存され、道路から簡単に区別されるように画像を処理することです。 4つの変換が適用され、その後結合されます。
最初の変換では、灰色に覆われた画像にx sobelを取ります。これは、X方向の微分を表し、垂直になる傾向がある線を検出するのに役立ちます。最小しきい値を超える値のみが保持されます。
# Transform image to gray scale
gray_img =cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply sobel (derivative) in x direction, this is usefull to detect lines that tend to be vertical
sobelx = cv2.Sobel(gray_img, cv2.CV_64F, 1, 0)
abs_sobelx = np.absolute(sobelx)
# Scale result to 0-255
scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))
sx_binary = np.zeros_like(scaled_sobel)
# Keep only derivative values that are in the margin of interest
sx_binary[(scaled_sobel >= 30) & (scaled_sobel <= 255)] = 1
2番目の変換は、灰色のスケーリングされた画像の白いピクセルを選択します。白は、指定された写真の試行錯誤を使用して選択された200〜255の値で定義されます。
# Detect pixels that are white in the grayscale image
white_binary = np.zeros_like(gray_img)
white_binary[(gray_img > 200) & (gray_img <= 255)] = 1
3番目の変換は、HLSカラースペースを使用して飽和コンポーネント上にあります。これは、軽いコンクリート道路の黄色の線を検出するために特に重要です。
# Convert image to HLS
hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
H = hls[:,:,0]
S = hls[:,:,2]
sat_binary = np.zeros_like(S)
# Detect pixels that have a high saturation value
sat_binary[(S > 90) & (S <= 255)] = 1
4番目の変換は、10〜25の値を持つ色相コンポーネント上にあり、黄色に対応するものとして識別されました。
hue_binary = np.zeros_like(H)
# Detect pixels that are yellow using the hue component
hue_binary[(H > 10) & (H <= 25)] = 1
レーンラインの検出は、すでに歪みされておらず、ゆがんでいるバイナリのしきい値画像で実行されます。最初にヒストグラムが画像に計算されます。これは、ピクセル値が各列に合計され、左および右車線の最も可能性の高いX位置を検出することを意味します。
# Take a histogram of the bottom half of the image
histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)
# Find the peak of the left and right halves of the histogram
# These will be the starting point for the left and right lines
midpoint = np.int(histogram.shape[0]//2)
leftx_base = np.argmax(histogram[:midpoint])
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
画像の下部にあるこれらのベース位置から始めて、スライディングウィンドウ法が適用され、ラインピクセルを検索して上向きに進みます。レーンピクセルは、xおよびy座標がウィンドウで定義されている領域内にある場合に考慮されます。自信を持って十分なピクセルが検出された場合、それらはラインの一部であると確信するため、平均位置が計算され、次の上向きウィンドウの出発点として保持されます。
# Choose the number of sliding windows
nwindows = 9
# Set the width of the windows +/- margin
margin = 100
# Set minimum number of pixels found to recenter window
minpix = 50
# Identify window boundaries in x and y (and right and left)
win_y_low = binary_warped.shape[0] - (window+1)*window_height
win_y_high = binary_warped.shape[0] - window*window_height
win_xleft_low = leftx_current - margin
win_xleft_high = leftx_current + margin
win_xright_low = rightx_current - margin
win_xright_high = rightx_current + margin
# Identify the nonzero pixels in x and y within the window #
good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
(nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]
good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
(nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]
# Append these indices to the lists
left_lane_inds.append(good_left_inds)
right_lane_inds.append(good_right_inds)
これらのすべてのピクセルは、XおよびY座標のリストにまとめられています。これは、両方のレーンラインで対称的に行われます。 leftx 、 lefty 、 rightx 、 rightyピクセルの位置は関数から返され、その後、選択したピクセルの最適なラインフィットを見つけるために、各左側と右側に2度目の多項式が取り付けられます。
# Fit a second order polynomial to each with np.polyfit() ###
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
ここでは、識別された左側と右の線のピクセルは、それぞれ赤と青でマークされています。 2度の多項式は、結果の画像で追跡されます。
あるビデオフレームから次のビデオフレームへのレーンライン検索をスピードアップするには、前のサイクルからの情報が使用されます。次の画像には、以前のレーンラインに近接してレーンラインがある可能性が高くなります。これは、前の画像の左側の線と右ラインに適合する多項式が使用されて、検索領域を定義するために使用されます。
スライディングウィンドウメソッドは引き続き使用されていますが、ヒストグラムのピークポイントから始める代わりに、検索は、ウィンドウの幅のために特定のマージンを持つ前の行に沿って実行されます。
# Set the area of search based on activated x-values within the +/- margin of our polynomial function
left_lane_inds = ((nonzerox > (prev_left_fit[0]*(nonzeroy**2) + prev_left_fit[1]*nonzeroy +
prev_left_fit[2] - margin)) & (nonzerox < (prev_left_fit[0]*(nonzeroy**2) +
prev_left_fit[1]*nonzeroy + prev_left_fit[2] + margin))).nonzero()[0]
right_lane_inds = ((nonzerox > (prev_right_fit[0]*(nonzeroy**2) + prev_right_fit[1]*nonzeroy +
prev_right_fit[2] - margin)) & (nonzerox < (prev_right_fit[0]*(nonzeroy**2) +
prev_right_fit[1]*nonzeroy + prev_right_fit[2] + margin))).nonzero()[0]
# Again, extract left and right line pixel positions
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds]
rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds]
検索は、左右に2度の多項式関数を備えたleftx 、 lefty 、 rightx 、 righty Pixel座標を返します。
半径と車両の道路上の車両の位置をメートルで計算するには、ピクセルから変換するためにスケーリング係数が必要です。対応するスケーリング値は、Y方向に30メートルから720ピクセル、Xディメンションでは3.7メートルから700ピクセルです。
多項式フィットは、変換を行うために使用されます。各右車線および左車線ラインの適合線からのアライメントピクセルのx座標を使用して、変換係数が適用され、それぞれに多項式フィットが実行されます。
# Define conversions in x and y from pixels space to meters
ym_per_pix = 30/720 # meters per pixel in y dimension
xm_per_pix = 3.7/700 # meters per pixel in x dimension
left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)
right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)
# Define y-value where we want radius of curvature
# We'll choose the maximum y-value, corresponding to the bottom of the image
y_eval = np.max(ploty)
# Calculation of R_curve (radius of curvature)
left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])
right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])
曲率の半径は、画像の下部にあるy点を使用して計算されます。車両の位置を計算するために、ピクセルの多項式適合を使用して、画像の下部にあるYに対応する左右のレーンのX位置を決定します。
# Define conversion in x from pixels space to meters
xm_per_pix = 3.7/700 # meters per pixel in x dimension
# Choose the y value corresponding to the bottom of the image
y_max = binary_warped.shape[0]
# Calculate left and right line positions at the bottom of the image
left_x_pos = left_fit[0]*y_max**2 + left_fit[1]*y_max + left_fit[2]
right_x_pos = right_fit[0]*y_max**2 + right_fit[1]*y_max + right_fit[2]
# Calculate the x position of the center of the lane
center_lanes_x_pos = (left_x_pos + right_x_pos)//2
# Calculate the deviation between the center of the lane and the center of the picture
# The car is assumed to be placed in the center of the picture
# If the deviation is negative, the car is on the felt hand side of the center of the lane
veh_pos = ((binary_warped.shape[1]//2) - center_lanes_x_pos) * xm_per_pix
これらの2つの値の平均は、画像内の車線の中心の位置を示します。レーンの中心がnbpピクセルの量によって右にシフトされている場合、車がnbp * xm_per_pix metersによって左にシフトされることを意味します。これは、カメラが車両の中心軸に取り付けられているという仮定に基づいています。