Esempio n. 1
0
    def test_monocular(self):
        ci = sensor_msgs.msg.CameraInfo()
        ci.width = 640
        ci.height = 480
        print ci
        cam = PinholeCameraModel()
        cam.fromCameraInfo(ci)
        print cam.rectifyPoint((0, 0))

        print cam.project3dToPixel((0,0,0))
Esempio n. 2
0
class WorldProjector(object):
    def __init__(self, camera_info, h_matrix, distorted_input=True):
        self.distorted_input = distorted_input
        self.camera_info = camera_info
        self.fisheye = False
        if camera_info.distortion_model == "fisheye":
            self.camera = FisheyeCameraModel()
            self.camera.from_camera_info(camera_info)
            self.fisheye = True
        else:
            self.camera = PinholeCameraModel()
            self.camera.fromCameraInfo(camera_info)
        self.h_matrix = h_matrix


    def pixel2ground(self, pixel_coord, distorted_input=None):
        """Returns the ground projection of a pixel."""
        # use default value if 'distorted_input' is not set
        if distorted_input is None:
            distorted_input = self.distorted_input
        if distorted_input:
            pixel_coord = self.rectify_pixel(pixel_coord)
        pixel_coord = np.array(pixel_coord)

        # normalize coordinates
        pixel_normalized = normalize_image_coordinates(
            pixel_coord,
            self.camera.width,
            self.camera.height)

        point = self._project_normalized_pixel(pixel_normalized)
        return point

    def _project_normalized_pixel(self, pixel_normalized):
        # create homogeneous coordinate
        pixel_coord = np.append(pixel_normalized, 1.0)
        # project to world
        ground_coord = np.dot(self.h_matrix, pixel_coord)
        # scale the coordinates appropriately -> transform to eucledian space
        ground_coord = ground_coord / ground_coord[2]

        return ground_coord


    def rectify_pixel(self, pixel_coord):
        """Calculates the rectified undistorted image coordinate of a pixel.

        Args:
            pixel_coord: Distorted pixel coordinates

        Returns: Undistorted pixel coordinates
        """
        if self.fisheye:
            return self.camera.undistort_point(pixel_coord)
        else:
            return self.camera.rectifyPoint(pixel_coord)
Esempio n. 3
0
 def make_pixel_to_3d(self):
     cm = PinholeCameraModel()
     cm.fromCameraInfo(self.camera_info)
     self.p3d = np.zeros(
         [self.camera_info.height, self.camera_info.width, 3])
     for v in range(0, self.camera_info.height):
         for u in range(0, self.camera_info.width):
             uv_rect = cm.rectifyPoint((u, v))
             p = cm.projectPixelTo3dRay(uv_rect)
             self.p3d[v, u, :] = (p[0] / p[2], p[1] / p[2], 1.0
                                  )  # make homogeneous
