def hough_transform(self, image): rho = 1 # distance resolution in pixels of the Hough grid theta = np.pi / 180 # angular resolution in radians of the Hough grid threshold = 50 # minimum number of votes (intersections in Hough grid cell) min_line_len = 15 #minimum number of pixels making up a line max_line_gap = 30 # maximum gap in pixels between connectable line segments hough_lines = cv2.HoughLinesP(image, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap) # save debug image color_image = np.dstack((image, image, image)) * 255 line_image = np.zeros_like(color_image) for line in hough_lines: for x1, y1, x2, y2 in line: cv2.line(line_image, (x1, y1), (x2, y2), (255, 0, 0), 10) line_image = cv2.addWeighted(color_image, 0.7, line_image, 0.7, 0) Logger.save(line_image, 'hough-lines') return hough_lines
def main(mode=None, source=None, out=None, log=False): # log properties Logger.logging = log Logger.mode = mode pipeline = Pipeline() if mode == 'test_images': images = glob.glob('test_images/*.jpg') for idx, fname in enumerate(images): Logger.source = fname pipeline.reset() image = pipeline.process(imread(fname)) elif mode == 'video': Logger.source = source source_video = VideoFileClip(source) output_video = source_video.fl_image(pipeline.process) output_video.write_videofile(out, audio=False) elif mode == 'calibrate': images = glob.glob('camera_cal/calibration*.jpg') calibration.calibrate(images) for idx, fname in enumerate(images): Logger.source = fname image = imread(fname) image = calibration.undistort(image) Logger.save(image, 'undistored')
def plot(left, right, shape): # save curvature plot fig = plt.figure(figsize=(8, 6)) plt.xlim(0, shape[1]) plt.ylim(0, shape[0]) left.plot(color='red') right.plot(color='blue') plt.gca().invert_yaxis() Logger.save(fig, 'curvature') plt.close()
def combined_thresh(image): """ Returns thresholded binary image Sobel directional, magnitude, and direction combined """ # Sobel X Threshold sobelx = abs_sobel_thresh(image, orient='x', sobel_kernel=3, thresh=thresholds.get('sobelx')) Logger.save(sobelx, 'sobelx-binary') # Saturation Threshold saturation_binary = hls_select(image, thresh=thresholds.get('saturation'), channel=2) Logger.save(saturation_binary, 'saturation-binary') # Lightness Threshold lightness_binary = hls_select(image, thresh=thresholds.get('lightness'), channel=1) Logger.save(lightness_binary, 'lightness-binary') # Combined Threshold binary_output = np.zeros(image.shape[:2], dtype='uint8') binary_output[((sobelx == 1) | (saturation_binary == 1)) & lightness_binary == 1] = 1 Logger.save(binary_output, 'combined-binary') return binary_output
def get_lane_pixels(image): histogram = get_histogram(image) # save picture of histogram fig = plt.figure(figsize=(8, 6)) plt.plot(histogram) Logger.save(fig, 'histogram') plt.close() height = image.shape[0] width = image.shape[1] midpoint = int(width / 2) slices = 30 search_window = 70 # determine starting points for seeking left_pos = np.argmax(histogram[:midpoint]) right_pos = np.argmax(histogram[midpoint:]) + midpoint # blank image to store lane line pixels left_blank = np.zeros_like(image) right_blank = np.zeros_like(image) # for debugging seek process rectangles = np.dstack((image, image, image)) for bottom in range(height, 0, -slices): top = bottom - slices image_slice = image[top:bottom, :] # draw window for debugging cv2.rectangle(rectangles, (left_pos - search_window, bottom), (left_pos + search_window, top), (0, 255, 0), 2) cv2.rectangle(rectangles, (right_pos - search_window, bottom), (right_pos + search_window, top), (0, 255, 0), 2) left_blank, left_pos = seek(left_blank, image_slice, left_pos, top, bottom, search_window) right_blank, right_pos = seek(right_blank, image_slice, right_pos, top, bottom, search_window) # debugging image both = cv2.addWeighted(left_blank, 1, right_blank, 1, 0) both = np.dstack((both, both, both)) * 255 both = cv2.addWeighted(both, 1, rectangles, 1, 0) Logger.save(both, 'lane-seek') return left_blank, right_blank
def mask_image(image): """ Applies an image mask. Only keeps the region of the image defined by the polygon formed from `vertices`. The rest of the image is set to black. """ height = image.shape[0] width = image.shape[1] bl = mask_region.get('bottom_left') tl = mask_region.get('top_left') tr = mask_region.get('top_right') br = mask_region.get('bottom_right') vertices = np.array([[(bl[0] * width, bl[1] * image.shape[0]), (tl[0] * width, tl[1] * height), (tr[0] * width, tr[1] * height), (br[0] * width, br[1] * image.shape[0])]], dtype=np.int32) #defining a blank mask to start with mask = np.zeros_like(image) #defining a 3 channel or 1 channel color to fill the mask with depending on the input image if len(image.shape) > 2: channel_count = image.shape[2] # i.e. 3 or 4 depending on your image ignore_mask_color = (255, ) * channel_count else: ignore_mask_color = 255 #filling pixels inside the polygon defined by "vertices" with the fill color cv2.fillPoly(mask, vertices, ignore_mask_color) #returning the image only where mask pixels are nonzero masked_image = cv2.bitwise_and(image, mask) Logger.save(masked_image, 'masked-image') return masked_image
def process(self, image): Logger.save(image, 'original') # Apply the distortion correction to the raw image. image = calibration.undistort(image) undistorted_image = copy.copy(image) Logger.save(undistorted_image, 'undistorted') # Use color transforms, gradients, etc., to create a thresholded binary image. thresholded_image = threshold.combined_thresh(image) masked_image = mask.mask_image(thresholded_image) # Apply a perspective transform to rectify binary image ("birds-eye view"). src, dest = self.perspective.get_transform_points(masked_image) M, Minv = self.perspective.get_matrix( src, dest), self.perspective.get_matrix(dest, src) image_src = self.perspective.draw_perspective_lines( undistorted_image, src) Logger.save(image_src, 'perspective-transform-src') image_dest = self.perspective.transform(undistorted_image, M) image_dest = self.perspective.draw_perspective_lines(image_dest, dest) Logger.save(image_dest, 'perspective-transform-dest') transformed_image = self.perspective.transform(thresholded_image, M) Logger.save(transformed_image, 'perspective-transform-binary') # Detect lane pixels and fit to find lane boundary. left_pixels, right_pixels = lane_finder.get_lane_pixels( transformed_image) self.left.fit(left_pixels) self.right.fit(right_pixels) lane_finder.plot(self.left, self.right, transformed_image.shape) lane_boundaries = self.overlay.draw(transformed_image, self.left, self.right) # Warp the detected lane boundaries back onto the original image. # Combine the result with the original image lane_boundaries = self.perspective.transform(lane_boundaries, Minv) image = cv2.addWeighted(undistorted_image, 1, lane_boundaries, 0.3, 0) # Determine curvature of the lane and vehicle position with respect to center. # Output visual display of the lane boundaries and numerical estimation of lane curvature and vehicle position. image = self.overlay.stats(image, self.left, self.right) Logger.save(image, 'final') Logger.increment() return image