Computer -Vision -Algorithmus zur Berechnung der Straßenkrümmung und des Spurfahrzeugversatzes mithilfe von OpenCV -Bildverarbeitung, Kamera -Kalibrierung, Perspektiventransformation, Farbmasken, Schluchzen und Polynomanpassungen.
Das Advanced Lane -Feststellungsprojekt ist einen Schritt weiter von der Erkennung von [Spurlinien] von der Identifizierung der Geometrie der Straße vor uns.
Mit einer Videoaufzeichnung von Autobahnfahrten ist es das Ziel dieses Projekts, den Radius der Krümmung der Straße zu berechnen. Gekrümmte Straßen sind eine schwierigere Aufgabe als gerade. Um die Krümmung korrekt zu berechnen, müssen die Fahrspurleitungen identifiziert werden, aber darüber hinaus müssen die Bilder ungelegt sein. Die Bildumwandlung ist für die Kamerakalibrierung und für die Perspektive -Transformation erforderlich, um einen Blick auf die Straße zu erhalten.
Dieses Projekt wird in Python implementiert und verwendet die OpenCV -Bildverarbeitungsbibliothek. Der Quellcode finden Sie in der AdvancedLaneFinding.ipynb
[Optische Verzerrung] ist ein physikalisches Phänomen, das bei der Bildaufzeichnung auftritt, bei der gerade Linien als leicht gekrümmte projiziert werden, wenn sie durch Kameraobjektive wahrgenommen werden. Das Autobahn -Fahrvideo wird mit der vorderen Kamera auf dem Auto aufgenommen und die Bilder werden verzerrt. Die Verzerrungskoeffizienten sind für jede Kamera spezifisch und können mit bekannten geometrischen Formen berechnet werden.
Mit der eingebettete Kamera aufgenommene Schachbrettbilder finden Sie im Ordner camera_cal . Der Vorteil dieser Bilder besteht darin, dass sie einen hohen Kontrast und eine bekannte Geometrie haben. Die Bilder lieferten 9 * 6 Ecken für die Arbeit.
# 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)
Objektpunkte werden basierend auf dem gemeinsamen Verständnis festgelegt, dass in einem Schachbrettmuster alle Quadrate gleich sind. Dies impliziert, dass Objektpunkte X- und Y -Koordinaten aus Gitterindizes erzeugen und Z immer 0 sind. Die Bildpunkte repräsentieren die entsprechenden Objektpunkte, die im Bild unter Verwendung von OpenCV findChessboardCorners im Bild gefunden wurden.
# 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)
Nach dem Durchsuchen aller Bilder enthält die Bildpunktliste genügend Daten, um mit den Objektpunkten zu vergleichen, um Kameratrix- und Verzerrungskoeffizienten zu berechnen. Dies führt zu einer genauen Kamera -Matrix- und Verzerrungskoeffizientenidentifikation unter Verwendung der Funktion von Calibratecamera.
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
undist = cv2.undistort(img, mtx, dist, None, mtx)
Die OpenCV undistort Funktion wird verwendet, um die Bilder mithilfe der Kameratrix- und Verzerrungskoeffizienten zu transformieren.
Das Ergebnis der Kamera -Kalibrierungstechnik ist beim Vergleich dieser Bilder sichtbar. Während auf dem Schachbrettbild ist die Verzerrung offensichtlicher, auf dem Straßenbild subtiler. Trotzdem würde ein unverzündetes Bild zu einer falschen Berechnung der Straßenkrümmung führen.
Um die Krümmung zu kalukluieren, ist die ideale Perspektive eine Vogelperspektive. Dies bedeutet, dass die Straße von oben anstatt in einem Winkel durch die Windschutzscheibe des Fahrzeugs wahrgenommen wird.
Diese Perspektive -Transformation wird unter Verwendung eines geraden Spurszenarios und früheren Kenntnissen berechnet, dass die Fahrspurlinien tatsächlich parallel sind. Quell- und Zielpunkte werden direkt aus dem Bild für die Perspektive -Transformation identifiziert.
#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 bietet Perspektive -Transformationsfunktionen zur Berechnung der Transformationsmatrix für die Bilder mit Quell- und Zielpunkten. Mit der warpPerspective wird die Perspektive -Transformation der Vogelperspektive durchgeführt.
# 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)
Ziel ist es, das Bild so zu verarbeiten, dass die Pixel der Spurlinie erhalten bleiben und sich leicht von der Straße unterscheiden. Vier Transformationen werden angewendet und dann kombiniert.
Die erste Transformation nimmt das x sobel auf das grauskalierte Bild. Dies repräsentiert das Derivat in X -Richtung und hilft dabei, Linien zu erkennen, die tendenziell vertikal sind. Es werden nur die Werte über einer minimalen Schwelle aufbewahrt.
# 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
Die zweite Transformation wählt die weißen Pixel im grau -skalierten Bild aus. Weiß wird durch Werte zwischen 200 und 255 definiert, die unter Verwendung von Versuch und Irrtum auf den angegebenen Bildern ausgewählt wurden.
# 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
Die dritte Transformation befindet sich in der Sättigungskomponente unter Verwendung des HLS -Farbspace. Dies ist besonders wichtig, um gelbe Linien auf der leichten Betonstraße zu erkennen.
# 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
Die vierte Transformation befindet sich auf der Farbtonkomponente mit Werten von 10 bis 25, die als gelb identifiziert wurden.
hue_binary = np.zeros_like(H)
# Detect pixels that are yellow using the hue component
hue_binary[(H > 10) & (H <= 25)] = 1
Die Erkennung von Spurleitungen erfolgt an binären Schwellenwertbildern, die bereits ungelebter und verzerrt wurden. Zunächst wird ein Histogramm auf dem Bild berechnet. Dies bedeutet, dass die Pixelwerte in jeder Spalte summiert werden, um die wahrscheinlichste x -Position der linken und rechten Spurlinien zu erkennen.
# 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
Beginnend mit diesen Basispositionen am unteren Rand des Bildes wird die Verschiebungsfenstermethode angewendet und sucht nach Zeilenpixeln. Lane -Pixel werden berücksichtigt, wenn die X- und Y -Koordinaten innerhalb des vom Fenster definierten Bereichs liegen. Wenn genügend Pixel festgestellt werden, um sicher zu sein, dass sie Teil einer Linie sind, wird ihre durchschnittliche Position berechnet und als Ausgangspunkt für das nächste Aufwärtsfenster aufbewahrt.
# 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)
Alle diese Pixel werden in einer Liste ihrer X- und Y -Koordinaten zusammengestellt. Dies geschieht symmetrisch auf beiden Fahrspurleitungen. leftx , lefty , rightx , righty Pixel-Positionen werden von der Funktion zurückgegeben, und danach wird auf jeder linken und rechten Seite ein Polynom zweiten Grades angepasst, um die beste Linienanpassung der ausgewählten Pixel zu finden.
# Fit a second order polynomial to each with np.polyfit() ###
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
Hier sind die identifizierten Pixel der linken und rechten Linie rot und blau markiert. Das Polynom zweite Grades wird auf dem resultierenden Bild verfolgt.
Um die Spurleitungssuche von einem Videoband zum nächsten zu beschleunigen, werden Informationen aus dem vorherigen Zyklus verwendet. Es ist wahrscheinlicher, dass das nächste Bild Fahrspurlinien in der Nähe der vorherigen Fahrspurleitungen hat. Hier werden die Polynomanpassung für die linke Linie und die rechte Linie des vorherigen Bildes verwendet, um den Suchbereich zu definieren.
Die Schiebefenstermethode wird weiterhin verwendet, aber anstatt mit den Spitzenpunkten des Histogramms zu beginnen, wird die Suche entlang der vorherigen Zeilen mit einem bestimmten Rand für die Breite des Fensters durchgeführt.
# 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]
Die Suche gibt leftx , lefty , rightx , righty Pixelkoordinaten zurück, die für jede linke und rechte Seite mit einer Polynomfunktion zweiten Grades ausgestattet sind.
Um den Radius und die Position des Fahrzeugs auf der Straße in Metern zu berechnen, sind Skalierungsfaktoren erforderlich, um aus Pixeln umzuwandeln. Die entsprechenden Skalierungswerte betragen 30 Meter bis 720 Pixel in Y -Richtung und 3,7 Meter bis 700 Pixel in der x -Dimension.
Eine Polynomanpassung wird verwendet, um die Umwandlung vorzunehmen. Unter Verwendung der X -Koordinaten der ausgerichteten Pixel aus der angepassten Linie jeder rechten und linken Spurlinie werden die Umrechnungsfaktoren angewendet und die Polynomanpassung wird auf jedem Fall durchgeführt.
# 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])
Der Radius der Krümmung wird unter Verwendung des y -Punkts am Boden des Bildes berechnet. Um die Position des Fahrzeugs zu berechnen, wird die Polynomanpassung in Pixel verwendet, um die x -Position der linken und rechten Spur zu bestimmen, die dem Y am unteren Rand des Bildes entspricht.
# 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
Der Durchschnitt dieser beiden Werte gibt die Position der Spurmitte im Bild an. Wenn das Zentrum der Lane durch nbp -Anzahl von Pixeln nach rechts verschoben wird, bedeutet dies, dass das Auto von nbp * xm_per_pix meters nach links verschoben wird. Dies basiert auf der Annahme, dass die Kamera auf der zentralen Achse des Fahrzeugs montiert ist.