Esempio n. 4
0
class GroundProjection():
    def __init__(self, robot_name="neo"):

        # defaults overwritten by param
        self.robot_name = robot_name
        self.rectified_input = False

        # Load homography
        self.H = self.load_homography()
        self.Hinv = np.linalg.inv(self.H)

        self.pcm_ = PinholeCameraModel()

        # Load checkerboard information
        self.board_ = self.load_board_info()

    # wait until we have recieved the camera info message through ROS and then initialize
    def initialize_pinhole_camera_model(self, camera_info):
        self.ci_ = camera_info
        self.pcm_.fromCameraInfo(camera_info)
        print("pinhole camera model initialized")

    def vector2pixel(self, vec):
        pixel = Pixel()
        cw = self.ci_.width
        ch = self.ci_.height
        pixel.u = cw * vec.x
        pixel.v = ch * vec.y
        if (pixel.u < 0): pixel.u = 0
        if (pixel.u > cw - 1): pixel.u = cw - 1
        if (pixel.v < 0): pixel.v = 0
        if (pixel.v > ch - 1): pixel.v = 0
        return pixel

    def pixel2vector(self, pixel):
        vec = Vector2D()
        vec.x = pixel.u / self.ci_.width
        vec.y = pixel.v / self.ci_.height
        return vec

    def vector2ground(self, vec):
        pixel = self.vector2pixel(vec)
        return self.pixel2ground(pixel)

    def ground2vector(self, point):
        pixel = self.ground2pixel(point)
        return self.pixel2vector(pixel)

    def pixel2ground(self, pixel):
        uv_raw = np.array([pixel.u, pixel.v])
        if not self.rectified_input:
            uv_raw = self.pcm_.rectifyPoint(uv_raw)
        #uv_raw = [uv_raw, 1]
        uv_raw = np.append(uv_raw, np.array([1]))
        ground_point = np.dot(self.H, uv_raw)
        point = Point()
        x = ground_point[0]
        y = ground_point[1]
        z = ground_point[2]
        point.x = x / z
        point.y = y / z
        point.z = 0.0
        return point

    def ground2pixel(self, point):
        ground_point = np.array([point.x, point.y, 1.0])
        image_point = np.dot(self.Hinv, ground_point)
        image_point = image_point / image_point[2]

        pixel = Pixel()
        if not self.rectified_input:
            distorted_pixel = self.pcm_.project3dToPixel(image_point)
            pixel.u = distorted_pixel[0]
            pixel.v = distorted_pixel[1]
        else:
            pixel.u = image_point[0]
            pixel.v = image_point[1]

    def rectify(self, cv_image_raw):
        '''Undistort image'''
        cv_image_rectified = np.zeros(np.shape(cv_image_raw))
        mapx = np.ndarray(shape=(self.pcm_.height, self.pcm_.width, 1),
                          dtype='float32')
        mapy = np.ndarray(shape=(self.pcm_.height, self.pcm_.width, 1),
                          dtype='float32')
        mapx, mapy = cv2.initUndistortRectifyMap(
            self.pcm_.K, self.pcm_.D, self.pcm_.R, self.pcm_.P,
            (self.pcm_.width, self.pcm_.height), cv2.CV_32FC1, mapx, mapy)
        return cv2.remap(cv_image_raw, mapx, mapy, cv2.INTER_CUBIC,
                         cv_image_rectified)

    def estimate_homography(self, cv_image):
        '''Estimate ground projection using instrinsic camera calibration parameters'''
        cv_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
        cv_image_rectified = self.rectify(cv_image)
        logger.info("image rectified")

        ret, corners = cv2.findChessboardCorners(
            cv_image_rectified, (self.board_['width'], self.board_['height']))
        if ret == False:
            logger.error("No corners found in image")
            exit(1)
        if len(corners) != self.board_['width'] * self.board_['height']:
            logger.error("Not all corners found in image")
            exit(2)
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30,
                    0.01)
        corners2 = cv2.cornerSubPix(cv_image_rectified, corners, (11, 11),
                                    (-1, -1), criteria)

        #TODO flip checks
        src_pts = []
        for r in range(self.board_['height']):
            for c in range(self.board_['width']):
                src_pts.append(
                    np.array([
                        r * self.board_['square_size'], c *
                        self.board_['square_size']
                    ],
                             dtype='float32') + self.board_['offset'])
        # OpenCV labels corners left-to-right, top-to-bottom
        # We're having a problem with our pattern since it's not rotation-invariant

        # only reverse order if first point is at bottom right corner
        if ((corners[0])[0][0] <
            (corners[self.board_['width'] * self.board_['height'] - 1])[0][0]
                and (corners[0])[0][0] <
            (corners[self.board_['width'] * self.board_['height'] - 1])[0][1]):
            rospy.loginfo("Reversing order of points.")
            src_pts.reverse()

        # Compute homography from image to ground
        self.H, mask = cv2.findHomography(corners2.reshape(len(corners2), 2),
                                          np.array(src_pts), cv2.RANSAC)
        extrinsics_filename = sys.path[
            0] + "/calibrations/camera_extrinsic/" + self.robot_name + ".yaml"
        self.write_homography(extrinsics_filename)
        logger.info(
            "Wrote ground projection to {}".format(extrinsics_filename))

        # Check if specific point in matrix is larger than zero (this would definitly mean we're having a corrupted rotation matrix)
        if (self.H[1][2] > 0):
            rospy.logerr("WARNING: Homography could be corrupt!")

    def load_homography(self):
        '''Load homography (extrinsic parameters)'''
        filename = (sys.path[0] + "/calibrations/camera_extrinsic/" +
                    self.robot_name + ".yaml")
        if not os.path.isfile(filename):
            logger.warn(
                "no extrinsic calibration parameters for {}, trying default".
                format(self.robot_name))
            filename = (sys.path[0] +
                        "/calibrations/camera_extrinsic/default.yaml")
            if not os.path.isfile(filename):
                logger.error("can't find default either, something's wrong")
            else:
                data = yaml_load_file(filename)
        else:
            rospy.loginfo("Using extrinsic calibration of " + self.robot_name)
            data = yaml_load_file(filename)
        logger.info("Loaded homography for {}".format(
            os.path.basename(filename)))
        return np.array(data['homography']).reshape((3, 3))

    def write_homography(self, filename):
        ob = {'homography': sum(self.H.reshape(9, 1).tolist(), [])}
        print ob
        yaml_write_to_file(ob, filename)

    def load_board_info(self, filename=''):
        '''Load calibration checkerboard info'''
        if not os.path.isfile(filename):
            filename = get_ros_package_path(
                'duckietown'
            ) + '/config/baseline/ground_projection/ground_projection/default.yaml'
        target_data = yaml_load_file(filename)
        target_info = {
            'width': target_data['board_w'],
            'height': target_data['board_h'],
            'square_size': target_data['square_size'],
            'x_offset': target_data['x_offset'],
            'y_offset': target_data['y_offset'],
            'offset':
            np.array([target_data['x_offset'], -target_data['y_offset']]),
            'size': (target_data['board_w'], target_data['board_h']),
        }
        logger.info("Loaded checkerboard parameters")
        return target_info

#######################################################

#                   OLD STUFF                         #

