Algorithme de vision par ordinateur pour calculer la courbure routière et le décalage du véhicule de voie à l'aide du traitement d'image OpenCV, de l'étalonnage de la caméra, de la transformation de la perspective, des masques de couleur, des sanglots et de l'ajustement polynomial.
Le projet avancé de recherche de voie est un pas plus loin de [la détection des lignes de voie] pour identifier la géométrie de la route à venir.
En utilisant un enregistrement vidéo de l'autoroute, l'objectif de ce projet est de calculer le rayon de la courbure de la route. Les routes incurvées sont une tâche plus difficile que les droites. Pour calculer correctement la courbure, les lignes de voie doivent être identifiées, mais en plus de cela, les images doivent être non déformées. La transformation de l'image est nécessaire pour l'étalonnage de la caméra et pour la transformation de la perspective pour obtenir une vue sur les yeux de la route.
Ce projet est implémenté dans Python et utilise la bibliothèque de traitement d'image OpenCV. Le code source peut être trouvé dans AdvancedLaneFinding.ipynb
[La distorsion optique] est un phénomène physique qui se produit dans l'enregistrement d'images, dans lequel les lignes droites sont projetées comme légèrement incurvées lorsqu'elles sont perçues à travers des lentilles de la caméra. La vidéo de conduite routière est enregistrée à l'aide de la caméra frontale sur la voiture et les images sont déformées. Les coefficients de distorsion sont spécifiques à chaque appareil photo et peuvent être calculés à l'aide de formes géométriques connues.
Les images d'échecs capturées avec la caméra intégrée sont fournies dans le dossier camera_cal . L'avantage de ces images est qu'ils ont un contraste élevé et une géométrie connue. Les images fournies présentent 9 * 6 coins avec lesquels travailler.
# 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)
Les points d'objet sont définis en fonction de la compréhension commune que dans un modèle d'échecs, tous les carrés sont égaux. Cela implique que les points d'objet auront des coordonnées x et y générées à partir des index de grille, et Z est toujours 0. Les points d'image représentent les points d'objet correspondants trouvés dans l'image en utilisant la fonction d'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)
Après avoir parcouru toutes les images, la liste des points d'image a suffisamment de données pour comparer les points d'objet afin de calculer la matrice de la caméra et les coefficients de distorsion. Cela conduit à une matrice de caméra précise et à l'identification des coefficients de distorsion à l'aide de la fonction «calibratecamera».
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
undist = cv2.undistort(img, mtx, dist, None, mtx)
La fonction OpenCV undistort est utilisée pour transformer les images à l'aide de la matrice de la caméra et des coefficients de distorsion.
Le résultat de la technique d'étalonnage de la caméra est visible lors de la comparaison de ces images. Alors que sur l'image d'échecs, la distorsion est plus évidente, sur l'image de la route, elle est plus subtile. Néanmoins, une image indésirable conduirait à un calcul incorrect de courbure de la route.
Pour cuire la courbure, la perspective idéale est une vue sur les yeux d'oiseau. Cela signifie que la route est perçue d'en haut, plutôt qu'à un angle à travers le pare-brise du véhicule.
Cette transformation de perspective est calculée à l'aide d'un scénario de voie droite et de notoriété publique préalable que les lignes de voie sont en fait parallèles. Les points de source et de destination sont identifiés directement à partir de l'image pour la transformation de perspective.
#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 fournit des fonctions de transformation de perspective pour calculer la matrice de transformation pour les images compte tenu des points source et de destination. En utilisant la fonction warpPerspective , la transformation de la perspective de la vue des yeux de l'oiseau est effectuée.
# 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)
L'objectif est de traiter l'image de telle manière que les pixels de ligne de voie sont conservés et facilement différenciés de la route. Quatre transformations sont appliquées puis combinées.
La première transformation prend le x sobel sur l'image à l'échelle gris. Cela représente la dérivée dans la direction x et aide à détecter les lignes qui ont tendance à être verticales. Seules les valeurs supérieures à un seuil minimum sont conservées.
# 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
La deuxième transformation sélectionne les pixels blancs dans l'image à l'échelle gris. Le blanc est défini par des valeurs comprises entre 200 et 255 qui ont été choisies en utilisant des essais et des erreurs sur les images données.
# 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
La troisième transformation est sur le composant de saturation en utilisant l'espace couleurs HLS. Ceci est particulièrement important pour détecter les lignes jaunes sur la route en béton léger.
# 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
La quatrième transformation est sur le composant Hue avec des valeurs de 10 à 25, qui ont été identifiées comme correspondant au jaune.
hue_binary = np.zeros_like(H)
# Detect pixels that are yellow using the hue component
hue_binary[(H > 10) & (H <= 25)] = 1
La détection de la ligne de voie est effectuée sur des images binaires seuils qui ont déjà été non déformées et déformées. Initialement, un histogramme est calculé sur l'image. Cela signifie que les valeurs de pixels sont additionnées sur chaque colonne pour détecter la position x la plus probable des lignes de voie gauche et droite.
# 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
En commençant par ces positions de base en bas de l'image, la méthode de la fenêtre coulissante est appliquée à la recherche de pixels de ligne. Les pixels de voie sont pris en compte lorsque les coordonnées x et y se trouvent dans la zone définie par la fenêtre. Lorsque suffisamment de pixels sont détectés pour être convaincus qu'ils font partie d'une ligne, leur position moyenne est calculée et conservée comme point de départ pour la prochaine fenêtre ascendante.
# 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)
Tous ces pixels sont assemblés dans une liste de leurs coordonnées X et Y. Cela se fait symétriquement sur les deux lignes de voie. Les positions de pixels leftx , lefty , rightx , righty sont renvoyées de la fonction et ensuite, un polynôme au deuxième degré est installé sur chaque côté gauche et droit pour trouver le meilleur ajustement de ligne des pixels sélectionnés.
# Fit a second order polynomial to each with np.polyfit() ###
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
Ici, les pixels de ligne gauche et droite identifiés sont marqués respectivement en rouge et en bleu. Le polynôme au deuxième degré est tracé sur l'image résultante.
Pour accélérer la recherche de ligne de voie d'un cadre vidéo à la suivante, les informations du cycle précédent sont utilisées. Il est plus probable que la prochaine image aura des lignes de voie à proximité des lignes de voie précédente. C'est là que l'ajustement polynomial pour la ligne gauche et la ligne droite de l'image précédente sont utilisés pour définir la zone de recherche.
La méthode de fenêtre coulissante est toujours utilisée, mais au lieu de commencer par les points de pic de l'histogramme, la recherche est effectuée le long des lignes précédentes avec une marge donnée pour la largeur de la fenêtre.
# 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]
La recherche renvoie les coordonnées leftx , lefty , rightx , righty Pixels qui sont équipées d'une fonction polynomiale au deuxième degré pour chaque côté gauche et droit.
Pour calculer le rayon et la position du véhicule sur la route en mètres, des facteurs de mise à l'échelle sont nécessaires pour se convertir à partir de pixels. Les valeurs de mise à l'échelle correspondantes sont de 30 mètres à 720 pixels dans la direction Y et de 3,7 mètres à 700 pixels dans la dimension x.
Un ajustement polynomial est utilisé pour effectuer la conversion. En utilisant les coordonnées X des pixels alignés de la ligne ajustée de chaque ligne de voie droite et gauche, les facteurs de conversion sont appliqués et l'ajustement polynomial est effectué sur chacun.
# 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])
Le rayon de la courbure est calculé en utilisant le point y au bas de l'image. Pour calculer la position du véhicule, l'ajustement polynomial dans les pixels est utilisé pour déterminer la position x de la piste gauche et droite correspondant au Y au bas de l'image.
# 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
La moyenne de ces deux valeurs donne la position du centre de la voie dans l'image. Si le centre de la voie est décalé vers la droite par la quantité nbp de pixels, cela signifie que la voiture est décalée à gauche par nbp * xm_per_pix meters . Ceci est basé sur l'hypothèse que la caméra est montée sur l'axe central du véhicule.