예제 #1
0
파일: data.py 프로젝트: paoli7612/bus-stops
    def __init__(self, city):
        self.path = os.path.dirname(__file__)
        self.path_xml = os.path.join(self.path, "data", city + ".xml")
        self.tree = element_tree.parse(self.path_xml)
        self.root = self.tree.getroot()

        self.table = Table()
        for ss in self.root:
            id = ss.attrib["id"]
            l = Line(id)
            for stop in ss:
                l.add(stop.text)
            self.table.add(l)
예제 #2
0
class PreProcess(object):
    def __init__(self, image_thresh, minv, fun_name_list):
        """
        PreProcess line, find the lines and draw picture
        """
        self.image_thresh = image_thresh
        self.Minv = minv
        self.l_line = Line()
        self.r_line = Line()
        self.i = 0
        self.fun_names = fun_name_list

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

    @staticmethod
    def find_base(binary_image):
        # Take a histogram of the bottom half of the image
        histogram = np.sum(binary_image[binary_image.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
        return leftx_base, rightx_base

    def get_binary_image(self, warped_img, fun_name):
        """
        type is the image_thresh function's name
        """
        return getattr(self.image_thresh, fun_name)(warped_img)

    def sliding_windows(self, binary_image, nwindows=9, margin=100, minpix=50):
        """
        Fit a second order polynomial to each
        """
        # Set height of windows
        window_height = np.int(binary_image.shape[0] / nwindows)
        # Identify the x and y positions of all nonzero pixels in the image
        nonzero = binary_image.nonzero()
        nonzeroy = np.array(nonzero[0])
        nonzerox = np.array(nonzero[1])
        # Current positions to be update for each window
        leftx_current, rightx_current = self.find_base(binary_image)
        # Create empty lists to receive left and right lane pixel indices
        left_lane_inds = []
        right_lane_inds = []

        # Step through the windows one by one
        for window in range(nwindows):
            # Identify window boundaries in x and y (and right and left)
            win_y_low = binary_image.shape[0] - (window + 1) * window_height
            win_y_high = binary_image.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)
            # If you found > minpix pixels, recenter next window on their mean position
            if len(good_left_inds) > minpix:
                leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
            if len(good_right_inds) > minpix:
                rightx_current = np.int(np.mean(nonzerox[good_right_inds]))
        left_lane_inds = np.concatenate(left_lane_inds)
        right_lane_inds = np.concatenate(right_lane_inds)

        # 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]

        # Fit a second order polynomial to each
        left_fit = np.polyfit(lefty, leftx, 2)
        right_fit = np.polyfit(righty, rightx, 2)

        # Fit new polynomials to x,y in world space
        left_fit_cr = np.polyfit(lefty * self.ym_per_pix,
                                 leftx * self.xm_per_pix, 2)
        right_fit_cr = np.polyfit(righty * self.ym_per_pix,
                                  rightx * self.xm_per_pix, 2)

        return locals()

    def skip_sliding_windows(self, binary_image, margin=100):
        """
        Skip the sliding windows step once you know where the lines are
        """
        nonzero = binary_image.nonzero()
        nonzeroy = np.array(nonzero[0])
        nonzerox = np.array(nonzero[1])

        left_fit_x = self.l_line.best_fit[0] * (nonzeroy ** 2) + self.l_line.best_fit[1] * nonzeroy + \
                     self.l_line.best_fit[2]
        right_fit_x = self.r_line.best_fit[0] * (nonzeroy ** 2) + self.r_line.best_fit[1] * nonzeroy + \
                      self.r_line.best_fit[2]

        left_lane_inds = ((nonzerox > left_fit_x - margin) &
                          (nonzerox < left_fit_x + margin))
        right_lane_inds = ((nonzerox > right_fit_x - margin) &
                           (nonzerox < right_fit_x + margin))

        leftx = nonzerox[left_lane_inds]
        lefty = nonzeroy[left_lane_inds]
        rightx = nonzerox[right_lane_inds]
        righty = nonzeroy[right_lane_inds]
        # Fit a second order polynomial to each Again
        left_fit = np.polyfit(lefty, leftx, 2)
        right_fit = np.polyfit(righty, rightx, 2)

        left_fit_cr = np.polyfit(lefty * self.ym_per_pix,
                                 leftx * self.xm_per_pix, 2)
        right_fit_cr = np.polyfit(righty * self.ym_per_pix,
                                  rightx * self.xm_per_pix, 2)

        return locals()

    def add_to_line(self, warped_img, fun_name, margin=100):
        try:
            self.binary_image = self.get_binary_image(warped_img, fun_name)
            if not self.l_line.detected or not self.r_line.detected:
                line_info = self.sliding_windows(self.binary_image,
                                                 margin=margin)
            else:
                line_info = self.skip_sliding_windows(self.binary_image,
                                                      margin=margin)

            left_fit = line_info.get('left_fit')
            right_fit = line_info.get('right_fit')
            left_lane_inds = line_info.get('left_lane_inds')
            right_lane_inds = line_info.get('right_lane_inds')
            left_fit_cr = line_info.get('left_fit_cr')
            right_fit_cr = line_info.get('right_fit_cr')

            if left_fit is None or right_fit is None:
                return False, ValueError('not found fit ...')
            self.l_line.add(left_fit, left_lane_inds)
            self.r_line.add(right_fit, right_lane_inds)

            self.l_line.fit_cr = left_fit_cr
            self.r_line.fit_cr = right_fit_cr
            return True, None
        except Exception as ex:
            # print(ex.args)
            return False, ex

    def calc_curv_rad_and_center_dist(self, warped_img, margin=100):
        """
        Method to determine radius of curvature and distance from lane center
        """
        # Todo: 可以在这里添加一个过滤,如果没有找到合适的路线,那就在已经找到中使用最合适的,或是使用上一下图像的
        for fun_name in self.fun_names:
            result, ex = self.add_to_line(warped_img, fun_name, margin=margin)
            if result:
                break
        else:
            print('notfound fun is: ', ex.args)
            # cv2.imwrite('output_images/test_{}.jpg'.format(self.i), warped_img)
            self.i += 1
            self.l_line.current_fit = []
            self.r_line.current_fit = []
            # raise ex

        l_best_fit = self.l_line.best_fit
        r_best_fit = self.r_line.best_fit
        left_fit_cr = self.l_line.fit_cr
        right_fit_cr = self.r_line.fit_cr
        ploty = np.linspace(0, self.binary_image.shape[0] - 1,
                            self.binary_image.shape[0])
        left_fitx = l_best_fit[0] * ploty**2 + l_best_fit[
            1] * ploty + l_best_fit[2]
        right_fitx = r_best_fit[0] * ploty**2 + r_best_fit[
            1] * ploty + r_best_fit[2]

        # Calculate the new radii of curvature
        y_eval = np.max(ploty)
        left_curverad = (
            (1 + (2 * left_fit_cr[0] * y_eval * self.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 * self.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
        self.l_line.radius_of_curvature = (left_curverad + right_curverad) / 2
        self.r_line.radius_of_curvature = (left_curverad + right_curverad) / 2

        # Calculating the distance between the vehicle distance and the center of the road, > 0 is right else left
        pix_center = self.binary_image.shape[1] / 2 - (left_fitx[-1] +
                                                       right_fitx[-1]) / 2
        m_center = pix_center * self.xm_per_pix

        self.l_line.line_base_pos = m_center
        self.r_line.line_base_pos = m_center

    def draw_lane(self, src_image):
        new_img = np.copy(src_image)
        l_fit = self.l_line.best_fit
        r_fit = self.r_line.best_fit

        if l_fit is None or r_fit is None:
            return src_image
        h, w = src_image.shape[0], src_image.shape[1]
        # Create an image to draw the lines on
        warp_zero = np.zeros((h, w), dtype=np.uint8)
        color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

        ploty = np.linspace(0, h - 1, num=h)  # to cover same y-range as image
        left_fitx = l_fit[0] * ploty**2 + l_fit[1] * ploty + l_fit[2]
        right_fitx = r_fit[0] * ploty**2 + r_fit[1] * ploty + r_fit[2]

        # 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, 180, 0))
        cv2.polylines(color_warp,
                      np.int32([pts_left]),
                      isClosed=False,
                      color=(255, 0, 0),
                      thickness=15)
        cv2.polylines(color_warp,
                      np.int32([pts_right]),
                      isClosed=False,
                      color=(0, 0, 255),
                      thickness=15)

        # Warp the blank back to original image space using inverse perspective matrix (Minv)
        newwarp = cv2.warpPerspective(color_warp, self.Minv, (w, h))
        # Combine the result with the original image
        result = cv2.addWeighted(new_img, 1, newwarp, 1, 0)
        return result

    def draw_small(self, src_image, warped_img):
        small_shape = (640, 300)
        small_binary = cv2.resize(self.binary_image, small_shape)
        small_binary_image = np.dstack(
            (small_binary, small_binary, small_binary)) * 255
        small_warp_image = cv2.resize(warped_img, small_shape)
        hmerge = np.hstack((small_warp_image, small_binary_image))
        background = self.draw_lane(src_image)
        background[:300, :, :] = hmerge * 0.8
        return background

    def draw_data(self, src_image, warped_img):
        self.calc_curv_rad_and_center_dist(warped_img)
        new_img = np.copy(self.draw_small(src_image, warped_img))
        font = cv2.FONT_HERSHEY_DUPLEX
        text = 'Curve radius: ' + '{:04.2f}.'.format(
            self.l_line.radius_of_curvature) + 'm'
        cv2.putText(new_img, text, (40, 350), font, 1.5, (255, 255, 255), 2,
                    cv2.LINE_AA)
        if self.l_line.line_base_pos > 0:
            direction = 'right'
        else:
            direction = 'left'
        abs_center_dist = abs(self.l_line.line_base_pos)
        text = direction + ' of center ' + '{:04.3f}'.format(
            abs_center_dist) + 'm'
        cv2.putText(new_img, text, (40, 400), font, 1.5, (255, 255, 255), 2,
                    cv2.LINE_AA)
        return new_img
예제 #3
0
class Simulator:
    def __init__(self,
                 server_use,
                 rounds,
                 round_number,
                 epsilon,
                 window,
                 seed=None):
        self.epsilon = epsilon
        self.window = window
        self.running = False
        self.server_use = server_use
        self.server = Server()  # Servidor
        self.measures = {}
        self.flags = {  # Dicionário usado para comunicação entre threads
            "stop": False,
            "services_num": 0,
            "served": 0,
            "round": round_number
        }
        self.f_line = Line(self.flags)  # Fila 1
        self.s_line = Line(self.flags, last_line=True)  # Fila 2
        self.rounds = rounds
        self.seed = seed
        self.iter = 0
        self.finished_by_key = False

        # Inicializando clientes de LOG
        LOGGER2.info("## Initializing arrivals ##")
        LOGGER3.info("## Initializing lines ##")
        LOGGER4.info("## Initializing services ##")
        LOGGER5.info("## Initializing server ##")
        LOGGER1.info(
            "## Initializing simulator <> Server user: {} <> Seed: {} <> Rounds: {} ##"
            .format(self.server_use, self.seed, self.rounds))
        self.initial_time = None

    # Método gerador de fregueses (roda em thread separada)
    def arrivals(self, rate):
        if self.seed:
            LOGGER2.info("Semente: {}".format(self.seed))
            random.seed(self.seed)

        arrivals_sample = []
        chron = Chronometer("Arrivals")
        while not self.flags["stop"]:  # Enquanto o simulador não parar
            chron.start()  # Inicia o temporizador
            next_sample = random.expovariate(
                rate
            )  # Gera um número com distribuição exponencial e taxa dada
            LOGGER2.info("Waiting: {}".format(next_sample))
            arrivals_sample.append(next_sample)
            while not chron.spent(
            ) / 1000000 > next_sample:  # Enquanto o temporizador for menor que o número
                pass  # Continua em loop
            chron.stop(final=True)  # Ao terminar, reseta o temporizador,
            chron.take_note("Service {} goes to line 1".format(self.iter),
                            "arrivals".format(self.iter))
            service = Service(self.iter)  # Cria um serviço
            self.f_line.add(service)  # e o adiciona na fila 1
            service.t_line1.start()
            self.iter += 1
            self.flags["services_num"] += 1

    @property
    def rate(self):  # Taxa = uso do servidor/2
        return self.server_use / 2

    # Inicia o simulador
    def start(self):
        self.running = True

        # Gera as threads de servidor e de chegadas de fregueses
        LOGGER1.info("Initializing server")
        t_server = Thread(target=self.server.start,
                          args=(self.flags, self.f_line, self.s_line),
                          name="server")
        LOGGER1.info("Initializing arrivals")
        t_arrivals = Thread(target=self.arrivals,
                            args=(self.rate, ),
                            name="arrivals")
        self.initial_time = datetime.now()
        t_server.start()
        t_arrivals.start()

        LOGGER1.info("Server and arrivals threads initialized")

        while self.flags[
                "served"] < self.rounds:  # Enquanto o número de fregueses servidos for menor
            # que o número de rodadas definido
            if msvcrt.kbhit() and ord(
                    msvcrt.getch()) == 27:  # A menos que se pressione ESC
                LOGGER1.info("Finished by ESC Key press")
                print("Terminando o simulador...")
                self.finished_by_key = True
                break
            # Continua em loop

        self.flags[
            "stop"] = True  # Ao terminar, manda um sinal para todas as threads finalizarem
        # print("Aguarde a finalização do simulador...")
        t_server.join()
        t_arrivals.join()

        LOGGER1.info("Finished simulator")

        # É hora de calcular: E[W], E[T], E[Nq], E[N], V[W] (PARA FILAS 1 E 2)
        self.take_off_transient()
        self.calculate_measures()

        if self.finished_by_key:
            return False
        return True

    def calculate_measures(self):
        self.measures["E[W1]"] = np.mean(self.measures["W1"])
        self.measures["E[T1]"] = np.mean(self.measures["T1"])
        self.measures["E[N1]"] = np.mean(self.measures["N1"])
        self.measures["E[Nq1]"] = np.mean(self.measures["Nq1"])
        self.measures["V[W1]"] = np.var(self.measures["W1"])
        self.measures["E[W2]"] = np.mean(self.measures["W2"])
        self.measures["E[T2]"] = np.mean(self.measures["T2"])
        self.measures["E[N2]"] = np.mean(self.measures["N2"])
        self.measures["E[Nq2]"] = np.mean(self.measures["Nq2"])
        self.measures["V[W2]"] = np.var(self.measures["W2"])

        print(
            "Simulador {}:\n"
            "- Fila 1:\n    >> E[W] = {}\n    >> E[T] = {}\n    >> E[N] = {}\n"
            "    >> E[Nq] = {}\n    >> Var[W] = {}\n\n"
            "- Fila 2:\n    >> E[W] = {}\n    >> E[T] = {}\n    >> E[N] = {}\n"
            "    >> E[Nq] = {}\n    >> Var[W] = {}\n\n".format(
                self.flags["round"], self.measures["E[W1]"],
                self.measures["E[T1]"], self.measures["E[N1]"],
                self.measures["E[Nq1]"], self.measures["V[W1]"],
                self.measures["E[W2]"], self.measures["E[T2]"],
                self.measures["E[N2]"], self.measures["E[Nq2]"],
                self.measures["V[W2]"]))

    def take_off_transient(self):
        measures = {
            "W1": [],
            "W2": [],
            "T1": [],
            "T2": [],
            "Nq1": [],
            "Nq2": [],
            "N1": [],
            "N2": []
        }

        line_timestamps = []
        service_timestamps = []
        with open("line_measures_{}.csv".format(self.flags["round"]),
                  "r") as f:
            lines = f.readlines()
            for line in lines[1:]:
                splitted = line.split(",")
                line_timestamps.append(splitted[0])
                measures["N1"].append(int(splitted[1]))
                measures["Nq1"].append(int(splitted[2]))
                measures["N2"].append(int(splitted[3]))
                measures["Nq2"].append(int(splitted[4]))

        with open("service_measures_{}.csv".format(self.flags["round"]),
                  "r") as f:
            lines = f.readlines()
            for line in lines[1:]:
                splitted = line.split(",")
                service_timestamps.append(splitted[0])
                measures["T1"].append(int(splitted[1]) / 1000000)
                measures["W1"].append(int(splitted[2]) / 1000000)
                measures["T2"].append(int(splitted[3]) / 1000000)
                measures["W2"].append(int(splitted[4]) / 1000000)

        finishes = self.get_time_transient_finishes(line_timestamps, measures)

        for i in range(len(service_timestamps)):
            if service_timestamps[i] > finishes:
                self.measures["W1"] = measures["W1"][i:]
                self.measures["W2"] = measures["W2"][i:]
                self.measures["T1"] = measures["T1"][i:]
                self.measures["T2"] = measures["T2"][i:]
                break

        for i in range(len(line_timestamps)):
            if line_timestamps[i] > finishes:
                self.measures["N1"] = measures["N1"][i:]
                self.measures["N2"] = measures["N2"][i:]
                self.measures["Nq1"] = measures["Nq1"][i:]
                self.measures["Nq2"] = measures["Nq2"][i:]
                break

    def get_time_transient_finishes(self, line_t, measures):

        sum = 0
        initial_t = line_t[0]
        acc_values = []
        timestamps = []
        for i in range(1, len(measures["N1"])):
            services_on_system = measures["N1"][i] + measures["N2"][i]
            passed_time = self.time_passed(line_t[i - 1], line_t[i])
            sum += services_on_system * passed_time
            acc_values.append(sum)

            timestamps.append(self.time_passed(initial_t, line_t[i]))

        acc = [x / y for x, y in zip(acc_values, timestamps)]

        for i in range(self.window, len(acc)):
            var = np.var(np.array(acc[i - self.window:i]))
            if var < self.epsilon:
                transient_found = i - self.window
                break

        plt.plot(timestamps, acc, 'b-')
        plt.axvline(x=timestamps[transient_found])
        plt.savefig("transient_{}.png".format(self.flags["round"]))
        """
        t_axis = np.array([self.time_passed(
            self.initial_time,
            datetime.strptime(time, "%Y-%m-%d %H:%M:%S.%f")
        ) for time in service_t])
        m_axis = np.array([x+y for x, y in zip(measures["T1"], measures["T2"])])

        constants, matrix = opt.curve_fit(
            self.transient_function,
            t_axis,
            m_axis,
            bounds=([0.0, 1.0], [100000.0, 100000.])
        )
        plt.plot(t_axis, m_axis, 'b-', label='data')
        plt.plot(
            m_axis,
            self.transient_function(m_axis, *constants),
            'r-',
            label='fit: a=%5.3f, b=%5.3f' % tuple(constants)
        )
        plt.legend()
        plt.show()

        for i in range(1, len(service_t)):
            if m_axis[i] - m_axis[i-1] < self.epsilon:
                return service_t[i]

        for i in range(len(measures["T1"])):
            variances.append(
                np.var([x+y for x, y in zip(measures["T1"][i:], measures["T2"][i:])])
            )

        variances = variances[0:-1]
        print("Variâncias de cálculo do período transiente: {}".format(variances))

        for i in range(len(variances)-3):
            if variances[i+1] - variances[i] < self.epsilon:
                if variances[i+2] - variances[i+1] < self.epsilon:
                    if variances[i+3] - variances[i+2] < self.epsilon:
                        return service_t[i]
        """
        return line_t[transient_found + 1]

    def time_passed(self, initial, final):
        delta = datetime.strptime(final, "%Y-%m-%d %H:%M:%S.%f") - \
                datetime.strptime(initial, "%Y-%m-%d %H:%M:%S.%f")
        return delta.seconds + delta.microseconds / 1000000
예제 #4
0
class ImageProcessor():
    """
        Implements the computer vision algorithms for detecting lanes in an image.
    """
    def __init__(self, frameDimensions, frameRate):

        # Define camera dimensions.
        self.frameDimensions = frameDimensions
        self.frameRate = frameRate
        self.w = self.frameDimensions[0]
        self.h = self.frameDimensions[1]

        # ROI dimensions in percentage.
        self.roiY = (0.57, 0.71)
        self.roiX = (0.67, 0.95)

        # Initialize the left and right lane classes.
        self.left = Line(self.frameDimensions, (0, 0, 255))
        self.right = Line(self.frameDimensions, (255, 0, 0))

        # Camera calibration
        # Scale the calibration matrix to the desired frame dimensions.
        self.calibrationResolution = (
            1280, 720)  # Resolution at which the camera matrix is provided.
        kx = self.w / self.calibrationResolution[
            0]  # Calculate the change in the -x axis.
        ky = self.h / self.calibrationResolution[
            1]  # Calculate the change in the -y axis.
        cameraMatrix = np.array([  # Raw camera calibration matrix.
            [1.00612323e+03, 0.00000000e+00, 6.31540281e+02],
            [0.00000000e+00, 1.00551440e+03, 3.48207362e+02],
            [0.00000000e+00, 0.00000000e+00, 1.00000000e+00]
        ])
        self.cameraMatrix = np.multiply(
            cameraMatrix,
            [  # Adjust the camera calibration matrix.
                [kx, 1, kx], [1, ky, ky], [1, 1, 1]
            ])
        self.distortionCoefficients = np.array(
            [[0.18541226, -0.32660915, 0.00088513, -0.00038131, -0.02052374]])
        self.newCameraMatrix, self.roi = cv2.getOptimalNewCameraMatrix(
            self.cameraMatrix, self.distortionCoefficients,
            self.frameDimensions, 1, self.frameDimensions)
        self.rectifyMapX, self.rectifyMapY = cv2.initUndistortRectifyMap(
            self.cameraMatrix, self.distortionCoefficients, None,
            self.newCameraMatrix, self.frameDimensions, 5)

    def doBlur(self, frame, iterations, kernelSize):
        """
            Performs a gaussian blur with the set number of iterations.
        """

        blured = frame.copy()
        while iterations > 0:
            blured = cv2.GaussianBlur(blured, (kernelSize, kernelSize),
                                      sigmaX=0,
                                      sigmaY=0)
            iterations -= 1
        return blured

    def doRegionOfInterest(self, frame):
        """
            Obtains the region of interest from a frame. The dimensions of the ROI are set by the class
            properties roiX and roiY.
        """

        y0Px = self.h * self.roiY[0]
        y1Px = self.h * self.roiY[1]
        x0Px = (1 - self.roiX[0]) * self.w / 2
        x1Px = (1 - self.roiX[1]) * self.w / 2
        vertices = np.array([[(x0Px, y0Px), (x1Px, y1Px),
                              (self.w - x1Px, y1Px), (self.w - x0Px, y0Px)]],
                            dtype=np.int32)
        mask = np.zeros_like(frame)
        cv2.fillPoly(mask, vertices, 255)
        return cv2.bitwise_and(frame, mask)

    def findLanes(self, frame, lines, minAngle=10, drawAll=False):
        """
            Iterates through the results from the Hough Transform and filters the detected lines into
            those who belong to the left and right lane. Finally fits the data to a 1st order polynomial.
        """

        self.left.clear()
        self.right.clear()
        if type(lines) == type(np.array([])):
            for line in lines:
                for x1, y1, x2, y2 in line:
                    angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))
                    if np.abs(angle) > minAngle:
                        if angle > 0:
                            self.right.add(x1, y1, x2, y2)
                            if drawAll:
                                cv2.line(frame, (x1, y1), (x2, y2),
                                         self.right.color)
                        else:
                            self.left.add(x1, y1, x2, y2)
                            if drawAll:
                                cv2.line(frame, (x1, y1), (x2, y2),
                                         self.left.color)
        self.left.fit()
        self.right.fit()
        return frame

    def drawPoly(self, frame, poly, color, width=3):
        """
            Draws a 1-D polynomial into the frame. Uses the roiY for the -y coordinates.
        """

        y0 = self.h * self.roiY[0]
        y1 = self.h * self.roiY[1]
        y0Px = int(y0)
        y1Px = int(y1)
        if poly:
            x0Px = int(poly(y0))
            x1Px = int(poly(y1))
            cv2.line(frame, (x0Px, y0Px), (x1Px, y1Px), color, width)
        else:
            cv2.line(frame, (0, y0Px), (0, y1Px), color, width)

    def process(self, frame):
        """
            Main pipeline for detecting lanes on a frame.
        """

        undistort = cv2.remap(frame, self.rectifyMapX, self.rectifyMapY,
                              cv2.INTER_LINEAR)
        gray = cv2.cvtColor(undistort, cv2.COLOR_BGR2GRAY)
        grayColor = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
        blured = self.doBlur(gray, iterations=3, kernelSize=7)
        canny = cv2.Canny(blured, threshold1=20, threshold2=40)
        roi = self.doRegionOfInterest(canny)
        houghLines = cv2.HoughLinesP(roi,
                                     rho=1,
                                     theta=np.pi / 180,
                                     threshold=20,
                                     lines=np.array([]),
                                     minLineLength=5,
                                     maxLineGap=60)
        lanes = self.findLanes(grayColor,
                               houghLines,
                               minAngle=10,
                               drawAll=True)
        # self.drawPoly(lanes, self.left.poly, self.left.color, width=3)
        # self.drawPoly(lanes, self.right.poly, self.right.color, width=3)

        return grayColor