예제 #1
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)
예제 #2
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)