#######################################################

    def load_camera_info(self):
        '''Load camera intrinsics'''
        filename = (sys.path[0] + "/calibrations/camera_intrinsic/" +
                    self.robot_name + ".yaml")
        if not os.path.isfile(filename):
            logger.warn(
                "no intrinsic calibration parameters for {}, trying default".
                format(self.robot_name))
            filename = (sys.path[0] +
                        "/calibrations/camera_intrinsic/default.yaml")
            if not os.path.isfile(filename):
                logger.error("can't find default either, something's wrong")
        calib_data = yaml_load_file(filename)
        #     logger.info(yaml_dump(calib_data))
        cam_info = CameraInfo()
        cam_info.width = calib_data['image_width']
        cam_info.height = calib_data['image_height']
        cam_info.K = np.array(calib_data['camera_matrix']['data']).reshape(
            (3, 3))
        cam_info.D = np.array(
            calib_data['distortion_coefficients']['data']).reshape((1, 5))
        cam_info.R = np.array(
            calib_data['rectification_matrix']['data']).reshape((3, 3))
        cam_info.P = np.array(calib_data['projection_matrix']['data']).reshape(
            (3, 4))
        cam_info.distortion_model = calib_data['distortion_model']
        logger.info(
            "Loaded camera calibration parameters for {} from {}".format(
                self.robot_name, os.path.basename(filename)))
        return cam_info

    def _load_homography(self, filename):
        data = yaml_load_file(filename)
        return np.array(data['homography']).reshape((3, 3))

    def _load_camera_info(self, filename):
        calib_data = yaml_load_file(filename)
        #     logger.info(yaml_dump(calib_data))
        cam_info = CameraInfo()
        cam_info.width = calib_data['image_width']
        cam_info.height = calib_data['image_height']
        cam_info.K = np.array(calib_data['camera_matrix']['data']).reshape(
            (3, 3))
        cam_info.D = np.array(
            calib_data['distortion_coefficients']['data']).reshape((1, 5))
        cam_info.R = np.array(
            calib_data['rectification_matrix']['data']).reshape((3, 3))
        cam_info.P = np.array(calib_data['projection_matrix']['data']).reshape(
            (3, 4))
        cam_info.distortion_model = calib_data['distortion_model']
        return cam_info

    def _rectify(self, cv_image_raw):
        '''old'''
        #cv_image_rectified = cvMat()
        cv_image_rectified = np.zeros(np.shape(cv_image_raw))
        # TODO: debug PinholeCameraModel()
        self.pcm_.rectifyImage(cv_image_raw, cv_image_rectified)
        return cv_image_rectified
Esempio n. 5
0
class GroundProjection():

    def __init__(self, robot_name="birdbot0"):

        # defaults overwritten by param
        self.robot_name = robot_name
        self.rectified_input = False

        # Load homography
        self.H = self.load_homography()
        self.Hinv = np.linalg.inv(self.H)

        self.pcm_ = PinholeCameraModel()

        # Load checkerboard information
        #self.board_ = self.load_board_info()

    # wait until we have received the camera info message through ROS and then initialize
    def initialize_pinhole_camera_model(self,camera_info):
        self.ci_=camera_info
        self.pcm_.fromCameraInfo(camera_info)
        print("pinhole camera model initialized")

    def vector2pixel(self, vec):
        pixel = Pixel()
        cw = self.ci_.width
        ch = self.ci_.height
        pixel.u = int(cw * vec.x)
        pixel.v = int(ch * vec.y)
        if (pixel.u < 0): pixel.u = 0
        if (pixel.u > cw -1): pixel.u = cw - 1
        if (pixel.v < 0): pixel.v = 0
        if (pixel.v > ch - 1): pixel.v = 0
        return pixel

    def pixel2vector(self, pixel):
        vec = Vector2D()
        vec.x = pixel.u / self.ci_.width
        vec.y = pixel.v / self.ci_.height
        return vec

    def vector2ground(self, vec):
        pixel = self.vector2pixel(vec)
        return self.pixel2ground(pixel)

    def ground2vector(self, point):
        pixel = self.ground2pixel(point)
        return self.pixel2vector(pixel)

    def pixel2ground(self,pixel):
        uv_raw = np.array([pixel.u, pixel.v])
        if not self.rectified_input:
            uv_raw = self.pcm_.rectifyPoint(uv_raw)
        #uv_raw = [uv_raw, 1]
        uv_raw = np.append(uv_raw, np.array([1]))
        ground_point = np.dot(self.H, uv_raw)
        point = Point()
        x = ground_point[0]
        y = ground_point[1]
        z = ground_point[2]
        point.x = x/z
        point.y = y/z
        point.z = 0.0
        return point

    def ground2pixel(self, point):
        ground_point = np.array([point.x, point.y, 1.0])
        image_point = np.dot(self.Hinv, ground_point)
        image_point = image_point / image_point[2]


        pixel = Pixel()
        if not self.rectified_input:
            distorted_pixel = self.pcm_.project3dToPixel(image_point)
            pixel.u = distorted_pixel[0]
            pixel.v = distorted_pixel[1]
        else:
            pixel.u = image_point[0]
            pixel.v = image_point[1]

    def rectify(self, cv_image_raw):
        '''Undistort image'''
        cv_image_rectified = np.zeros(np.shape(cv_image_raw))
        mapx = np.ndarray(shape=(self.pcm_.height, self.pcm_.width, 1), dtype='float32')
        mapy = np.ndarray(shape=(self.pcm_.height, self.pcm_.width, 1), dtype='float32')
        mapx, mapy = cv2.initUndistortRectifyMap(self.pcm_.K, self.pcm_.D, self.pcm_.R, self.pcm_.P, (self.pcm_.width, self.pcm_.height), cv2.CV_32FC1, mapx, mapy)
        return cv2.remap(cv_image_raw, mapx, mapy, cv2.INTER_CUBIC, cv_image_rectified)

    def load_homography(self):
        '''Load homography (extrinsic parameters)'''
        """
        filename = (get_duckiefleet_root() + "/calibrations/camera_extrinsic/" + self.robot_name + ".yaml")
        if not os.path.isfile(filename):
            #logger.warn("no extrinsic calibration parameters for {}, trying default".format(self.robot_name))
            filename = (get_duckiefleet_root() + "/calibrations/camera_extrinsic/default.yaml")
            if not os.path.isfile(filename):
                #logger.error("can't find default either, something's wrong")
            else:
                data = yaml_load_file(filename)
        else:
            #rospy.loginfo("Using extrinsic calibration of " + self.robot_name)
            data = yaml_load_file(filename)
        #logger.info("Loaded homography for {}".format(os.path.basename(filename)))
        return np.array(data['homography']).reshape((3,3))
        """

        # hardcorded birdbot0 homography
        #return np.array([[-1.3138222289537473e-05,-0.00016799247320246641, -0.18508764249409215],
        #                 [0.0008702503150508597, -1.314730233433855e-06, -0.2779744199471874],
        #                 [-7.940529271577331e-05,-0.006045110837995103,1.0]])
        return np.array([[-5.987554218305854e-05,-0.00012542467699075597, -0.183339048091576],
                    [0.0008439191581241052, -3.098547600170272e-05, -0.28159137198032724],
                    [-0.00015406069103711482,-0.006343978853492832, 0.9999999999999999]])
