Ejemplo n.º 1
0
    def evaluate(self, scene: bpy.types.Scene) -> Dict:
        """Given a scene evaluate the camera pose w.r.t. the ground truth.

        Arguments:
            scene {scene} -- scene, includes the render camera that will be used as ground truth

        Returns:
            Dict -- evaluation result dictionary containing:
                        'position_distance' {float}: position distance (measure unit depends on the scene's unit)
                        'lookat_difference_rad' {float}: non-oriented angle between lookAt vectors, in radians
                        'lookat_difference_deg' {float}: non-oriented angle between lookAt vectors, in degrees
                        'rotation_difference_rad' {float}: angle to align reconstructed camera to gt, in radians
                        'rotation_difference_deg' {float}: angle to align reconstructed camera to gt, in degrees
        """
        # get ground truth
        scene.frame_set(self.frame_number)
        gt_matrix_world = scene.camera.matrix_world
        gt_pos = gt_matrix_world.to_translation()
        gt_rotation = gt_matrix_world.to_quaternion()
        gt_lookat = get_camera_lookat(scene.camera)
        #
        # --- position evaluation
        pos_distance = euclidean_distance(gt_pos, self.position)
        logger.debug("Camera position distance: %f (GT=%s, recon=%s)",
                     pos_distance, gt_pos, self.position)
        #
        # --- look-at evaluation
        # compute the non-oriented angle between look-at vectors (gt and reconstructed)
        cos_theta = (gt_lookat @ self.look_at) / (gt_lookat.length *
                                                  self.look_at.length)
        if cos_theta > 1.0 and cos_theta < 1.1:  # rounding error
            cos_theta = 1.0
        theta_rad = acos(cos_theta)
        theta_deg = degrees(theta_rad)
        logger.debug("Camera look-at: %f deg, %f rad. (GT=%s, recon=%s)",
                     theta_deg, theta_rad, gt_lookat, self.look_at)
        #
        # --- rotation evaluation
        # compute rotation angle to align reconstructed camera to gt
        rot_diff = self.rotation.conjugated() @ gt_rotation
        #rot_diff = self.rotation.rotation_difference(gt_rotation)
        rot_diff_rad = rot_diff.angle
        rot_diff_deg = degrees(rot_diff_rad)
        if rot_diff_deg > 180.0:  # angle in range 0-360, equal to +0-180 or -0-180
            rot_diff_deg = 360.0 - rot_diff_deg
        logger.debug("Camera rotation difference: %f deg (GT=%s, recon=%s)",
                     rot_diff_deg, gt_rotation, self.rotation)
        #
        results = {
            "position_distance": pos_distance,
            "lookat_difference_rad": theta_rad,
            "lookat_difference_deg": theta_deg,
            "rotation_difference_rad": rot_diff_rad,
            "rotation_difference_deg": rot_diff_deg
        }
        return results
Ejemplo n.º 2
0
    def render_complete_callback(scene: bpy.types.Scene) -> None:
        """Callback on frame rendered and saved to file.

        Arguments:
            scene {bpy.types.Scene} -- scene being rendered

        Raises:
            RuntimeError: if something goes wrong with ExifTool
        """
        logger.info("Rendering of frame %s completed.", scene.frame_current)
        scene.frame_set(
            scene.frame_current)  # update current frame to the rendered one
        #
        # --- update EXIF metadata
        ff = scene.render.image_settings.file_format
        if ff in SFMFLOW_OT_render_images._files_with_exif:
            logger.debug("Updating EXIF metadata")

            filepath = scene.render.frame_path(frame=scene.frame_current)
            user_preferences = bpy.context.preferences
            addon_user_preferences_name = (__name__)[:__name__.index('.')]
            addon_prefs = user_preferences.addons[
                addon_user_preferences_name].preferences  # type: AddonPreferences
            exiftool_path = addon_prefs.exiftool_path
            camera_data = scene.camera.data

            # compute 35mm focal length
            fl = camera_data.lens
            fl35 = 43.27 / sqrt(camera_data.sensor_width**2 +
                                camera_data.sensor_height**2) * fl
            res_percent = scene.render.resolution_percentage / 100.

            # build exiftool command
            exiftool_cmd = [
                exiftool_path,
                "-exif:FocalLength={} mm".format(fl),
                "-exif:FocalLengthIn35mmFormat={}".format(int(fl35)),
                "-exif:Model=blender{}".format(int(camera_data.sensor_width)),
                "-exif:FocalPlaneXResolution={}".format(
                    camera_data.sensor_width),
                "-exif:FocalPlaneYResolution={}".format(
                    camera_data.sensor_height),
                "-exif:FocalPlaneResolutionUnit#=4",  # millimeters
                "-exif:ExifImageWidth={}".format(
                    floor(scene.render.resolution_x * res_percent)),
                "-exif:ExifImageHeight={}".format(
                    floor(scene.render.resolution_y * res_percent)),
                "-exif:ExifVersion=0230",  # some pipelines do not work with newer versions
                "-overwrite_original",
                filepath
            ]
            logger.info("Running ExifTool: %s", ' '.join(exiftool_cmd))

            # run exiftool
            try:
                exit_code = run(exiftool_cmd, timeout=5,
                                check=False).returncode
            except TimeoutExpired:
                exit_code = -1
                logger.error("Timeout expired for EXIF metadata update!")
            except Exception as e:  # pylint: disable=broad-except
                logger.error("Exiftool execution exception: %s)", e)
            finally:
                if exit_code != 0:
                    msg = "Failed to set EXIF metadata for rendered frame '{}'".format(
                        filepath)
                    logger.error(msg)
                    raise RuntimeError(msg)
                else:
                    logger.info("Metadata correctly set for frame '%s'",
                                filepath)
        else:
            logger.debug(
                "Skipping EXIF metadata update, not supported by %s format",
                ff)
        #
        # --- save camera pose ground truth
        SFMFLOW_OT_render_images._gt_writer.save_entry_for_current_frame()
        if scene.frame_current == scene.frame_end:
            SFMFLOW_OT_render_images._gt_writer.close()