class TreasurePickupSuccessDetector(Detector): TREASURE_COLOR_RANGE = ColorRange(Color.from_hsv(40, 20, 75), Color.from_hsv(70, 255, 255)) DETECTION_CAMERA_ANGLE = Vector2(0, -80) RELATIVE_TREASURE_SIZE = Vector2(0.15, 0.117) DETECTION_RECT_CENTERED_SCALE = 1.25 DETECTION_RECT_SCALE = Vector2(1, 1.25) def __init__(self): self._treasure_contour_filter = ContourFilter(min_size_scale = Vector2(0.5, 0.5), max_size_scale = Vector2(2, 2)) def detect(self, image, gripper): self._treasure_contour_filter.target_size = image.size * self.RELATIVE_TREASURE_SIZE detection_zone_mask = self._create_detection_zone_mask(image, gripper) detection_zone_image = self._apply_mask(image, detection_zone_mask) treasure_mask = self._create_mask(detection_zone_image, self.TREASURE_COLOR_RANGE, opening_kernel_size = 10, closure_kernel_size = 20) treasure_contours = self._find_contours(treasure_mask, detection_zone_image) return self._detect_treasures_from_contours(treasure_contours) def _create_detection_zone_mask(self, image, gripper): mask = Image.from_attributes(image.width, image.height, ColorMode.BLACK_AND_WHITE) detection_rect = Rect.from_center(gripper.location, image.size * self.RELATIVE_TREASURE_SIZE) detection_rect = detection_rect.scale_centered(self.DETECTION_RECT_CENTERED_SCALE).scale(self.DETECTION_RECT_SCALE) OpenCV.fill_rectangle(mask, detection_rect, Color.WHITE) self._log_detection_step("Treasure Detection Zone Mask", mask) return mask def _detect_treasures_from_contours(self, contours): filtered_contours = self._treasure_contour_filter.filter_contours(contours) if len(filtered_contours) == 0: raise DetectionError("Treasure pickup success detection failed. Detected contours: {0}.".format(len(filtered_contours))) return [self._create_treasure(filtered_contours[0])] def _create_treasure(self, contour): treasure_location = OpenCV.get_contour_center(contour) return Treasure(treasure_location)
class TreasurePickupDetector(Detector): TREASURE_COLOR_RANGE = ColorRange(Color.from_hsv(40, 100, 75), Color.from_hsv(70, 255, 255)) DETECTION_CAMERA_ANGLE = Vector2(0, -80) RELATIVE_TREASURE_SIZE = Vector2(0.15, 0.03) CAMERA_PHYSICAL_SCALE = 32 def __init__(self): self._treasure_contour_filter = ContourFilter(min_size_scale = Vector2(0.75, 0.5), max_size_scale = Vector2(1.25, 5)) def detect(self, image, gripper): self._treasure_contour_filter.target_size = image.size * self.RELATIVE_TREASURE_SIZE detection_zone_mask = self._create_detection_zone_mask(image, gripper) detection_zone_image = self._apply_mask(image, detection_zone_mask) treasure_mask = self._create_mask(detection_zone_image, self.TREASURE_COLOR_RANGE, opening_kernel_size = 10, closure_kernel_size = 10) treasure_contours = self._find_contours(treasure_mask, detection_zone_image) return self._detect_treasures_from_contours(treasure_contours) def _create_detection_zone_mask(self, image, gripper): mask = Image.from_attributes(image.width, image.height, ColorMode.BLACK_AND_WHITE) detection_rect = Rect.from_size(image.size).clone() detection_rect.bottom = gripper.location.y OpenCV.fill_rectangle(mask, detection_rect, Color.WHITE) self._log_detection_step("Treasure Detection Zone Mask", mask) return mask def _detect_treasures_from_contours(self, contours): filtered_contours = self._treasure_contour_filter.filter_contours(contours) treasures = [] for contour in filtered_contours: treasures.append(self._create_treasure(contour)) return treasures def _create_treasure(self, contour): treasure_location = OpenCV.get_contour_center(contour) return Treasure(treasure_location)
class GripperDetector(Detector): GRIPPER_COLOR_RANGE = ColorRange(Color.from_hsv(82, 60, 100), Color.from_hsv(168, 255, 255)) GRIPPER_DETECTION_CAMERA_ANGLE = Vector2(0, -90) RELATIVE_GRIPPER_SIZE = Vector2(0.1, 0.075) DETECTION_RECT_SCALE = Vector2(1, 0.8) def __init__(self): self._gripper_contour_filter = ContourFilter(min_size_scale = 0.75, max_size_scale = 1.25) def detect(self, image): self._gripper_contour_filter.target_size = image.size * self.RELATIVE_GRIPPER_SIZE detection_zone_mask = self._create_detection_zone_mask(image) detection_zone_image = self._apply_mask(image, detection_zone_mask) gripper_mask = self._create_mask(detection_zone_image, self.GRIPPER_COLOR_RANGE, opening_kernel_size = 5, closure_kernel_size = 10) contours = self._find_contours(gripper_mask, image) return self._detect_gripper_from_contours(contours) def _create_detection_zone_mask(self, image): mask = Image.from_attributes(image.width, image.height, ColorMode.BLACK_AND_WHITE) detection_rect = Rect.from_size(image.size.clone()) detection_rect = detection_rect.scale(self.DETECTION_RECT_SCALE) OpenCV.fill_rectangle(mask, detection_rect, Color.WHITE) self._log_detection_step("Gripper Detection Zone Mask", mask) return mask def _detect_gripper_from_contours(self, contours): filtered_contours = self._gripper_contour_filter.filter_contours(contours, use_rotated_rect = True) if len(filtered_contours) != 1: raise DetectionError("The gripper recognition pattern could not be detected.") return self._create_gripper(filtered_contours[0]) def _create_gripper(self, contour): gripper_location = OpenCV.get_contour_center(contour) return Gripper(gripper_location)
class IslandDetector(Detector): MAX_SEGMENT_COUNT = 12 PLAYFIELD_RECT_MASK_SCALE = Vector2(1, 1.1) def __init__(self, coordinate_factory): self._island_factory = IslandFactory(coordinate_factory) self._simple_shape_finder = SimpleShapeFinder() self._contour_filter = ContourFilter(min_size_scale = 0.75, max_size_scale = 1.5, min_aspect_ratio = 0.8, max_aspect_ratio = 1.2, min_area_scale = 0.5, max_area_scale = 2) def detect(self, image, playfield, color_ranges, island_color, robot): Logger.get_instance().log(self, "Island Detection", details = "Detection started. Color: {0}.".format(island_color)) self._contour_filter.target_size = Coordinate.translate_physical_to_game(Island.AVERAGE_PHYSICAL_SIZE_CM, playfield) self._contour_filter.target_area = self._contour_filter.target_size.area return self._perform_detection(image.clone(), playfield, color_ranges, island_color, robot) def _perform_detection(self, image, playfield, color_ranges, island_color, robot): island_zone = playfield.rect.clone().scale_centered(self.PLAYFIELD_RECT_MASK_SCALE) island_zone_mask = self._create_island_zone_mask(image, island_zone, robot) island_zone_image = self._apply_mask(image, island_zone_mask) island_mask = self._create_mask(island_zone_image, color_ranges, closure_kernel_size = 7) contours = self._find_contours(island_mask, island_zone_image) return self._detect_islands_from_contours(contours, island_color) def _create_island_zone_mask(self, image, island_zone, robot): mask = Image.from_attributes(image.width, image.height, ColorMode.BLACK_AND_WHITE) OpenCV.fill_rectangle(mask, island_zone, Color.WHITE) self._mask_robot(mask, robot) self._log_detection_step("Island Zone Mask", mask) return mask def _mask_robot(self, mask, robot): if robot is not None: OpenCV.fill_rotated_rectangle(mask, robot.get_rotated_rect(CoordinateSystem.CAMERA), Color.BLACK) def _detect_islands_from_contours(self, contours, island_color): islands = [] filtered_contours = self._contour_filter.filter_contours(contours) for contour in filtered_contours: islands.append(self._create_island_from_contour(contour, island_color)) Logger.get_instance().log(self, "Island Detection", details = "Detected islands: {0}.".format(len(islands))) return islands def _create_island_from_contour(self, contour, island_color): simplified_path, enclosing_circle = self._simple_shape_finder.find_shape(contour, self.MAX_SEGMENT_COUNT) return self._island_factory.create_island(IslandType.from_segment_count(simplified_path.segment_count), island_color, simplified_path, enclosing_circle)
class ChargingStationDetector(Detector): RECOGNITION_PATTERN_COLOR_RANGES = [ColorRange(Color.from_hsv(0, 100, 100), Color.from_hsv(20, 255, 255)), ColorRange(Color.from_hsv(340, 100, 100), Color.from_hsv(360, 255, 255))] def __init__(self, coordinate_factory): self._coordinate_factory = coordinate_factory self._contour_filter = ContourFilter(min_size_scale = 0.75, max_size_scale = 1.5, min_aspect_ratio = 0.75, max_aspect_ratio = 1.25) def detect(self, image, playfield): self._contour_filter.target_size = Coordinate.translate_physical_to_game(ChargingStation.RECOGNITION_PATTERN_PHYSICAL_SIZE_CM, playfield) return self._perform_detection(image, playfield) def _perform_detection(self, image, playfield): detection_zone = self._define_detection_zone(playfield) detection_zone_mask = self._create_detection_zone_mask(image, detection_zone) detection_zone_image = self._apply_mask(image, detection_zone_mask) recognition_pattern_mask = self._create_mask(detection_zone_image, self.RECOGNITION_PATTERN_COLOR_RANGES, opening_kernel_size = 3) contours = self._find_contours(recognition_pattern_mask, detection_zone_image) return self._detect_charging_station_from_contours(contours, playfield) def _define_detection_zone(self, playfield): charging_station_size = Coordinate.translate_physical_to_game(ChargingStation.PHYSICAL_SIZE_CM, playfield) chargin_station_rect = Rect(Vector2(playfield.rect.right - charging_station_size.width, 0), Vector2(charging_station_size.width, playfield.rect.top)) return chargin_station_rect def _create_detection_zone_mask(self, image, zone): mask = Image.from_attributes(image.width, image.height, ColorMode.BLACK_AND_WHITE) OpenCV.fill_rectangle(mask, zone, Color.WHITE) self._log_detection_step("Charging Station Detection Zone Mask", mask) return mask def _detect_charging_station_from_contours(self, contours, playfield): filtered_contours = self._contour_filter.filter_contours(contours) if len(filtered_contours) != 1: raise DetectionError("The recognition pattern could not be detected.") return self._create_charging_station(filtered_contours[0], playfield) def _create_charging_station(self, contour, playfield): center = OpenCV.get_contour_center(contour) center.y += Coordinate.translate_physical_to_game(ChargingStation.RECOGNITION_PATTERN_PHYSICAL_SIZE_CM, playfield).height location_coordinate = self._coordinate_factory.create(center, CoordinateSystem.CAMERA, altitude_cm = ChargingStation.RECOGNITION_PATTERN_HEIGHT_CM) return ChargingStation(location_coordinate)
class EndZoneTreasureDetector(Detector): TREASURE_COLOR_RANGE = ColorRange(Color.from_hsv(40, 150, 85), Color.from_hsv(70, 255, 255)) DETECTION_CAMERA_MOVEMENT_SEQUENCE = [Vector2(-35, -35), Vector2(-29, -35), Vector2(-23, -35), Vector2(-17, -35), Vector2(-11, -35)] RELATIVE_TREASURE_SIZE = Vector2(0.017, 0.0125) RELATIVE_LOCATION_ADJUSTEMENT = 0.03 def __init__(self): self._treasure_contour_filter = ContourFilter(min_size_scale = 0.5, max_size_scale = 1.5) def detect(self, image, playfield): detection_zone_mask = self._create_detection_zone_mask(image, playfield) detection_zone_image = self._apply_mask(image, detection_zone_mask) treasure_mask = self._create_mask(detection_zone_image, self.TREASURE_COLOR_RANGE, opening_kernel_size = 3) contours = self._find_contours(treasure_mask, detection_zone_image) return self._detect_treasures_from_contours(contours, playfield, image) def _create_detection_zone_mask(self, image, playfield): mask = Image.from_attributes(image.width, image.height, ColorMode.BLACK_AND_WHITE) detection_rect = Rect.from_points([Vector2(playfield.delimiting_segment.point1.x, 0), Vector2(playfield.delimiting_segment.point2.x, playfield.lowest_delimiting_point.y)]) OpenCV.fill_rectangle(mask, detection_rect, Color.WHITE) mask = OpenCV.combine_masks([mask.invert(), playfield.surface_mask]).invert() self._log_detection_step("Treasure Detection Zone Mask", mask) return mask def _detect_treasures_from_contours(self, contours, playfield, image): treasures = [] self._treasure_contour_filter.target_size = image.size * self.RELATIVE_TREASURE_SIZE filtered_contours = self._treasure_contour_filter.filter_contours(contours) for contour in filtered_contours: treasures.append(self._create_treasure(contour, playfield)) Logger.get_instance().log(self, "Treasure Detection", details = "Detected treasures: {0}.".format(len(treasures))) return treasures def _create_treasure(self, contour, playfield): treasure_location = OpenCV.get_contour_bounding_rect(contour).center panel_relative_location = (treasure_location.x - playfield.delimiting_segment.point1.x) / playfield.delimiting_segment.width + self.RELATIVE_LOCATION_ADJUSTEMENT return Treasure(treasure_location, panel_relative_location)
class PlayfieldDetector(Detector): LEFT_MASK_AREA_WIDTH = 5 RELATIVE_PLAYFIELD_AREA_SIZE = Vector2(0.95, 0.65) def __init__(self): self._rect_finder = RectFinder() self._image = None self._contour_filter = ContourFilter(min_size_scale = 0.7, max_size_scale = 1.25, min_aspect_ratio = 0.75, max_aspect_ratio = 2.25) def detect(self, image): self._image = image self._contour_filter.target_size = image.size * self.RELATIVE_PLAYFIELD_AREA_SIZE return self._perform_detection(image) def _perform_detection(self, image): playfield_mask = self._create_playfield_mask(image) playfield_contour = self._find_playfield_contour(playfield_mask, image) playfield_rect, detected_corners = self._find_rect_corners(playfield_contour) self._adjust_playfield_width(image, playfield_rect) return Playfield(playfield_rect) def _create_playfield_mask(self, image): mask = self._create_mask(image, ColorRange(Color.from_hsv(0, 0, 0), Color.from_hsv(360, 255, 75)), closure_kernel_size = 14) mask.invert() self._draw_left_exclusion_area(mask, image) self._draw_right_exclusion_area(mask, image) return mask def _draw_left_exclusion_area(self, mask, image): OpenCV.fill_rectangle(mask, Rect.from_size(Vector2(self.LEFT_MASK_AREA_WIDTH, image.height)), Color.BLACK) def _draw_right_exclusion_area(self, mask, image): charging_station_area_width = ChargingStation.PHYSICAL_SIZE_CM.width / Playfield.PHYSICAL_SIZE_CM.width * image.width OpenCV.fill_rectangle(mask, Rect(Vector2(image.width - charging_station_area_width, 0), Vector2(charging_station_area_width, image.height)), Color.BLACK) def _find_playfield_contour(self, playfield_mask, image): contours = self._find_contours(playfield_mask, image) playfield_contours = self._contour_filter.filter_contours(contours) self._log_contour_detection_step("Playfield Contours", playfield_contours, self._image) if len(playfield_contours) != 1: raise DetectionError("Playfield detection failed. Detected contours: {0}.".format(len(playfield_contours))) return playfield_contours[0] def _find_rect_corners(self, playfield_contour): table_rect, detected_corners = self._rect_finder.find_rectangle(playfield_contour) self._log_rect_corners_detection_step(table_rect, detected_corners) return table_rect, detected_corners def _adjust_playfield_width(self, image, playfield_rect): playfield_rect.left = 0 playfield_rect.width = playfield_rect.height * Playfield.PHYSICAL_SIZE_CM.aspect_ratio # Logging def _log_rect_corners_detection_step(self, table_rect, detected_corners): if Logger.get_instance().save_blob_data: self._image.save_state() OpenCV.draw_rectangle(self._image, table_rect, Color.YELLOW, thickness = 3) for corner in detected_corners: OpenCV.draw_circle(self._image, Vector2(corner[0], corner[1]), radius = 10, color = Color.RED, thickness = 3) self._log_detection_step("Playfield Rect Corners", self._image) self._image.restore_state() def _log_playfield_detection_step(self, image, playfield_rect): if Logger.get_instance().save_blob_data: self._image.save_state() image.crop(playfield_rect) self._log_detection_step("Playfield", image) self._image.restore_state()
def __init__(self, coordinate_factory): self._coordinate_factory = coordinate_factory self._contour_filter = ContourFilter(min_size_scale = 0.75, max_size_scale = 1.5, min_aspect_ratio = 0.75, max_aspect_ratio = 1.25)
class RobotDetector(Detector): DETECTION_RECT_SCALE_FACTOR = Vector2(1, 1.2) RECOGNITION_PATTERN_COLOR_RANGE = ColorRange(Color.from_hsv(280, 120, 180), Color.from_hsv(335, 255, 255)) def __init__(self, robot_factory): self._robot_factory = robot_factory self._pattern_circle_filter = ContourFilter( min_size_scale=0.5, max_size_scale=1.5, min_aspect_ratio=0.7, max_aspect_ratio=1.3 ) self._pattern_filter = ContourFilter(min_aspect_ratio=0.75, max_aspect_ratio=1.25) def detect(self, image, playfield): self._pattern_circle_filter.target_size = Coordinate.translate_physical_to_game( Robot.RECOGNITION_CIRCLE_PHYSICAL_SIZE_CM, playfield ) self._recognition_circle_distance = Coordinate.translate_physical_to_game( Robot.RECOGNITION_CIRCLE_DISTANCE_CM, playfield ) return self._perform_detection(image.clone(), playfield) def _perform_detection(self, image, playfield): detection_rect = self._create_detection_area(image, playfield) mask = self._create_mask( image, self.RECOGNITION_PATTERN_COLOR_RANGE, opening_kernel_size=5, closure_kernel_size=5 ) contours = self._find_contours(mask, image) return self._detect_robot_from_contours(contours, detection_rect, playfield) def _create_detection_area(self, image, playfield): detection_rect = playfield.rect.clone().scale_centered(self.DETECTION_RECT_SCALE_FACTOR) image.crop(detection_rect) self._log_detection_step("Robot Detection Area", image) return detection_rect def _detect_robot_from_contours(self, contours, detection_rect, playfield): recognition_circles = self._get_robot_recognition_circles(contours, detection_rect) return self._get_robot_from_recognition_circles(recognition_circles) def _get_robot_recognition_circles(self, contours, detection_rect): recognition_circles = [] filtered_contours = self._pattern_circle_filter.filter_contours(contours) for contour in filtered_contours: recognition_circles.append(OpenCV.get_contour_center(contour).offset(detection_rect.location)) return recognition_circles def _get_robot_from_recognition_circles(self, recognition_circles): self._validate_recognition_circles(recognition_circles) robot = self._robot_factory.create_from_recognition_circles( recognition_circles, CoordinateSystem.CAMERA, RobotCreationSource.DETECTION ) self._validate_robot(robot) return robot def _validate_recognition_circles(self, recognition_circles): if len(recognition_circles) != 3: raise DetectionError( "The required amount of recognition circles were not detected. Detected: {0}.".format( len(recognition_circles) ) ) for pair in ListUtils.pairs(recognition_circles): pair_distance = Segment(pair[0], pair[1]).length if not MathUtils.in_range( pair_distance, self._recognition_circle_distance.x, self._recognition_circle_distance.y ): raise DetectionError( "Distance between detected recognition circles was not of the correct length. Detected: {0}.".format( pair_distance ) ) def _validate_robot(self, robot): rotated_rect = robot.get_rotated_rect() if not self._pattern_filter.filter_rotated_rect(rotated_rect): raise DetectionError( "Detected recognition circles do not meet the required criterias. Aspect ratio: {0}".format( rotated_rect.aspect_ratio ) )
def __init__(self, robot_factory): self._robot_factory = robot_factory self._pattern_circle_filter = ContourFilter( min_size_scale=0.5, max_size_scale=1.5, min_aspect_ratio=0.7, max_aspect_ratio=1.3 ) self._pattern_filter = ContourFilter(min_aspect_ratio=0.75, max_aspect_ratio=1.25)
class TreasureDetector(Detector): TREASURE_ZONE_SCALE_FACTOR = 0.0125 TREASURE_COLOR_RANGE = ColorRange(Color.from_hsv(40, 100, 75), Color.from_hsv(70, 255, 255)) ROBOT_MASK_RECT_SCALE = 1.2 TOP_ZONE = 0 BOTTOM_ZONE = 1 def __init__(self, coordinate_factory): self._coordinate_factory = coordinate_factory self._contour_filter = ContourFilter(min_size_scale = Vector2(0.75, 1), max_size_scale = Vector2(2, 4), min_aspect_ratio = 1, max_aspect_ratio = 3) def detect(self, image, playfield, robot, islands): Logger.get_instance().log(self, "Treasure Detection", details = "Detection started.") self._contour_filter.target_size = Coordinate.translate_physical_to_game(Treasure.PHYSICAL_SIZE_CM.xy, playfield) return self._perform_detection(image, playfield, robot, islands) def _perform_detection(self, image, playfield, robot, islands): treasure_zones = self._define_treasure_zones(image, playfield) treasure_zone_mask = self._create_treasure_zone_mask(image, treasure_zones, robot, islands) treasure_zone_image = self._apply_mask(image, treasure_zone_mask) treasure_mask = self._create_mask(treasure_zone_image, self.TREASURE_COLOR_RANGE, opening_kernel_size = 3) contours = self._find_contours(treasure_mask, treasure_zone_image) return self._detect_treasures_from_contours(contours) def _define_treasure_zones(self, image, playfield): treasure_zones = ListUtils.init_list(2) zone_size = image.width * self.TREASURE_ZONE_SCALE_FACTOR charging_station_area_width = Coordinate.translate_physical_to_game(ChargingStation.PHYSICAL_SIZE_CM, playfield).width treasure_zones[self.TOP_ZONE] = Rect(Vector2(playfield.rect.left, playfield.rect.top - zone_size), Vector2(playfield.rect.width - charging_station_area_width, zone_size * 2)) treasure_zones[self.BOTTOM_ZONE] = Rect(Vector2(playfield.rect.left, playfield.rect.bottom - zone_size), Vector2(playfield.rect.width - charging_station_area_width, zone_size * 2)) return treasure_zones def _create_treasure_zone_mask(self, image, treasure_zones, robot, islands): mask = Image.from_attributes(image.width, image.height, ColorMode.BLACK_AND_WHITE) for treasure_zone in treasure_zones: OpenCV.fill_rectangle(mask, treasure_zone, Color.WHITE) self._mask_robot(mask, robot) self._mask_islands(mask, islands) self._log_detection_step("Treasure Zone Mask", mask) return mask def _mask_robot(self, mask, robot): if robot is not None: robot_mask_rect = robot.get_rotated_rect(CoordinateSystem.CAMERA).clone().scale_centered(self.ROBOT_MASK_RECT_SCALE) OpenCV.fill_rotated_rectangle(mask, robot_mask_rect, Color.BLACK) def _mask_islands(self, mask, islands): for island in islands: bounding_rect = island.get_bounding_rect(CoordinateSystem.CAMERA) OpenCV.fill_rectangle(mask, bounding_rect, Color.BLACK) def _detect_treasures_from_contours(self, contours): treasures = [] filtered_contours = self._contour_filter.filter_contours(contours) for index, contour in enumerate(filtered_contours): treasures.append(self._create_treasure(contour, index)) Logger.get_instance().log(self, "Treasure Detection", details = "Detected treasures: {0}.".format(len(treasures))) return treasures def _create_treasure(self, contour, label): treasure_rect = OpenCV.get_contour_bounding_rect(contour) location_coordinate = self._coordinate_factory.create(treasure_rect.center, CoordinateSystem.CAMERA, altitude_cm = Treasure.PHYSICAL_SIZE_CM.z / 2) return Treasure(location_coordinate, label)
def __init__(self, coordinate_factory): self._coordinate_factory = coordinate_factory self._contour_filter = ContourFilter(min_size_scale = Vector2(0.75, 1), max_size_scale = Vector2(2, 4), min_aspect_ratio = 1, max_aspect_ratio = 3)
def __init__(self): self._playfield_contour_filter = ContourFilter()
def __init__(self): self._rect_finder = RectFinder() self._image = None self._contour_filter = ContourFilter(min_size_scale = 0.7, max_size_scale = 1.25, min_aspect_ratio = 0.75, max_aspect_ratio = 2.25)
def __init__(self): self._gripper_contour_filter = ContourFilter(min_size_scale = 0.75, max_size_scale = 1.25)
def __init__(self): self._treasure_contour_filter = ContourFilter(min_size_scale = 0.5, max_size_scale = 1.5)
def __init__(self): self._treasure_contour_filter = ContourFilter(min_size_scale = Vector2(0.5, 0.5), max_size_scale = Vector2(2, 2))
class PlayfieldDetector(Detector): PLAYFIELD_COLOR_RANGE = ColorRange(Color.from_hsv(0, 0, 0), Color.from_hsv(360, 255, 75)) RELATIVE_PLAYFIELD_AREA_SIZE = Vector2(0.95, 0.5) RELATIVE_DELIMITING_SEGMENT_SIZE = Vector2(0.55, 0.9) MIN_PLAYFIELD_VERTICE_COUNT = 5 MAX_PLAYFIELD_VERTICE_COUNT = 7 DELIMITING_SEGMENT_DISTANCE_FILTER = 10 EXPECTED_DELIMITING_POINT_COUNT = 2 EXPECTED_CONTOUR_COUNT = 1 def __init__(self): self._playfield_contour_filter = ContourFilter() def detect(self, image): self._playfield_contour_filter.target_size = image.size * self.RELATIVE_PLAYFIELD_AREA_SIZE mask = self._create_playfield_detection_mask(image) surface_contour = self._find_surface_contour(mask, image) delimiting_segment = self._find_delimiting_segment(mask, image) self._validate_segment(delimiting_segment, image) return self._create_playfield(surface_contour, delimiting_segment, mask) def _create_playfield_detection_mask(self, image): mask = self._create_mask(image, self.PLAYFIELD_COLOR_RANGE) mask = self._apply_opening_morph_transform(mask, relative_kernel_size=15) mask = self._apply_closure_morph_transform(mask, relative_kernel_size=25) mask.invert() return mask def _find_surface_contour(self, mask, image): contours = self._find_contours(mask.clone(), image) filtered_contours = self._playfield_contour_filter.filter_contours(contours) if len(filtered_contours) != self.EXPECTED_CONTOUR_COUNT: raise DetectionError( "Playfield detection failed. Detected delimiting contours: {0}.".format(len(filtered_contours)) ) return filtered_contours[0] def _find_delimiting_segment(self, mask, image): hough_lines = OpenCV.hough_lines(mask, threshold=50, canny_threshold1=50, canny_threshold2=150) top_hough_lines, left_hough_lines, right_hough_lines = self._group_hough_lines(hough_lines) median_top_line = Segment.average(top_hough_lines) median_left_line = Segment.average(left_hough_lines) median_right_line = Segment.average(right_hough_lines) left_delimiting_point = median_top_line.intersection_point(median_left_line) right_delimiting_point = median_top_line.intersection_point(median_right_line) return Segment(left_delimiting_point, right_delimiting_point) def _group_hough_lines(self, hough_lines): top_hough_lines = list(filter(lambda line: MathUtils.in_range(line.angle, 345, 360), hough_lines)) left_hough_lines = list(filter(lambda line: MathUtils.in_range(line.angle, 1, 74), hough_lines)) right_hough_lines = list(filter(lambda line: MathUtils.in_range(line.angle, 270, 344), hough_lines)) if len(top_hough_lines) == 0 or len(left_hough_lines) == 0 or len(right_hough_lines) == 0: raise DetectionError("Playfield detection failed. No hough lines found.") return top_hough_lines, left_hough_lines, right_hough_lines def _validate_segment(self, delimiting_segment, image): if not MathUtils.in_range( delimiting_segment.length, image.width * self.RELATIVE_DELIMITING_SEGMENT_SIZE.x, image.width * self.RELATIVE_DELIMITING_SEGMENT_SIZE.y, ): raise DetectionError( "Playfield detection failed. Delimiting segment length was not within the expected range." ) def _create_playfield(self, surface_contour, delimiting_segment, mask): surface_contour = OpenCV.simplify_contour(surface_contour, epsilon_base=0.003) surface_contour_path = OpenCV.get_path_from_contour(surface_contour) return Playfield(surface_contour_path, delimiting_segment, mask)
def __init__(self, coordinate_factory): self._island_factory = IslandFactory(coordinate_factory) self._simple_shape_finder = SimpleShapeFinder() self._contour_filter = ContourFilter(min_size_scale = 0.75, max_size_scale = 1.5, min_aspect_ratio = 0.8, max_aspect_ratio = 1.2, min_area_scale = 0.5, max_area_scale = 2)