Esempio n. 6
0
class GroundProjector(object):
    def __init__(self, camera_info, h_matrix, distorted_input=True):
        """
        The GroundProjector can rectify pixels or the whole image and the
        ground coordinates of a pixel by using the homography matrix H

        Args:
            camera_info:
            h_matrix: Homography matrix, that maps normalized undistorted pixel
                coordinates into the ground plane.
            distorted_input: Should only be False if simulation without
                simulated camera distortion is used.
        """
        self.distorted_input = distorted_input
        self.camera_info = camera_info
        self.fisheye = False
        if camera_info.distortion_model == "fisheye":
            self.camera = FisheyeCameraModel()
            self.camera.from_camera_info(camera_info)
            self.fisheye = True
        else:
            self.camera = PinholeCameraModel()
            self.camera.fromCameraInfo(camera_info)
        self.h_matrix = h_matrix

    def pixel2ground(self, pixel_coord, distorted_input=None):
        """Returns the ground projection of a pixel.

        For distorted input the undistorted coordinates of the pixel will be
        calculcated. Afterwards the pixel will get noramlized and projected to
        the ground plane by applying the homography matrix.

        Args:
            pixel_coord (tuple/list/numpy.ndarray): Pixel coordinates of the
                point that will be projected into the ground plane.
            distorted_input (bool): Can be set to override the default value
                defined when creating the instance.

        Returns: Coordinates of the pixel projected into the ground plane
            expressed in the vehicles coordinate system.

        """
        # use default value if 'distorted_input' is not set
        if distorted_input is None:
            distorted_input = self.distorted_input
        if distorted_input:
            pixel_coord = self.rectify_pixel(pixel_coord)
        pixel_coord = np.array(pixel_coord)

        # normalize coordinates
        pixel_normalized = normalize_image_coordinates(
            pixel_coord,
            self.camera.width,
            self.camera.height)

        point = self._fake_project(pixel_normalized)

        return point

    def _project_normalized_pixel(self, pixel_normalized):
        """ TODO: WRITE CODE TO COMPUTE THE PIXEL'S CORRESPONDING GROUND PLANE POINT

        Args:
            pixel_normalized: Normalized pixel coordinate

        Returns: 3D-coordinates of the projection expressed in the vehicle's
            frame

        """
        point = Point32()

        # YOUR CALCULATION NEEDS TO BE DONE HERE. REPLACE THE ASSIGNMENT
        # OF THE POINT'S COORDINATES BY REASONABLE ASSIGNMENTS OF YOUR GROUND
        # PROJECTION.
        point.x = 0.0
        point.y = 0.0
        point.z = 0.0

        return point

    def _fake_project(self, pixel_normalized):
        """
        THIS METHOD IS JUST A DUMMY. REPLACE ALL CALLS OF THIS METHOD BY
        '_project_normalized_pixel()'

        Args:
            pixel_normalized:

        Returns:

        """
        point = Point32()
        point.x = 1.0 - pixel_normalized[1]
        point.y = 0.5 - pixel_normalized[0]
        point.z = 0.0
        return point

    def rectify_pixel(self, pixel_coord):
        """Calculates the rectified undistorted image coordinate of a pixel.

        Args:
            pixel_coord: Distorted pixel coordinates

        Returns: Undistorted pixel coordinates
        """
        if self.fisheye:
            return self.camera.undistort_point(pixel_coord)
        else:
            return self.camera.rectifyPoint(pixel_coord)

    def rectify_image(self, img):
        if self.fisheye:
            return self.camera.undistort_image(img)
        else:
            output = np.empty_like(img)
            self.camera.rectifyImage(img, output)
            return output
