อัลกอริทึมการมองเห็นคอมพิวเตอร์เพื่อคำนวณความโค้งของถนนและการชดเชยยานพาหนะเลนโดยใช้การประมวลผลภาพ OpenCV, การสอบเทียบกล้อง, การแปลงมุมมอง, มาสก์สี, sobels และพหุนามพอดี
โครงการ ค้นหาเลนขั้นสูง เป็นขั้นตอนต่อไปจาก [การตรวจจับเส้นเลน] ในการระบุรูปทรงเรขาคณิตของถนนข้างหน้า
การใช้การบันทึกวิดีโอของการขับขี่บนทางหลวงเป้าหมายของโครงการนี้คือการคำนวณรัศมีของความโค้งของถนน ถนนโค้งเป็นงานที่ท้าทายกว่าถนนสายตรง ในการคำนวณความโค้งอย่างถูกต้องเส้นเลนจำเป็นต้องระบุ แต่ยิ่งไปกว่านั้นภาพจะต้องไม่ถูกแยกออก การแปลงภาพเป็นสิ่งจำเป็นสำหรับการสอบเทียบกล้องและสำหรับการแปลงมุมมองเพื่อให้ได้มุมมองของนกบนท้องถนน
โครงการนี้ถูกนำไปใช้ใน 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 เสมอจุดภาพแสดงถึงจุดวัตถุที่เกี่ยวข้องที่พบในภาพโดยใช้ฟังก์ชั่น findChessboardCorners ฟังก์ชั่นของ OpenCV
# 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 ใช้ในการแปลงภาพโดยใช้เมทริกซ์กล้องและค่าสัมประสิทธิ์การบิดเบือน
ผลลัพธ์ของเทคนิคการสอบเทียบกล้องสามารถมองเห็นได้เมื่อเปรียบเทียบรูปภาพเหล่านี้ ในขณะที่อยู่บนกระดานหมากรุกการบิดเบือนชัดเจนขึ้นบนภาพถนนมันบอบบางมากขึ้น อย่างไรก็ตามภาพที่ไม่ได้แยกจะนำไปสู่การคำนวณความโค้งของถนนที่ไม่ถูกต้อง
เพื่อความโค้งของ Calucluate มุมมองในอุดมคติคือมุมมองของนก ซึ่งหมายความว่าถนนถูกรับรู้จากด้านบนแทนที่จะเป็นมุมผ่านกระจกหน้ารถของยานพาหนะ
การแปลงมุมมองนี้คำนวณโดยใช้สถานการณ์ทางเลนตรงและความรู้ทั่วไปก่อนหน้านี้ว่าเส้นเลนนั้นขนานกัน จุดแหล่งที่มาและปลายทางจะถูกระบุโดยตรงจากภาพสำหรับการแปลงมุมมอง
#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)
วัตถุประสงค์คือการประมวลผลภาพในลักษณะที่พิกเซลเส้นเลนได้รับการเก็บรักษาไว้และแตกต่างจากถนนได้อย่างง่ายดาย การแปลงสี่ครั้งจะถูกนำไปใช้แล้วรวมกัน
การแปลงครั้งแรกใช้ 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
การแปลงที่สองเลือกพิกเซลสีขาวในภาพที่ปรับสัดส่วนสีเทา สีขาวถูกกำหนดโดยค่าระหว่าง 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
การแปลงครั้งที่สามอยู่ในองค์ประกอบความอิ่มตัวโดยใช้ HLS Colorspace นี่เป็นสิ่งสำคัญอย่างยิ่งในการตรวจจับเส้นสีเหลืองบนถนนคอนกรีตแสง
# 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
การเปลี่ยนแปลงครั้งที่สี่อยู่ในองค์ประกอบของสีที่มีค่าตั้งแต่ 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 จะถูกส่งคืนจากฟังก์ชั่นและหลังจากนั้นพหุนามระดับที่สองจะถูกติดตั้งในแต่ละด้านซ้ายและขวาเพื่อค้นหาเส้นที่ดีที่สุดของพิกเซลที่เลือก
# Fit a second order polynomial to each with np.polyfit() ###
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 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]
การค้นหาจะส่งกลับพิกัดพิกเซลที่ lefty leftx , rightx , พิกเซล righty ซึ่งติดตั้งฟังก์ชั่นพหุนามระดับที่สองสำหรับแต่ละด้านซ้ายและขวา
ในการคำนวณรัศมีและตำแหน่งของยานพาหนะบนถนนเป็นเมตรจำเป็นต้องมีปัจจัยการปรับขนาดเพื่อแปลงจากพิกเซล ค่าการปรับขนาดที่สอดคล้องกันคือ 30 เมตรถึง 720 พิกเซลในทิศทาง y และ 3.7 เมตรถึง 700 พิกเซลในมิติ x
พอดีพหุนามใช้ในการแปลง การใช้พิกัด 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 ที่ด้านล่างของภาพ ในการคำนวณตำแหน่งของยานพาหนะพหุนามพอดีในพิกเซลจะใช้เพื่อกำหนดตำแหน่ง x ของเลนซ้ายและขวาที่สอดคล้องกับ Y ที่ด้านล่างของภาพ
# 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
ค่าเฉลี่ยของค่าทั้งสองนี้ให้ตำแหน่งของศูนย์กลางของเลนในภาพ หากศูนย์กลางของเลนถูกเลื่อนไปทางขวาด้วยพิกเซลจำนวน nbp ซึ่งหมายความว่ารถจะถูกเลื่อนไปทางซ้ายโดย nbp * xm_per_pix meters สิ่งนี้ขึ้นอยู่กับสมมติฐานที่ว่ากล้องติดตั้งอยู่บนแกนกลางของยานพาหนะ