def test_point_distance(self): l1 = Line((0, 0), (0, 1)) point1 = (0, 0) point2 = (1, 0) self.assertEqual(l1.point_distance(point1), 0) self.assertEqual(l1.point_distance(point2), 1)
def stopline(self, value): """ Sets the stopline with specified offset, assuming that first vanishing point is in the top part of frame """ self._stop_line = value left_edge_point = self.stopline.edge_points(info=self._info)[0] right_edge_point = self.stopline.edge_points(info=self._info)[1] self._stop_line = Line( point1=(left_edge_point[0], left_edge_point[1] - constants.CORRIDORS_STOP_LINE_OFFSET), point2=(right_edge_point[0], right_edge_point[1] - constants.CORRIDORS_STOP_LINE_OFFSET)) left_edge_point = self.stopline.edge_points(info=self._info)[0] right_edge_point = self.stopline.edge_points(info=self._info)[1] top_line = Line(point1=(left_edge_point[0], 3 * left_edge_point[1] / 5), point2=(right_edge_point[0], 3 * right_edge_point[1] / 5)) self._info.update_area.change_area(top_line=top_line)
def filter_lifelines(lifelines, vp1): """ Filters given trajectories by those which have direction ti the first vanishing point. :param lifelines: given trajectories :param vp1: detected vanishing point :return: filtered trajectories """ filtered = [] for lifeline in lifelines: line_to_vp = Line(lifeline[0], vp1.point) try: lifeline_line = Line(lifeline[0], lifeline[-1]) except SamePointError: continue if constants.TRACKER_TRAJECTORY_MIN_ANGLE < lifeline_line.angle(line_to_vp) < constants.TRACKER_TRAJECTORY_MAX_ANGLE: continue elif lifeline[0][1] < lifeline[-1][1]: continue else: filtered.append(lifeline) return filtered
def middle_point(self): """ :return: middle point of area """ l1 = Line(point1=self._top_left, point2=self._bot_right) l2 = Line(point1=self._bot_left, point2=self._top_right) return l1.intersection(l2)
def add_line(self): """ Adds stop line """ try: self.line = Line(self.point1, self.point2) except SamePointError: pass
def test_angle(self): l1 = Line((0, 0), (0, 1)) l2 = Line((0, 0), (1, 0)) l3 = Line((0, 0), (1, 1)) self.assertEqual(l1.angle(l2), 90) self.assertAlmostEqual(l1.angle(l3), 45, 4) l2.general_equation()
def add_line(self): """ Adds new line to storage """ try: new_line = Line(self.point1, self.point2) if new_line.angle(Line.horizontal_line()) != 0: self.selected.append(Line(self.point1, self.point2)) except SamePointError: pass
def draw_trajectories(self, image, method="second"): """ Helper method for drawing car trajectories (trajectories of center point) :param image: selected image :param method: method of trajectory obtaining to be printed :return: updated image """ if method == "second": for index, point in enumerate(self._history): cv2.circle(img=image, center=point.tuple(), color=constants.COLOR_BLUE, radius=5, thickness=constants.FILL) try: cv2.line(img=image, pt1=point.tuple(), pt2=self._history[index + 1].tuple(), color=constants.COLOR_BLUE, thickness=2) except IndexError: pass if method == "first": try: Line(self._history[0].tuple(), self._history[-1].tuple()).draw(image, constants.COLOR_RED, 1) except SamePointError: return cv2.circle(img=image, center=self._history[0].tuple(), color=constants.COLOR_BLUE, radius=5, thickness=constants.FILL) cv2.circle(img=image, center=self._history[-1].tuple(), color=constants.COLOR_BLUE, radius=5, thickness=constants.FILL) cv2.line(img=image, pt1=self._history[0].tuple(), pt2=self._history[-1].tuple(), color=constants.COLOR_BLUE, thickness=2) position_history = [ coordinates.tuple() for coordinates in self._history ] line, value = ransac(position_history, position_history, 1) if line is not None and value > 5: line.draw(image, constants.COLOR_RED, 2)
def find_stop_line(self): """ Finds stop line from stored stop-points. For stop line is used second vanishing point which is used as anchor for all detected stop line points. Stop line is detected using RANSAC algorithm. """ best_line_ratio = 0 best_line = None vp2 = self._info.vanishing_points[1] test_image = np.zeros(shape=(self._info.height, self._info.width)) for point in self._stop_places: cv2.circle(test_image, point.tuple(), radius=5, color=255, thickness=5) cv2.imwrite("stopky.png", test_image) for point2 in self._stop_places: try: line = Line(vp2.point, point2.tuple()) except SamePointError: continue except VanishingPointError: line = Line(point1=point2.tuple(), direction=vp2.direction) num = 0 ransac_threshold = constants.CORRIDORS_RANSAC_THRESHOLD for point in self._stop_places: distance = line.point_distance(point.tuple()) if distance < ransac_threshold: num += 1 if num > best_line_ratio: best_line_ratio = num best_line = line self.stopline = best_line self._stopline_found = True
def _calculate_third_vp(self): """ Because principal point is said to be in midle, the third vanishing point can be obtained by simple mathematics. After vanishing point is being found it propagates this information to InputInfo where it adds VanishingPoint to corresponding list """ vp1 = self._info.vanishing_points[0].point try: vp2 = self._info.vanishing_points[1].point vp1_to_vp2 = Line(point1=vp1, point2=vp2) except VanishingPointError: vp1_to_vp2 = Line( point1=vp1, direction=self._info.vanishing_points[1].direction) self._info.vanishing_points.append( VanishingPoint(direction=vp1_to_vp2.normal_direction()))
def _find_corridors(self, lifelines): """ After both vanishing points are found corridors can be constructed with the information of car trajectories. :param lifelines: trajectories of cars """ filtered_lifelines = TrackedObject.filter_lifelines( lifelines, self._info.vp1) mask = np.zeros(shape=(self._info.height, self._info.width, 3), dtype=np.uint8) # for history in filtered_lifelines: # line, value = ransac(history, history, 1) # # if line is not None and value > 5: # bottom_point = Coordinates(*line.find_coordinate(y=self._info.height)).tuple() # # cv2.line(img=mask, # pt1=bottom_point, # pt2=self._info.vp1.point, # color=params.COLOR_LIFELINE, # thickness=100) for history in filtered_lifelines: helper_mask = np.zeros_like(mask) first_point = history[0] line = Line(first_point, self._info.vp1.point) line.draw(image=helper_mask, color=constants.COLOR_LIFELINE, thickness=100) mask = cv2.add(mask, helper_mask) cv2.imwrite("lifeline.jpg", mask) self._info.corridors_repository.find_corridors( lifelines_mask=mask, vp1=self._info.vanishing_points[0])
def __init__(self, info, top_left, bottom_right, top_right, bottom_left): """ :param info: instance of InputInfo :param top_left: top left anchor of area :param bottom_right: bottom right anchor of area :param top_right: top right anchor of area :param bottom_left: bottom left anchor of area """ self._top_left = top_left.tuple() self._bot_right = bottom_right.tuple() self._top_right = top_right.tuple() self._bot_left = bottom_left.tuple() self.top_line = Line(point1=self._top_left, point2=self._top_right) self.left_line = Line(point1=self._top_left, point2=self._bot_left) self.bottom_line = Line(point1=self._bot_left, point2=self._bot_right) self.right_line = Line(point1=self._top_right, point2=self._bot_right) self._info = info
def draw_line(self, image, point, color, thickness): """ Helper function to dra line from point to this vanishing point. Works in both cases - when vanishing point is defined by a point or when defined by an angle :param image: selected image to draw on :param point: selected point as origin :param color: color of line :param thickness: thickness of line """ if self.infinity: line = Line(point1=point, direction=self.direction) else: line = Line(point1=point, point2=self.point) line.draw(image=image, color=color, thickness=thickness)
def test_vertical(self): self.assertFalse(Line((0, 1), (2, 1)).vertical) self.assertTrue(Line((0, 1), (0, 2)).vertical)
import numpy as np from primitives.point import Point from primitives.line import Line # testing point creation pointa = Point(1, 2) pointb = Point(3, 4) print(pointa.coords) # test line object creation with coordinates linea = Line([pointa.coords, pointb.coords]) # test setter with point objects lineb = Line([pointb, pointa]) print(lineb.coords) # test exception handling for line # linec = Line(["asdkjalskdfj", pointa]) # test coordinate setting from point coordinates points = [pointa.coords, pointb.coords] lineb.setcoords(points) # test coordinate setting from point objects lineb.setcoords([pointb, pointa]) # test adding points from coordinates pointc = Point(0, 0) lineb.addPoint(pointc.coords)
def _detect_second_vanishing_point(self, new_frame, boxes_mask, boxes_mask_no_border, light_status) -> None: """ Calculates second vanishing point using information about car positions and detection of edges supporting second vanishing point. It is being detected only if green light status is present on current frame. Detected lines from edges are accumulated into parallel coordinate space - RANSAC algorithm is used for intersection detection - Vanishing Point. After vanishing point is being found it propagates this information to InputInfo where it adds VanishingPoint to corresponding list :param new_frame: examined frame :param boxes_mask: mask used for selecting parts of image where cars exists :param boxes_mask_no_border: mask used for selecting parts of image where cars exists :param light_status: current light status """ if light_status in [Color.RED, Color.RED_ORANGE]: return selected_areas = cv2.bitwise_and( new_frame, cv2.cvtColor(boxes_mask, cv2.COLOR_GRAY2RGB)) blured = cv2.GaussianBlur(selected_areas, (7, 7), 0) canny = cv2.Canny(blured, 50, 150, apertureSize=3) no_border_canny = cv2.bitwise_and(canny, boxes_mask_no_border) no_border_canny = cv2.bitwise_and(no_border_canny, no_border_canny, mask=self._info.update_area.mask()) lines = cv2.HoughLinesP( image=no_border_canny, rho=1, theta=np.pi / 350, threshold=constants.CALIBRATOR_HLP_THRESHOLD, minLineLength=constants.CALIBRATOR_MIN_LINE_LENGTH, maxLineGap=constants.CALIBRATOR_MAX_LINE_GAP) vp1 = self._info.vanishing_points[0] canny = cv2.cvtColor(no_border_canny, cv2.COLOR_GRAY2RGB) if lines is not None: for (x1, y1, x2, y2), in lines: point1 = x1, y1 point2 = x2, y2 try: if vp1.coordinates.distance(Coordinates( x1, y1)) > vp1.coordinates.distance( Coordinates(x2, y2)): line_to_vp = Line(point1, vp1.point) else: line_to_vp = Line(point2, vp1.point) if Line(point1, point2).angle(line_to_vp) < 30 or Line( point1, point2).angle(line_to_vp) > 150: cv2.line(canny, point1, point2, constants.COLOR_RED, 2) continue self._pc_lines.add_to_pc_space(point1, point2) cv2.line(canny, point1, point2, constants.COLOR_BLUE, 2) except SamePointError: continue cv2.imwrite("test.jpg", canny) if self._pc_lines.count > constants.CALIBRATOR_VP2_TRACK_MINIMUM: new_vanishing_point = self._pc_lines.find_most_lines_cross() x, y = new_vanishing_point if y is not None: self._info.vanishing_points.append( VanishingPoint(point=new_vanishing_point)) else: dx = np.cos(np.deg2rad(x)) dy = np.sin(np.deg2rad(x)) direction = dx, dy self._info.vanishing_points.append( VanishingPoint(direction=direction)) self._pc_lines.clear()
class Area: """ Representing interesting area on image. Specified by anchors. Can be any 4 corner object. """ def __init__(self, info, top_left, bottom_right, top_right, bottom_left): """ :param info: instance of InputInfo :param top_left: top left anchor of area :param bottom_right: bottom right anchor of area :param top_right: top right anchor of area :param bottom_left: bottom left anchor of area """ self._top_left = top_left.tuple() self._bot_right = bottom_right.tuple() self._top_right = top_right.tuple() self._bot_left = bottom_left.tuple() self.top_line = Line(point1=self._top_left, point2=self._top_right) self.left_line = Line(point1=self._top_left, point2=self._bot_left) self.bottom_line = Line(point1=self._bot_left, point2=self._bot_right) self.right_line = Line(point1=self._top_right, point2=self._bot_right) self._info = info @property def middle_point(self): """ :return: middle point of area """ l1 = Line(point1=self._top_left, point2=self._bot_right) l2 = Line(point1=self._bot_left, point2=self._top_right) return l1.intersection(l2) @property def defined(self): """ :return: if area is defined """ return self._top_left is not None and self._bot_right is not None def change_area(self, top_line=None, bot_line=None, left_line=None, right_line=None): """ Changes area shape :param top_line: new top line :param bot_line: new bot line :param left_line: new left line :param right_line: new right line """ self.top_line = top_line if top_line is not None else self.top_line self.bottom_line = bot_line if bot_line is not None else self.bottom_line self.right_line = right_line if right_line is not None else self.right_line self.left_line = left_line if left_line is not None else self.left_line def draw(self, image, color=constants.COLOR_AREA): """ Helper function to draw an area :param image: selected image to draw on :param color: selected color to draw :return: updated image """ if self.defined: self.top_line.draw(image, color, constants.AREA_THICKNESS) return image def anchors(self): """ :return: top left nad bottom right anchor of the area """ return self._top_left, self._bot_right def __contains__(self, coordinates): if not self.defined: return False if self.top_line.find_coordinate(x=coordinates.x)[1] > coordinates.y: return False if self.bottom_line.find_coordinate( x=coordinates.x)[1] < coordinates.y: return False if self.right_line.find_coordinate(y=coordinates.y)[0] < coordinates.x: return False if self.left_line.find_coordinate(y=coordinates.y)[0] > coordinates.x: return False return True def mask(self): """ Generates binary mask of area :return: binary mask of area """ mask = np.zeros(shape=(self._info.height, self._info.width), dtype=np.uint8) self.draw(image=mask, color=constants.COLOR_WHITE_MONO) mask_with_border = np.pad(mask, 1, 'constant', constant_values=255) cv2.floodFill(image=mask, mask=mask_with_border, seedPoint=(int(self.middle_point[0]), int(self.middle_point[1])), newVal=constants.COLOR_WHITE_MONO) return mask
def test_same_points(self): with self.assertRaises(SamePointError): Line((3, 3), (3, 3))
def setUpClass(cls): p1 = Point(0, 0) p2 = Point(1, 1) l = Line([p1, p2])
def test_angle_90(self): l1 = Line((0, 1), (0, 0)) l2 = Line((0, 0), (1, 0)) self.assertEqual(l1.angle(l2), 90.)
def test_point(self): l1 = Line((1, 1), (2, 2)) self.assertTrue(l1.on_line((3, 3))) self.assertFalse(l1.on_line((-3, 3)))
def test_angle_019(self): l1 = Line((1, 2), (3, 5)) l2 = Line((3, 4), (2, 5)) self.assertAlmostEqual(l1.angle(l2), 78.7, 1)
def test_intersection(self): l1 = Line((0, 0), (1, 0)) l2 = Line((0, 0), (0, 1)) self.assertEqual(l1.intersection(l2), (0, 0))
def find_corridors(self, lifelines_mask, vp1): """ Finds corridors on frame using first positions of cars and detected first vanishing point to construct mask on which thresholding is used to detect separate corridors. By computation is used only 1px wide age around left, bottom and right edge of frame. :param lifelines_mask: mask of lifelines :param vp1: detected first vanishing point """ # extract left-bot-right 1px border around lifeline image and make "1D array" left_edge = lifelines_mask[:, :1] bottom_edge = lifelines_mask[-1:, :].transpose(1, 0, 2) right_edge = np.flip(lifelines_mask[:, -1:]) frame_edge = np.concatenate((left_edge, bottom_edge, right_edge)) # greyscale image and reduce noise by multiple blur and threshold edge_grey = cv2.cvtColor(frame_edge, cv2.COLOR_RGB2GRAY) edge_grey_blured = cv2.medianBlur(edge_grey, 11) _, threshold = cv2.threshold(edge_grey_blured, 50, 255, cv2.THRESH_BINARY) threshold = cv2.dilate(threshold, (5, 5), iterations=5) height, width = edge_grey_blured.shape edge_grey_canny = cv2.Canny(threshold, 50, 150) coordinates = [] # border image with 0 edge_grey_canny[0][0] = 0 edge_grey_canny[-1][0] = 0 for i in range(height): if edge_grey_canny[i][0] == 255: coordinates.append(i) points = np.array(coordinates).reshape(-1, 2) for index, point in enumerate(points): try: half_diff = int((points[index + 1][0] - point[1]) / 2) except IndexError: break point[1] += half_diff points[index + 1][0] -= half_diff for point in points: points = [] for coordinate in list(point): if coordinate < self._info.height: points.append((0, coordinate)) elif coordinate < self._info.height + self._info.width: points.append((coordinate - self._info.height, self._info.height - 1)) else: points.append((self._info.width - 1, self._info.width + 2 * self._info.height - coordinate)) left_bottom = tuple(points[0]) right_bottom = tuple(points[1]) left_top = right_top = vp1.point self.create_new_corridor(left_line=Line(left_bottom, left_top), right_line=Line(right_bottom, right_top)) # cv2.imwrite("mask.jpg", self.get_mask()) self._corridor_mask = cv2.bitwise_and( self._corridor_mask, self._corridor_mask, mask=self._info.update_area.mask()) self._corridors_found = True
class StopLineMaker(LineDrag): """ Used for stop line creation """ def __init__(self, image): super().__init__(image) self.line = None def add_line(self): """ Adds stop line """ try: self.line = Line(self.point1, self.point2) except SamePointError: pass def draw(self, image): """ Helper function to draw stop line :param image: selected image """ super().draw(image) if self.line is not None: self.line.draw( image, color=constants.COLOR_RED, thickness=constants.CORRIDORS_LINE_SELECTOR_THICKNESS) def run(self, info) -> []: """ Infinite loop until user selects stop line :param info: instance of InputInfo """ cv2.namedWindow("select_stop_line") cv2.setMouseCallback("select_stop_line", mouse_callback, self) while True: image_copy = np.copy(self.base_image) self.draw(image_copy) cv2.imshow("select_stop_line", image_copy) key = cv2.waitKey(1) if key == 13: self.line.draw(image_copy, constants.COLOR_BLUE, constants.CORRIDORS_LINE_SELECTOR_THICKNESS) cv2.imshow("select_stop_line", image_copy) key = cv2.waitKey(0) if key == 13: cv2.destroyWindow("select_stop_line") return self.line if key == 8: self.clear() if key == 8: self.erase_last() def erase_last(self): """ Deletes last selected stopline """ self.line = None
def test_same(self): l1 = Line((-3, 2), (-3, 3)) l2 = Line((-2, 2), (-2, 3)) with self.assertRaises(NoIntersectionError): l1.intersection(l2)
def test_horizontal(self): self.assertTrue(Line((0, 1), (2, 1)).horizontal) self.assertFalse(Line((0, 1), (2, 1)).vertical)