Esempio n. 7
0
class Rectify:
    """
    Handles the Rectification operations.

    """

    ci: CameraInfo
    pcm: PinholeCameraModel
    _rectify_inited: bool
    _distort_inited: bool

    rmapx: np.ndarray
    rmapy: np.ndarray

    def __init__(self, camera_info: CameraInfo):
        self.ci = camera_info
        self.pcm = PinholeCameraModel()
        self.pcm.fromCameraInfo(self.ci)
        self._rectify_inited = False
        self._distort_inited = False

    def rectify_point(self, pixel: Point) -> Point:
        p = (pixel.x, pixel.y)
        return Point(*list(self.pcm.rectifyPoint(p)))

    def _init_rectify_maps(self):
        W = self.pcm.width
        H = self.pcm.height
        mapx = np.ndarray(shape=(H, W, 1), dtype="float32")
        mapy = np.ndarray(shape=(H, W, 1), dtype="float32")
        mapx, mapy = cv2.initUndistortRectifyMap(self.pcm.K, self.pcm.D,
                                                 self.pcm.R, self.pcm.P,
                                                 (W, H), cv2.CV_32FC1, mapx,
                                                 mapy)
        self.mapx = mapx
        self.mapy = mapy
        self._rectify_inited = True

    def rectify(self,
                cv_image_raw: np.ndarray,
                interpolation=cv2.INTER_NEAREST):
        """Undistort an image.
        To be more precise, pass interpolation= cv2.INTER_CUBIC
        """
        if not self._rectify_inited:
            self._init_rectify_maps()
        #
        #        inter = cv2.INTER_NEAREST  # 30 ms
        #         inter = cv2.INTER_CUBIC # 80 ms
        #         cv_image_rectified = np.zeros(np.shape(cv_image_raw))
        cv_image_rectified = np.empty_like(cv_image_raw)
        res = cv2.remap(cv_image_raw, self.mapx, self.mapy, interpolation,
                        cv_image_rectified)
        return res

    def distort(self, rectified: np.ndarray) -> np.ndarray:

        if not self._rectify_inited:
            self._init_rectify_maps()
        if not self._distort_inited:
            self.rmapx, self.rmapy = invert_map(self.mapx, self.mapy)
            self._distort_inited = True
        distorted = np.zeros(np.shape(rectified))
        res = cv2.remap(rectified, self.rmapx, self.rmapy, cv2.INTER_NEAREST,
                        distorted)
        return res

    def rectify_full(self,
                     cv_image_raw: np.ndarray,
                     interpolation=cv2.INTER_NEAREST,
                     ratio=1):
        """
        Undistort an image by maintaining the proportions.
        To be more precise, pass interpolation= cv2.INTER_CUBIC
        Returns the new camera matrix as well.
        """
        W = int(self.pcm.width * ratio)
        H = int(self.pcm.height * ratio)
        #        mapx = np.ndarray(shape=(H, W, 1), dtype='float32')
        #        mapy = np.ndarray(shape=(H, W, 1), dtype='float32')
        print(f"K: {self.pcm.K}")
        print(f"P: {self.pcm.P}")

        #        alpha = 1
        #        new_camera_matrix, validPixROI = cv2.getOptimalNewCameraMatrix(self.pcm.K, self.pcm.D, (H,
        #        W), alpha)
        #        print('validPixROI: %s' % str(validPixROI))

        # Use the same camera matrix
        new_camera_matrix = self.pcm.K.copy()
        new_camera_matrix[0, 2] = W / 2
        new_camera_matrix[1, 2] = H / 2
        print(f"new_camera_matrix: {new_camera_matrix}")
        mapx, mapy = cv2.initUndistortRectifyMap(self.pcm.K, self.pcm.D,
                                                 self.pcm.R, new_camera_matrix,
                                                 (W, H), cv2.CV_32FC1)
        cv_image_rectified = np.empty_like(cv_image_raw)
        res = cv2.remap(cv_image_raw, mapx, mapy, interpolation,
                        cv_image_rectified)
        return new_camera_matrix, res
