Example #1
0
    def _process(self, image: np.ndarray) -> None:
        grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        self._image_display.display_debug_image('[CvSourceFinder] grey', grey)

        _, threshold = cv2.threshold(grey, 0, 255,
                                     cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        kernel = np.ones((5, 5), np.uint8)
        threshold = cv2.morphologyEx(threshold, cv2.MORPH_OPEN, kernel)
        self._image_display.display_debug_image('[CvSourceFinder] threshold',
                                                threshold)

        contour_with_source = self._compute_biggest_contour(threshold, False)
        contour_without_source = self._compute_biggest_contour(threshold, True)
        source_contour = self._compute_source_contour(grey,
                                                      contour_with_source,
                                                      contour_without_source)

        self._image_display.display_debug_contours(
            '[CvSourceFinder] contours', image,
            [contour_with_source, contour_without_source], [source_contour])

        self._source = Rectangle(*cv2.boundingRect(source_contour))
        image_height, image_width, _ = image.shape
        self._compute_orientation(image_width, image_height)
        return None
Example #2
0
 def find(self, image: Image) -> Tuple[Rectangle, Angle]:
     play_area = self._play_area_finder.find(image)
     image.crop(play_area).process(self._process)
     self._goal = Rectangle(
         self._goal.top_left_corner.x + play_area.top_left_corner.x,
         self._goal.top_left_corner.y + play_area.top_left_corner.y,
         self._goal.width, self._goal.height)
     return self._goal, self._orientation
Example #3
0
    def _does_contour_fit_area(contour: np.ndarray) -> bool:
        area = Rectangle(*cv2.boundingRect(contour))
        try:
            # The table is 231cm * 111cm, which gives it a width/height ratio of 2.08
            does_ratio_fit = 1.5 < area.width_height_ratio < 2.5

            return does_ratio_fit
        except ValueError:
            return False
Example #4
0
 def _does_contour_fit_source(contour: np.ndarray) -> bool:
     is_rectangle = len(contour) == 4
     if is_rectangle:
         rectangle = Rectangle(*cv2.boundingRect(contour))
         # From experimentation, we know that the goal has an area of around 1650 pixels
         does_area_fit = 450 < rectangle.area < 2850
         return does_area_fit
     else:
         return False
Example #5
0
    def _does_contour_fit_goal(contour: np.ndarray) -> bool:
        is_contour_rectangle = len(contour) == 4
        rectangle = Rectangle(*cv2.boundingRect(contour))

        # goal area is 27cm * 7.5cm, which gives a width/height ratio of 3.6 or 0.27
        ratio = rectangle.width_height_ratio
        does_ratio_fit = 2.6 < ratio < 4.6 or 2.6 < (1.0 / ratio) < 4.6

        # From experimentation, we know that the goal has an area of around 1650 pixels
        does_area_fit = 650 < rectangle.area < 2650

        return is_contour_rectangle and does_ratio_fit and does_area_fit
Example #6
0
    def _process(self, image: np.ndarray) -> None:
        grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        self._image_display.display_debug_image('[CvGoalFinder] grey', grey)

        canny = cv2.Canny(grey, 100, 200)
        self._image_display.display_debug_image('[CvGoalFinder] canny', canny)

        contours, _ = cv2.findContours(canny, cv2.RETR_TREE,
                                       cv2.CHAIN_APPROX_SIMPLE)
        contours = [CvGoalFinder._approximate_contour(c) for c in contours]
        contours = [
            c for c in contours if CvGoalFinder._does_contour_fit_goal(c)
        ]
        if len(contours) == 0:
            raise GoalCouldNotBeFound

        goal_contour = CvGoalFinder._get_brightest_area(grey, contours)

        self._goal = Rectangle(*cv2.boundingRect(goal_contour))
        image_height, image_width, _ = image.shape
        self._compute_orientation(image_width, image_height)
        self._image_display.display_debug_contours(
            '[CvGoalFinder] goal_contour', image, contours, [goal_contour])
Example #7
0
    def _process(self, image: np.ndarray) -> None:
        grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        self._image_display.display_debug_image('[OpenCvPlayAreaFinder] grey', grey)

        _, threshold = cv2.threshold(grey, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        self._image_display.display_debug_image('[OpenCvPlayAreaFinder] threshold', threshold)

        contours, _ = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
        contours = [CvPlayAreaFinder._approximate_contour(c) for c in contours]
        contours = [c for c in contours if CvPlayAreaFinder._does_contour_fit_area(c)]
        if len(contours) == 0:
            raise PlayAreaCouldNotBeFound

        contour_table = max(contours, key=cv2.contourArea)
        self._image_display.display_debug_contours('[OpenCvPlayAreaFinder] contours', image, contours, [contour_table])

        self._play_area = Rectangle(*cv2.boundingRect(contour_table))
    def test_when_get_goal_then_center_of_goal_and_orientation_are_returned_as_real_coordinate(
            self) -> None:
        self.initialiseService()
        expected_coord = Coord(0, 0)
        expected_angle = Angle(0)
        self.goal_finder.find = Mock(return_value=(Rectangle(0, 0, 10, 8),
                                                   expected_angle))
        self.camera_calibration.convert_table_pixel_to_real = Mock(
            return_value=Coord(0, 0))

        position = self.vision_service.get_goal()
        actual_coord = position.coordinate
        actual_angle = position.orientation

        self.camera_calibration.convert_table_pixel_to_real.assert_called_with(
            Coord(5, 4))
        self.assertEqual(expected_coord, actual_coord)
        self.assertEqual(expected_angle, actual_angle)
Example #9
0
    def _process(self, image: np.ndarray) -> None:
        grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

        _, threshold = cv2.threshold(grey, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        self._image_display.display_debug_image('[CvObstacleFinder] threshold', threshold)

        corners, ids, _ = aruco.detectMarkers(threshold, self._aruco_dictionary, parameters=self._detection_parameter)
        self._image_display.display_debug_aruco_markers('[CvObstacleFinder] markers', image, corners, ids)

        self._obstacles.clear()
        if ids is not None:
            for i in range(ids.shape[0]):
                if ids[i] == self._obstacles_id:
                    min_x = np.min(corners[i][0, :, 0])
                    max_x = np.max(corners[i][0, :, 0])
                    min_y = np.min(corners[i][0, :, 1])
                    max_y = np.max(corners[i][0, :, 1])
                    self._obstacles.append(Rectangle(min_x, min_y, max_x - min_x, max_y - min_y))
        else:
            raise ObstaclesCouldNotBeFound
Example #10
0
class CvGoalFinder(IGoalFinder):
    def __init__(self) -> None:
        self._goal: Rectangle = None
        self._orientation: Angle = None
        self._play_area_finder = CvPlayAreaFinder()
        self._image_display = CvImageDisplay()

    def find(self, image: Image) -> Tuple[Rectangle, Angle]:
        play_area = self._play_area_finder.find(image)
        image.crop(play_area).process(self._process)
        self._goal = Rectangle(
            self._goal.top_left_corner.x + play_area.top_left_corner.x,
            self._goal.top_left_corner.y + play_area.top_left_corner.y,
            self._goal.width, self._goal.height)
        return self._goal, self._orientation

    def _process(self, image: np.ndarray) -> None:
        grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        self._image_display.display_debug_image('[CvGoalFinder] grey', grey)

        canny = cv2.Canny(grey, 100, 200)
        self._image_display.display_debug_image('[CvGoalFinder] canny', canny)

        contours, _ = cv2.findContours(canny, cv2.RETR_TREE,
                                       cv2.CHAIN_APPROX_SIMPLE)
        contours = [CvGoalFinder._approximate_contour(c) for c in contours]
        contours = [
            c for c in contours if CvGoalFinder._does_contour_fit_goal(c)
        ]
        if len(contours) == 0:
            raise GoalCouldNotBeFound

        goal_contour = CvGoalFinder._get_brightest_area(grey, contours)

        self._goal = Rectangle(*cv2.boundingRect(goal_contour))
        image_height, image_width, _ = image.shape
        self._compute_orientation(image_width, image_height)
        self._image_display.display_debug_contours(
            '[CvGoalFinder] goal_contour', image, contours, [goal_contour])

    @staticmethod
    def _does_contour_fit_goal(contour: np.ndarray) -> bool:
        is_contour_rectangle = len(contour) == 4
        rectangle = Rectangle(*cv2.boundingRect(contour))

        # goal area is 27cm * 7.5cm, which gives a width/height ratio of 3.6 or 0.27
        ratio = rectangle.width_height_ratio
        does_ratio_fit = 2.6 < ratio < 4.6 or 2.6 < (1.0 / ratio) < 4.6

        # From experimentation, we know that the goal has an area of around 1650 pixels
        does_area_fit = 650 < rectangle.area < 2650

        return is_contour_rectangle and does_ratio_fit and does_area_fit

    @staticmethod
    def _approximate_contour(contour: np.ndarray) -> np.ndarray:
        epsilon = 0.05 * cv2.arcLength(contour, True)
        return cv2.approxPolyDP(contour, epsilon, True)

    @staticmethod
    def _get_brightest_area(grey: np.ndarray,
                            contours: List[np.ndarray]) -> np.ndarray:
        highest_mean_value = -1
        brightest_area_contour = None

        for contour in contours:
            mask = np.zeros(grey.shape, np.uint8)
            cv2.drawContours(mask, [contour], 0, 255, -1)

            current_mean_value, _, _, _ = cv2.mean(grey, mask=mask)

            if current_mean_value > highest_mean_value:
                highest_mean_value = current_mean_value
                brightest_area_contour = contour

        return brightest_area_contour

    def _compute_orientation(self, image_width, image_height) -> None:
        goal_center = self._goal.get_center()

        if self._goal.width_height_ratio > 1.0:  # target is horizontal
            if goal_center.y > image_height / 2:  # target is on bottom
                self._orientation = Angle(pi)
            else:
                self._orientation = Angle(0)
        else:  # target is vertical
            if goal_center.x > image_width / 2:  # target is on the right
                self._orientation = Angle(pi / 2)
            else:
                self._orientation = Angle(3 * pi / 2)
Example #11
0
 def _compute_marker_bounding_rectangle(corners: np.ndarray) -> Rectangle:
     min_x = np.min(corners[0, :, 0])
     max_x = np.max(corners[0, :, 0])
     min_y = np.min(corners[0, :, 1])
     max_y = np.max(corners[0, :, 1])
     return Rectangle(min_x, min_y, max_x - min_x, max_y - min_y)
Example #12
0
 def __init__(self) -> None:
     self._color = (0, 0, 0)
     self._rectangle = Rectangle(0, 0, 0, 0)
Example #13
0
class CvSourceFinder(ISourceFinder):
    def __init__(self) -> None:
        self._source: Rectangle = None
        self._orientation: Angle = None
        self._image_display = CvImageDisplay()

    def find(self, image: Image) -> Tuple[Rectangle, Angle]:
        image.process(self._process)
        return self._source, self._orientation

    def _process(self, image: np.ndarray) -> None:
        grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        self._image_display.display_debug_image('[CvSourceFinder] grey', grey)

        _, threshold = cv2.threshold(grey, 0, 255,
                                     cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        kernel = np.ones((5, 5), np.uint8)
        threshold = cv2.morphologyEx(threshold, cv2.MORPH_OPEN, kernel)
        self._image_display.display_debug_image('[CvSourceFinder] threshold',
                                                threshold)

        contour_with_source = self._compute_biggest_contour(threshold, False)
        contour_without_source = self._compute_biggest_contour(threshold, True)
        source_contour = self._compute_source_contour(grey,
                                                      contour_with_source,
                                                      contour_without_source)

        self._image_display.display_debug_contours(
            '[CvSourceFinder] contours', image,
            [contour_with_source, contour_without_source], [source_contour])

        self._source = Rectangle(*cv2.boundingRect(source_contour))
        image_height, image_width, _ = image.shape
        self._compute_orientation(image_width, image_height)
        return None

    def _compute_source_contour(self, grey: np.ndarray, contour_with_source,
                                contour_without_source):
        mask = np.zeros((grey.shape[0], grey.shape[1], 1), np.uint8)

        cv2.drawContours(mask, [contour_without_source], 0, 255, -1)
        cv2.drawContours(mask, [contour_with_source], 0, 0, -1)

        kernel = np.ones((5, 5), np.uint8)
        mask = cv2.erode(mask, kernel, iterations=1)

        self._image_display.display_debug_image('[CvSourceFinder] source mask',
                                                mask)

        contours, _ = cv2.findContours(mask, cv2.RETR_TREE,
                                       cv2.CHAIN_APPROX_SIMPLE)
        contours = [CvSourceFinder._approximate_contour(c) for c in contours]
        contours = [
            c for c in contours if CvSourceFinder._does_contour_fit_source(c)
        ]
        if len(contours) == 0:
            raise SourceCouldNotBeFound

        source_mean_value = 256
        source_contour = None
        for contour in contours:
            mask = np.zeros(grey.shape, np.uint8)
            cv2.drawContours(mask, [contour], 0, 255, -1)

            current_mean_value, _, _, _ = cv2.mean(grey, mask=mask)
            if current_mean_value < source_mean_value:
                source_mean_value = current_mean_value
                source_contour = contour
        return source_contour

    def _compute_biggest_contour(self, image: np.ndarray, approximate):
        contours, _ = cv2.findContours(image, cv2.RETR_TREE,
                                       cv2.CHAIN_APPROX_SIMPLE)
        if len(contours) == 0:
            raise SourceCouldNotBeFound

        biggest_contour = max(contours, key=cv2.contourArea)
        self._image_display.display_debug_contours(
            '[CvSourceFinder] _compute_biggest_contour', image, contours,
            [biggest_contour])

        if approximate:
            biggest_contour = CvSourceFinder._approximate_contour(
                biggest_contour)

        return biggest_contour

    @staticmethod
    def _approximate_contour(contour: np.ndarray) -> np.ndarray:
        epsilon = 0.05 * cv2.arcLength(contour, True)
        return cv2.approxPolyDP(contour, epsilon, True)

    @staticmethod
    def _does_contour_fit_source(contour: np.ndarray) -> bool:
        is_rectangle = len(contour) == 4
        if is_rectangle:
            rectangle = Rectangle(*cv2.boundingRect(contour))
            # From experimentation, we know that the goal has an area of around 1650 pixels
            does_area_fit = 450 < rectangle.area < 2850
            return does_area_fit
        else:
            return False

    def _compute_orientation(self, image_width, image_height) -> None:
        goal_center = self._source.get_center()

        if self._source.width_height_ratio > 1.0:  # target is horizontal
            if goal_center.y > image_height / 2:  # target is on bottom
                self._orientation = Angle(pi)
            else:
                self._orientation = Angle(0)
        else:  # target is vertical
            if goal_center.x > image_width / 2:  # target is on the right
                self._orientation = Angle(pi / 2)
            else:
                self._orientation = Angle(3 * pi / 2)