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
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()