class Rectify:
    """
    Handles the Rectification operations.

    """

    def __init__(self, camera_info):
        self.ci = camera_info
        self.pcm = PinholeCameraModel()
        self.pcm.fromCameraInfo(self.ci)
        self._rectify_inited = False
        self._distort_inited = False



    def rectify_point(self, pixel):
        return Point(*list(self.pcm.rectifyPoint([pixel.x, pixel.y])))

    def _init_rectify_maps(self):
        W = self.pcm.width
        H = self.pcm.height
        mapx = np.ndarray(shape=(H, W, 1), dtype='float32')
        mapy = np.ndarray(shape=(H, W, 1), dtype='float32')
        mapx, mapy = cv2.initUndistortRectifyMap(self.pcm.K, self.pcm.D, self.pcm.R,
                                                 self.pcm.P, (W, H),
                                                 cv2.CV_32FC1, mapx, mapy)
        self.mapx = mapx
        self.mapy = mapy
        self._rectify_inited = True

    def rectify(self, cv_image_raw, interpolation=cv2.INTER_NEAREST):
        ''' Undistort an image.
            To be more precise, pass interpolation= cv2.INTER_CUBIC
        '''
        if not self._rectify_inited:
            self._init_rectify_maps()
        #
        #        inter = cv2.INTER_NEAREST  # 30 ms
        #         inter = cv2.INTER_CUBIC # 80 ms
        #         cv_image_rectified = np.zeros(np.shape(cv_image_raw))
        cv_image_rectified = np.empty_like(cv_image_raw)
        res = cv2.remap(cv_image_raw, self.mapx, self.mapy, interpolation,
                        cv_image_rectified)
        return res

    def distort(self, rectified):
        if not self._rectify_inited:
            self._init_rectify_maps()
        if not self._distort_inited:
            self.rmapx, self.rmapy = invert_map(self.mapx, self.mapy)
            self._distort_inited = True
        distorted = np.zeros(np.shape(rectified))
        res = cv2.remap(rectified, self.rmapx, self.rmapy, cv2.INTER_NEAREST, distorted)
        return res

    def rectify_full(self, cv_image_raw, interpolation=cv2.INTER_NEAREST, ratio=1):
        '''
            Undistort an image by maintaining the proportions.
            To be more precise, pass interpolation= cv2.INTER_CUBIC
            Returns the new camera matrix as well.
        '''
        W = int(self.pcm.width * ratio)
        H = int(self.pcm.height * ratio)
        #        mapx = np.ndarray(shape=(H, W, 1), dtype='float32')
        #        mapy = np.ndarray(shape=(H, W, 1), dtype='float32')
        # print('K: %s' % self.pcm.K)
        # print('P: %s' % self.pcm.P)

        #        alpha = 1
        #        new_camera_matrix, validPixROI = cv2.getOptimalNewCameraMatrix(self.pcm.K, self.pcm.D, (H, W), alpha)
        #        print('validPixROI: %s' % str(validPixROI))

        # Use the same camera matrix
        new_camera_matrix = self.pcm.K.copy()
        new_camera_matrix[0, 2] = W / 2
        new_camera_matrix[1, 2] = H / 2
        # print('new_camera_matrix: %s' % new_camera_matrix)
        mapx, mapy = cv2.initUndistortRectifyMap(self.pcm.K, self.pcm.D, self.pcm.R,
                                                 new_camera_matrix, (W, H),
                                                 cv2.CV_32FC1)
        cv_image_rectified = np.empty_like(cv_image_raw)
        res = cv2.remap(cv_image_raw, mapx, mapy, interpolation,
                        cv_image_rectified)
        return new_camera_matrix, res

    def invert_map(mapx, mapy):
        H, W = mapx.shape[0:2]
        rmapx = np.empty_like(mapx)
        rmapx.fill(np.nan)
        rmapy = np.empty_like(mapx)
        rmapy.fill(np.nan)

        for y, x in itertools.product(range(H), range(W)):
            tx = mapx[y, x]
            ty = mapy[y, x]

            tx = int(np.round(tx))
            ty = int(np.round(ty))

            if (0 <= tx < W) and (0 <= ty < H):
                rmapx[ty, tx] = x
                rmapy[ty, tx] = y

        # fill holes
        #     if False:

        fill_holes(rmapx, rmapy)

        #     D = 4
        #     for y, x in itertools.product(range(H), range(W)):
        #         v0 = max(y-D, 0)
        #         v1 = max(y+D, H-1)
        #         u0 = max(x-D, 0)
        #         u1 = max(x+D, W-1)
        #
        #         rmapx[y,x] = np.median(rmapx[v0:v1,u0:u1].flatten())
        #         rmapy[y,x] = np.median(rmapy[v0:v1,u0:u1].flatten())

        return rmapx, rmapy

    def fill_holes(rmapx, rmapy):
        H, W = rmapx.shape[0:2]

        nholes = 0

        R = 2
        F = R * 2 + 1

        def norm(x):
            return np.hypot(x[0], x[1])

        deltas0 = [(i - R - 1, j - R - 1) for i, j in itertools.product(range(F), range(F))]
        deltas0 = [x for x in deltas0 if norm(x) <= R]
        deltas0.sort(key=norm)

        def get_deltas():
            #         deltas = list(deltas0)
            #
            return deltas0

        holes = set()

        for i, j in itertools.product(range(H), range(W)):
            if np.isnan(rmapx[i, j]):
                holes.add((i, j))

        while holes:
            nholes = len(holes)
            nholes_filled = 0

            for i, j in list(holes):
                # there is nan
                nholes += 1
                for di, dj in get_deltas():
                    u = i + di
                    v = j + dj
                    if (0 <= u < H) and (0 <= v < W):
                        if not np.isnan(rmapx[u, v]):
                            rmapx[i, j] = rmapx[u, v]
                            rmapy[i, j] = rmapy[u, v]
                            nholes_filled += 1
                            holes.remove((i, j))
                            break

            #         print('holes %s filled: %s' % (nholes, nholes_filled))
            if nholes_filled == 0:
                break
