Beispiel #1
0
class FindLane(object):
    def __init__(self):

        self.image_shape = [0, 0]

        self.camera_calibration_path = '../camera_cal/'
        self.output_images_path = '../output_images/'
        self.input_video_path = '../input_video/'
        self.output_video_path = '../output_video/'

        self.sobel_kernel_size = 7
        self.sx_thresh = (60, 255)
        self.sy_thresh = (60, 150)
        self.s_thresh = (170, 255)
        self.mag_thresh = (40, 255)
        self.dir_thresh = (.65, 1.05)

        self.wrap_src = np.float32([[595, 450], [686, 450], [1102, 719], [206, 719]])
        self.wrap_dst = np.float32([[320, 0], [980, 0], [980, 719], [320, 719]])

        self.mask_offset = 30
        self.vertices = [np.array([[206-self.mask_offset, 719],
                                   [595-self.mask_offset, 460-self.mask_offset],
                                   [686+self.mask_offset, 460-self.mask_offset],
                                   [1102+self.mask_offset, 719]],
                                  dtype=np.int32)]

        self.mask_offset_inverse = 30
        self.vertices_inverse = [np.array([[206+self.mask_offset_inverse, 719],
                                   [595+self.mask_offset_inverse, 460-self.mask_offset_inverse],
                                   [686-self.mask_offset_inverse, 460-self.mask_offset_inverse],
                                   [1102-self.mask_offset_inverse, 719]],
                                  dtype=np.int32)]

        self.thresh = Threshold()
        self.lane = Lane()

    def warp(self, img, visualize=False):
        img_size = (img.shape[1], img.shape[0])

        perspective_M = cv2.getPerspectiveTransform(self.wrap_src, self.wrap_dst)

        # warped
        top_down = cv2.warpPerspective(img, perspective_M, img_size, flags=cv2.INTER_LINEAR)

        top_down[:, 0:230] = 0
        top_down[:, top_down.shape[1] - 100:top_down.shape[1]] = 0

        if visualize:
            f, (ax1, ax2, ax3) = plt.subplots(3, sharex=True, figsize=(24, 9))
            f.tight_layout()
            ax1.imshow(img, cmap='gray')

            # Create a Polygon patch
            rect_src = patches.Polygon(self.wrap_src, fill=False, edgecolor='r', linestyle='solid', linewidth=2.0)
            # Add the patch to the Axes
            ax1.add_patch(rect_src)

            ax1.set_title('Thresholded Image', fontsize=10)
            ax2.imshow(top_down, cmap='gray')

            # Create a Polygon patch
            rect_dst = patches.Polygon(self.wrap_dst, fill=False, edgecolor='r', linestyle='solid', linewidth=2.0)
            # Add the patch to the Axes
            ax2.add_patch(rect_dst)

            ax2.set_title('Warped Image', fontsize=10)
            histogram = np.sum(top_down[top_down.shape[0] // 2:, :], axis=0)
            ax3.plot(histogram)
            ax3.set_title('Histogram', fontsize=10)

        return top_down


    def region_of_interest(self, img, vertices):
        """
        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.
        """
        # defining a blank mask to start with
        mask = np.zeros_like(img)

        # defining a 3 channel or 1 channel color to fill the mask with depending on the input image
        if len(img.shape) > 2:
            channel_count = img.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(img, mask)
        return masked_image

    def region_of_interest_inverse(self, img, vertices):
        """
        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.
        """
        # defining a blank mask to start with
        mask = img.copy()
        mask.fill(255)
        # mask = np.zeros_like(img)

        # defining a 3 channel or 1 channel color to fill the mask with depending on the input image
        if len(img.shape) > 2:
            channel_count = img.shape[2]  # i.e. 3 or 4 depending on your image
            ignore_mask_color = (0,) * channel_count
        else:
            ignore_mask_color = 0

        # filling pixels inside the polygon defined by "vertices" with the fill color
        cv2.fillPoly(mask, vertices, ignore_mask_color)

        # plt.imshow(img, cmap='gray')
        # plt.imshow(mask, cmap='gray')

        # returning the image only where mask pixels are nonzero
        masked_image = cv2.bitwise_and(img, mask)

        # plt.imshow(masked_image, cmap='gray')

        return masked_image

    # The pipeline.
    def pipeline(self, img, sobel_kernel_size=3, sx_thresh=(20, 100), sy_thresh=(20, 100), s_thresh=(170, 255),
                 mag_thresh=(10, 255), dir_thresh=(0, 1), test_image_pipeline=False, visualize=False):

        # Perform Gaussian Blur
        kernel_size = 5
        img = cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)

        gray, gradx, grady = self.thresh.sobel_thresh(img, sx_thresh, sy_thresh, sobel_kernel_size)

        mag_binary = self.thresh.mag_thresh(gray, sobel_kernel=sobel_kernel_size, mag_thresh=mag_thresh)
        dir_binary = self.thresh.dir_threshold(gray, sobel_kernel=sobel_kernel_size, thresh=dir_thresh)

        sobel_combined = np.zeros_like(dir_binary)
        sobel_combined[((gradx == 1) & (grady == 1)) | ((mag_binary == 1) & (dir_binary == 1))] = 1

        color_thresh = self.thresh.color_thresh(img, s_thresh)

        thresholded_binary = np.zeros_like(sobel_combined)
        thresholded_binary[(color_thresh > 0) | (sobel_combined > 0)] = 1

        # Masked area
        thresholded_binary = self.region_of_interest(thresholded_binary, self.vertices)
        thresholded_binary[0:450, :] = 0

        # thresholded_binary = self.region_of_interest_inverse(thresholded_binary, self.vertices_inverse)

        warped = self.warp(thresholded_binary, visualize)
        out_img, ploty, left_fitx, right_fitx = self.lane.find(warped, test_image_pipeline)

        return thresholded_binary, warped, out_img, ploty, left_fitx, right_fitx

    def calculate_position(self, pts):

        # Find the position of the car from the center
        # It will show if the car is 'x' meters from the left or right
        position = self.image_shape[1] / 2

        try:
            left = np.min(pts[(pts[:, 1] < position) & (pts[:, 0] > 700)][:, 1])
            right = np.max(pts[(pts[:, 1] > position) & (pts[:, 0] > 700)][:, 1])
            center = (left + right) / 2
            # Define conversions in x and y from pixels space to meters
            xm_per_pix = 3.7 / 700  # meters per pixel in x dimension
            return (position - center), (position - center) * xm_per_pix
        except:
            return 0, 0

    def calculate_curvatures(self, ploty, left_fitx, right_fitx):
        y_eval = np.max(ploty)

        ym_per_pix = 30 / 720  # meters per pixel in y dimension
        xm_per_pix = 3.7 / 700  # meters per pixel in x dimension

        # Fit new polynomials to x,y in world space
        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)

        # Calculate the new 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])

        # Now our radius of curvature is in meters
        return left_curverad, right_curverad

    def plot_dashboard(self, image, ploty, left_fitx, right_fitx, newwarp):
        left_curverad, right_curverad = self.calculate_curvatures(ploty, left_fitx, right_fitx)

        self.lane.left_line.radius_of_curvature = left_curverad
        self.lane.right_line.radius_of_curvature = right_curverad

        # Put text on an image
        font = cv2.FONT_HERSHEY_SIMPLEX

        text = "Radius of Left Line Curvature: {} m".format(int(left_curverad))
        cv2.putText(image, text, (100, 50), font, 1, (255, 255, 255), 2)

        text = "Radius of Right Line Curvature: {} m".format(int(right_curverad))
        cv2.putText(image, text, (100, 100), font, 1, (255, 255, 255), 2)

        # Find the position of the car
        pts = np.argwhere(newwarp[:, :, 1])
        position_pixels, position_meters = self.calculate_position(pts)

        if position_meters < 0:
            text = "Vehicle is {:.2f} m left of center".format(-position_meters)
            self.lane.left_line.line_base_pos = 3.7/2 - position_meters
            self.lane.right_line.line_base_pos = 3.7/2 + position_meters
        else:
            text = "Vehicle is {:.2f} m right of center".format(position_meters)
            self.lane.left_line.line_base_pos = 3.7/2 + position_meters
            self.lane.right_line.line_base_pos = 3.7/2 - position_meters

        cv2.putText(image, text, (100, 200), font, 1, (255, 255, 255), 2)

        # text = "Left diff: {}".format(self.lane.left_line.diffs)
        # cv2.putText(image, text, (100, 200), font, 1, (255, 255, 255), 2)
        #
        # text = "Right diff: {}".format(self.lane.right_line.diffs)
        # cv2.putText(image, text, (100, 250), font, 1, (255, 255, 255), 2)
        #
        # text = "Left fit: {}".format(self.lane.left_line.current_fit)
        # cv2.putText(image, text, (100, 300), font, 1, (255, 255, 255), 2)
        #
        # text = "Right fit: {}".format(self.lane.right_line.current_fit)
        # cv2.putText(image, text, (100, 350), font, 1, (255, 255, 255), 2)

        text = "Left base line pos: {} m".format(round(self.lane.left_line.line_base_pos, 4))
        cv2.putText(image, text, (100, 250), font, 1, (255, 255, 255), 2)

        text = "Right base line pos: {} m".format(round(self.lane.right_line.line_base_pos, 4))
        cv2.putText(image, text, (100, 300), font, 1, (255, 255, 255), 2)

        return image


    def project_back(self, undist, thresholded_binary, warped, ploty, left_fitx, right_fitx):
        # Create an image to draw the lines on
        warp_zero = np.zeros_like(warped).astype(np.uint8)
        color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

        # Recast the x and y points into usable format for cv2.fillPoly()
        pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
        pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
        pts = np.hstack((pts_left, pts_right))

        # Draw the lane onto the warped blank image
        cv2.fillPoly(color_warp, np.int_([pts]), (0, 255, 0))

        Minv = cv2.getPerspectiveTransform(self.wrap_dst, self.wrap_src)

        # Warp the blank back to original image space using inverse perspective matrix (Minv)
        newwarp = cv2.warpPerspective(color_warp, Minv, (thresholded_binary.shape[1], thresholded_binary.shape[0]))

        # Combine the result with the original image
        result = cv2.addWeighted(undist, 1, newwarp, 0.3, 0)
        # print('result shape', result.shape)
        # plt.imshow(result)

        return result, newwarp

    def execute_image_pipeline(self, visualize=False):
        images = glob.glob(self.output_images_path + 'undist_*.jpg')

        for idx, fname in enumerate(images):
            image_fname = ntpath.basename(fname)

            print("Processing", fname)

            image = mpimg.imread(self.output_images_path + image_fname)

            self.image_shape = image.shape

            thresholded_binary, warped, out_img, ploty, left_fitx, right_fitx = \
                self.pipeline(
                    image,
                    sobel_kernel_size=self.sobel_kernel_size,
                    sx_thresh=self.sx_thresh,
                    sy_thresh=self.sy_thresh,
                    s_thresh=self.s_thresh,
                    mag_thresh=self.mag_thresh,
                    dir_thresh=self.dir_thresh,
                    test_image_pipeline=True,
                    visualize=True)

            result, newwarp = self.project_back(image, thresholded_binary, warped, ploty, left_fitx, right_fitx)

            result = self.plot_dashboard(result, ploty, left_fitx, right_fitx, newwarp)

            mpimg.imsave(self.output_images_path + 'thres_' + image_fname, thresholded_binary, cmap=cm.gray)
            mpimg.imsave(self.output_images_path + 'warp_' + 'thres_' + image_fname, warped, cmap=cm.gray)
            mpimg.imsave(self.output_images_path + 'out_' + 'warp_' + 'thres_' + image_fname, out_img)
            mpimg.imsave(self.output_images_path + 'result_' + 'out_' + 'warp_' + 'thres_' + image_fname, result)

            if visualize:
                # Plot the result
                f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
                f.tight_layout()

                ax1.imshow(image)
                ax1.set_title('Undistorted Image', fontsize=30)

                ax2.imshow(thresholded_binary, cmap='gray')
                ax2.set_title('Thresholded Result', fontsize=30)
                plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)

                ax2.imshow(warped, cmap='gray')
                ax2.set_title('Warped Result', fontsize=30)
                plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)

                ax2.imshow(out_img, cmap='gray')
                ax2.set_title('Line fit', fontsize=30)
                ax2.plot(left_fitx, ploty, color='yellow')
                ax2.plot(right_fitx, ploty, color='yellow')

                plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)

                ax1.imshow(result)
                ax1.set_title('Output Image', fontsize=30)

    def execute_video_pipeline(self, image):

        handle = open(self.camera_calibration_path + "wide_dist_pickle.p", 'rb')
        dist_pickle = pickle.load(handle)

        undist_image = cv2.undistort(image, dist_pickle["mtx"], dist_pickle["dist"], None, dist_pickle["mtx"])

        self.image_shape = undist_image.shape

        # thresholded_binary, warped, out_img, ploty, left_fitx, right_fitx = self.pipeline(undist_image)

        thresholded_binary, warped, out_img, ploty, left_fitx, right_fitx = \
            self.pipeline(
                undist_image,
                sobel_kernel_size=self.sobel_kernel_size,
                sx_thresh=self.sx_thresh,
                sy_thresh=self.sy_thresh,
                s_thresh=self.s_thresh,
                mag_thresh=self.mag_thresh,
                dir_thresh=self.dir_thresh,
                test_image_pipeline=False,
                visualize=False)

        result, newwarp = self.project_back(image, thresholded_binary, warped, ploty, left_fitx, right_fitx)

        return self.plot_dashboard(result, ploty, left_fitx, right_fitx, newwarp)

    def project_video(self):
        # Execute the pipeline for the video file
        in_project_video = self.input_video_path + 'project_video.mp4'

        # project_clip = VideoFileClip(in_project_video).subclip(20, 26)
        # project_clip = VideoFileClip(in_project_video).subclip(35, 42)
        # project_clip = VideoFileClip(in_project_video).subclip(35, 45)
        # project_clip = VideoFileClip(in_project_video).subclip(41, 45)
        # project_clip = VideoFileClip(in_project_video).subclip(20, 45)
        project_clip = VideoFileClip(in_project_video)

        out_project_clip = project_clip.fl_image(self.execute_video_pipeline)  # NOTE: this function expects color images!!

        out_project_video = self.output_video_path + 'out_project_video.mp4'
        out_project_clip.write_videofile(out_project_video, audio=False)