class GroundProjectionGeometry(object):
    """

        This class only knows about geometry, but not configuration files.

        Conventions and coordinate frames:

            "vector"    Vector2D   (x, y)        normalized image coordinates in rectified image
            "pixel"     Pixel      (u, v)        pixel coordinates
            "ground"    Point      (x, y, z=0)   axle frame

        A previous version of the code allowed for a hidden flag
        to specify whether the points were rectified or not.

        Now, "vector" is always rectified.
    """
    @dtu.contract(camera_info=CameraInfo, homography='array[3x3]')
    def __init__(self, camera_info, homography):
        self.ci = camera_info
        self.H = homography
        self.Hinv = np.linalg.inv(self.H)

        self.pcm = PinholeCameraModel()
        self.pcm.fromCameraInfo(self.ci)

        self._rectify_inited = False
        self._distort_inited = False

    def get_camera_info(self):
        return self.ci
#         # XXX: this needs to be removed
#         self.rectified_input = False

    @dtu.contract(vec=Vector2D, returns=Pixel)
    def vector2pixel(self, vec):
        """ Converts a [0,1]*[0,1] representation to [0, W]x[0, H]. """
        pixel = Pixel()
        cw = self.ci.width
        ch = self.ci.height
        pixel.u = cw * vec.x
        pixel.v = ch * vec.y
        return pixel

    @dtu.contract(pixel=Pixel, returns=Vector2D)
    def pixel2vector(self, pixel):
        """ Converts a [0,W]*[0,H] representation to [0, 1]x[0, 1]. """
        x = pixel.u / self.ci.width
        y = pixel.v / self.ci.height
        return Vector2D(x, y)

    @dtu.contract(vec=Vector2D, returns=Point)
    def vector2ground(self, vec):
        """ Converts normalized coordinates to ground plane """
        pixel = self.vector2pixel(vec)
        return self.pixel2ground(pixel)

    @dtu.contract(point=Point, returns=Pixel)  # NO
    def ground2vector(self, point):
        pixel = self.ground2pixel(point)
        return self.pixel2vector(pixel)

    @dtu.contract(pixel=Pixel, returns=Point)
    def pixel2ground(self, pixel):
        uv_raw = np.array([pixel.u, pixel.v])
        #         if not self.rectified_input:
        #             uv_raw = self.pcm.rectifyPoint(uv_raw)
        #uv_raw = [uv_raw, 1]
        uv_raw = np.append(uv_raw, np.array([1]))
        ground_point = np.dot(self.H, uv_raw)
        point = Point()
        x = ground_point[0]
        y = ground_point[1]
        z = ground_point[2]
        point.x = x / z
        point.y = y / z
        point.z = 0.0
        return point

    @dtu.contract(point=Point, returns=Pixel)
    def ground2pixel(self, point):
        if point.z != 0:
            msg = 'This method assumes that the point is a ground point (z=0). '
            msg += 'However, the point is (%s,%s,%s)' % (point.x, point.y,
                                                         point.z)
            raise ValueError(msg)

        ground_point = np.array([point.x, point.y, 1.0])
        # An applied mathematician would cry for this
        #    image_point = np.dot(self.Hinv, ground_point)
        # A better way:
        image_point = np.linalg.solve(self.H, ground_point)

        image_point = image_point / image_point[2]

        pixel = Pixel()
        #         if not self.rectified_input:
        #             dtu.logger.debug('project3dToPixel')
        #             distorted_pixel = self.pcm.project3dToPixel(image_point)
        #             pixel.u = distorted_pixel[0]
        #             pixel.v = distorted_pixel[1]
        #         else:
        pixel.u = image_point[0]
        pixel.v = image_point[1]

        return pixel

    def rectify_point(self, p):
        res1 = self.pcm.rectifyPoint(p)
        #
        #         pcm = self.pcm
        #         point = np.zeros((2, 1))
        #         point[0] = p[0]
        #         point[1] = p[1]
        #
        #         res2 = cv2.undistortPoints(point.T, pcm.K, pcm.D, R=pcm.R, P=pcm.P)
        #         print res1, res2
        return res1

    def _init_rectify_maps(self):
        W = self.pcm.width
        H = self.pcm.height
        mapx = np.ndarray(shape=(H, W, 1), dtype='float32')
        mapy = np.ndarray(shape=(H, W, 1), dtype='float32')
        mapx, mapy = cv2.initUndistortRectifyMap(self.pcm.K, self.pcm.D,
                                                 self.pcm.R, self.pcm.P,
                                                 (W, H), cv2.CV_32FC1, mapx,
                                                 mapy)
        self.mapx = mapx
        self.mapy = mapy
        self._rectify_inited = True

    def rectify(self, cv_image_raw, interpolation=cv2.INTER_NEAREST):
        ''' Undistort an image.

            To be more precise, pass interpolation= cv2.INTER_CUBIC
        '''
        if not self._rectify_inited:
            self._init_rectify_maps()


#
#        inter = cv2.INTER_NEAREST  # 30 ms
#         inter = cv2.INTER_CUBIC # 80 ms
#         cv_image_rectified = np.zeros(np.shape(cv_image_raw))
        cv_image_rectified = np.empty_like(cv_image_raw)
        res = cv2.remap(cv_image_raw, self.mapx, self.mapy, interpolation,
                        cv_image_rectified)
        return res

    def distort(self, rectified):
        if not self._rectify_inited:
            self._init_rectify_maps()
        if not self._distort_inited:
            self.rmapx, self.rmapy = invert_map(self.mapx, self.mapy)
            self._distort_inited = True
        distorted = np.zeros(np.shape(rectified))
        res = cv2.remap(rectified, self.rmapx, self.rmapy, cv2.INTER_NEAREST,
                        distorted)
        return res

    def rectify_full(self,
                     cv_image_raw,
                     interpolation=cv2.INTER_NEAREST,
                     ratio=1):
        '''

            Undistort an image by maintaining the proportions.

            To be more precise, pass interpolation= cv2.INTER_CUBIC

            Returns the new camera matrix as well.
        '''
        W = int(self.pcm.width * ratio)
        H = int(self.pcm.height * ratio)
        #        mapx = np.ndarray(shape=(H, W, 1), dtype='float32')
        #        mapy = np.ndarray(shape=(H, W, 1), dtype='float32')
        print('K: %s' % self.pcm.K)
        print('P: %s' % self.pcm.P)

        #        alpha = 1
        #        new_camera_matrix, validPixROI = cv2.getOptimalNewCameraMatrix(self.pcm.K, self.pcm.D, (H, W), alpha)
        #        print('validPixROI: %s' % str(validPixROI))

        # Use the same camera matrix
        new_camera_matrix = self.pcm.K.copy()
        new_camera_matrix[0, 2] = W / 2
        new_camera_matrix[1, 2] = H / 2
        print('new_camera_matrix: %s' % new_camera_matrix)
        mapx, mapy = cv2.initUndistortRectifyMap(self.pcm.K, self.pcm.D,
                                                 self.pcm.R, new_camera_matrix,
                                                 (W, H), cv2.CV_32FC1)
        cv_image_rectified = np.empty_like(cv_image_raw)
        res = cv2.remap(cv_image_raw, mapx, mapy, interpolation,
                        cv_image_rectified)
        return new_camera_matrix, res
Esempio n. 10
0
class BodyEstimator3D(object):
    def __init__(self, camera_info):
        self.pinhole_camera_model = PinholeCameraModel()
        self.pinhole_camera_model.fromCameraInfo(camera_info)
        self.body_tracker = SequentialTracker()
        self.kalman_filter = {}  # TODO
        self.image_resolution = np.array(
            [camera_info.width, camera_info.height])
        self.bound_x = [0, self.image_resolution[0] - 1]
        self.bound_y = [0, self.image_resolution[1] - 1]
        self.kernel_size = 7
        self.part_id_to_exclude = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

    def estimate(self, bodies2D, np_depth_img):
        filtered_bodies2D = [
            self.__filter_body_parts(body2D) for body2D in bodies2D
        ]

        tracked_bodies3D = self.__project_body_on_disparity(
            filtered_bodies2D, np_depth_img)

        # track the bodies regarding the last callback
        self.body_tracker.track(tracked_bodies3D)
        tracked_bodies3D = self.body_tracker.get_tracked_elements()

        # smooth the poses
        #stab_bodies3D = self.kalman_filter.stabilize(tracked_bodies3D)
        return tracked_bodies3D

    def reset(self):
        self.body_tracker.clear_elements()

    def __filter_body_parts(self, body):
        return {
            part_id: part
            for (part_id, part) in body.items()
            if part_id not in self.part_id_to_exclude and part[2] >= 0.5
        }

    def __project_body_on_disparity(self, bodies, np_depth_img):
        tracked_bodies3D = []
        for body in bodies:
            body_dict = {}
            for part_id, part in body.items():
                np_pos3D = self.__project_point2D_on_disparity(
                    part[:2], np_depth_img)
                if np_pos3D is not None:
                    body_dict[part_id] = np.array([np_pos3D, part[2]])
            tracked_bodies3D.append(TrackedBody(body_dict))
        return tracked_bodies3D

    def __project_point2D_on_disparity(self, np_normalized_point,
                                       np_depth_img):
        np_pos = np.multiply(np_normalized_point, self.image_resolution)
        np_rect_pos = np.array(
            self.pinhole_camera_model.rectifyPoint((np_pos[0], np_pos[1])))
        np_vector3D = np.array(
            self.pinhole_camera_model.projectPixelTo3dRay(
                (np_rect_pos[0], np_rect_pos[1])))

        np_clipped_rect_pos = np.clip(np_rect_pos, [0, 0],
                                      self.image_resolution)
        x, y = int(np_clipped_rect_pos[0]), int(np_clipped_rect_pos[1])
        bloc = np_depth_img[y - self.kernel_size:y + self.kernel_size,
                            x - self.kernel_size:x +
                            self.kernel_size]  # TODO: check out of bounds
        nzeros = np.nonzero(bloc)
        if len(nzeros[0]) > 0:
            depth = np.min(bloc[nzeros]) / 1000.0  # scale
            return np_vector3D * depth

        return None