Skip to content
Snippets Groups Projects
space_clip_editor_refine_solution.py 6.97 KiB
Newer Older
  • Learn to ignore specific revisions
  • # SPDX-License-Identifier: GPL-2.0-or-later
    
    Stephen Leger's avatar
    Stephen Leger committed
    
    
    Stephen Leger's avatar
    Stephen Leger committed
    bl_info = {
        "name": "Refine tracking solution",
        "author": "Stephen Leger",
        "license": "GPL",
    
    Stephen Leger's avatar
    Stephen Leger committed
        "blender": (2, 80, 0),
    
    Stephen Leger's avatar
    Stephen Leger committed
        "location": "Clip Editor > Tools > Solve > Refine Solution",
    
        "description": "Refine motion solution by setting track weight according"
                       " to reprojection error",
    
    Stephen Leger's avatar
    Stephen Leger committed
        "warning": "",
    
        "doc_url": "{BLENDER_MANUAL_URL}/addons/video_tools/refine_tracking.html",
    
        "category": "Video Tools",
    
    Stephen Leger's avatar
    Stephen Leger committed
    }
    
    import bpy
    
    from bpy.types import (
            Operator,
            Panel,
            )
    from bpy.props import FloatProperty
    
    Stephen Leger's avatar
    Stephen Leger committed
    from mathutils import Vector
    
    
    Stephen Leger's avatar
    Stephen Leger committed
    class TRACKING_OP_refine_solution(Operator):
    
        bl_idname = "tracking.refine_solution"
    
    Stephen Leger's avatar
    Stephen Leger committed
        bl_label = "Refine"
    
        bl_description = "Set track weight by error and solve camera motion"
    
    Stephen Leger's avatar
    Stephen Leger committed
        bl_options = {"UNDO"}
    
    Stephen Leger's avatar
    Stephen Leger committed
        @classmethod
        def poll(cls, context):
    
            return (context.area and context.area.spaces and
                    hasattr(context.area.spaces.active, 'clip') and
                    context.area.spaces.active.clip is not None
            )
    
    Stephen Leger's avatar
    Stephen Leger committed
        def execute(self, context):
            error = context.window_manager.TrackingTargetError
            smooth = context.window_manager.TrackingSmooth
            clip = context.area.spaces.active.clip
            try:
                tracking = clip.tracking
                tracks = tracking.tracks
                winx = float(clip.size[0])
                winy = float(clip.size[1])
    
                aspy = 1.0 / tracking.camera.pixel_aspect
    
    Stephen Leger's avatar
    Stephen Leger committed
                start = tracking.reconstruction.cameras[0].frame
    
                end = tracking.reconstruction.cameras[-1].frame
    
    Stephen Leger's avatar
    Stephen Leger committed
            except:
    
    Stephen Leger's avatar
    Stephen Leger committed
            marker_position = Vector()
    
    Stephen Leger's avatar
    Stephen Leger committed
            for frame in range(start, end):
    
    Stephen Leger's avatar
    Stephen Leger committed
                camera = tracking.reconstruction.cameras.find_frame(frame=frame)
    
    Stephen Leger's avatar
    Stephen Leger committed
                if camera is not None:
    
    Stephen Leger's avatar
    Stephen Leger committed
                    camera_invert = camera.matrix.inverted()
    
    Stephen Leger's avatar
    Stephen Leger committed
                else:
                    continue
    
    Stephen Leger's avatar
    Stephen Leger committed
                for track in tracking.tracks:
                    marker = track.markers.find_frame(frame)
                    if marker is None:
                        continue
    
    Stephen Leger's avatar
    Stephen Leger committed
                    # weight incomplete tracks on start and end
                    if frame > start + smooth and frame < end - smooth:
                        for m in track.markers:
                            if not m.mute:
    
    Stephen Leger's avatar
    Stephen Leger committed
                                break
                        for m in reversed(track.markers):
                            if not m.mute:
    
    Stephen Leger's avatar
    Stephen Leger committed
                                break
    
                        dt = min(0.5 * (tend.frame - tstart.frame), smooth)
    
    Stephen Leger's avatar
    Stephen Leger committed
                        if dt > 0:
    
                            t0 = min(1.0, (frame - tstart.frame) / dt)
                            t1 = min(1.0, (tend.frame - frame) / dt)
    
    Stephen Leger's avatar
    Stephen Leger committed
                            tw = min(t0, t1)
                        else:
                            tw = 0.0
                    else:
                        tw = 1.0
    
    Stephen Leger's avatar
    Stephen Leger committed
                    reprojected_position = camera_invert @ track.bundle
    
    Stephen Leger's avatar
    Stephen Leger committed
                    if reprojected_position.z == 0:
                        track.weight = 0
                        track.keyframe_insert("weight", frame=frame)
                        continue
    
                    reprojected_position = reprojected_position / -reprojected_position.z * \
                                           tracking.camera.focal_length_pixels
                    reprojected_position = Vector(
                                            (tracking.camera.principal[0] + reprojected_position[0],
                                            tracking.camera.principal[1] * aspy + reprojected_position[1], 0)
                                            )
    
    
    Stephen Leger's avatar
    Stephen Leger committed
                    marker_position[0] = (marker.co[0] + track.offset[0]) * winx
                    marker_position[1] = (marker.co[1] + track.offset[1]) * winy * aspy
    
    Stephen Leger's avatar
    Stephen Leger committed
                    dp = marker_position - reprojected_position
                    if dp.length == 0:
                        track.weight = 1.0
                    else:
                        track.weight = min(1.0, tw * error / dp.length)
                    track.keyframe_insert("weight", frame=frame)
    
            bpy.ops.clip.solve_camera('INVOKE_DEFAULT')
    
    Stephen Leger's avatar
    Stephen Leger committed
            return{'FINISHED'}
    
    Stephen Leger's avatar
    Stephen Leger committed
    class TRACKING_OP_reset_solution(Operator):
    
        bl_idname = "tracking.reset_solution"
    
    Stephen Leger's avatar
    Stephen Leger committed
        bl_label = "Reset"
    
        bl_description = "Reset track weight and solve camera motion"
    
    Stephen Leger's avatar
    Stephen Leger committed
        bl_options = {"UNDO"}
    
    Stephen Leger's avatar
    Stephen Leger committed
        @classmethod
        def poll(cls, context):
            return (context.area.spaces.active.clip is not None)
    
    Stephen Leger's avatar
    Stephen Leger committed
        def execute(self, context):
            clip = context.area.spaces.active.clip
            try:
                tracking = clip.tracking
                tracks = tracking.tracks
                start = tracking.reconstruction.cameras[0].frame
    
                end = tracking.reconstruction.cameras[-1].frame
    
    Stephen Leger's avatar
    Stephen Leger committed
            except:
    
    Stephen Leger's avatar
    Stephen Leger committed
            start = tracking.reconstruction.cameras[0].frame
    
            end = tracking.reconstruction.cameras[-1].frame
    
    Stephen Leger's avatar
    Stephen Leger committed
            for frame in range(start, end):
    
    Stephen Leger's avatar
    Stephen Leger committed
                camera = tracking.reconstruction.cameras.find_frame(frame=frame)
    
    Stephen Leger's avatar
    Stephen Leger committed
                if camera is None:
                    continue
                for track in tracking.tracks:
    
    Stephen Leger's avatar
    Stephen Leger committed
                    marker = track.markers.find_frame(frame=frame)
    
    Stephen Leger's avatar
    Stephen Leger committed
                    if marker is None:
                        continue
                    track.weight = 1.0
    
                    track.keyframe_insert("weight", frame=frame)
    
    
            bpy.ops.clip.solve_camera('INVOKE_DEFAULT')
    
    Stephen Leger's avatar
    Stephen Leger committed
            return{'FINISHED'}
    
    
    Stephen Leger's avatar
    Stephen Leger committed
    class TRACKING_PT_RefineMotionTracking(Panel):
    
    Stephen Leger's avatar
    Stephen Leger committed
        bl_label = "Refine solution"
        bl_space_type = "CLIP_EDITOR"
        bl_region_type = "TOOLS"
        bl_category = "Solve"
    
    Stephen Leger's avatar
    Stephen Leger committed
        @classmethod
        def poll(cls, context):
    
            return (context.area.spaces.active.clip is not None)
    
    
    Stephen Leger's avatar
    Stephen Leger committed
        def draw(self, context):
            layout = self.layout
    
    
            col = layout.column(align=True)
            col.prop(context.window_manager, "TrackingTargetError", text="Target error")
            col.prop(context.window_manager, "TrackingSmooth", text="Smooth transition")
            sub_box = col.box()
            sub_box.scale_y = 0.25
    
            row = col.row(align=True)
    
    Stephen Leger's avatar
    Stephen Leger committed
            row.operator("tracking.refine_solution")
            row.operator("tracking.reset_solution")
    
    Stephen Leger's avatar
    Stephen Leger committed
    classes =(
    
    Stephen Leger's avatar
    Stephen Leger committed
        TRACKING_OP_refine_solution,
        TRACKING_OP_reset_solution,
        TRACKING_PT_RefineMotionTracking
    
    Stephen Leger's avatar
    Stephen Leger committed
    def register():
    
        bpy.types.WindowManager.TrackingTargetError = FloatProperty(
                name="Target Error",
                description="Refine motion track target error",
                default=0.3,
                min=0.01
                )
        bpy.types.WindowManager.TrackingSmooth = FloatProperty(
                name="Smooth Transition",
                description="Smooth weight transition on start and end of incomplete tracks",
                default=25,
                min=1
                )
    
    Stephen Leger's avatar
    Stephen Leger committed
        for cls in classes:
            bpy.utils.register_class(cls)
    
    Stephen Leger's avatar
    Stephen Leger committed
    def unregister():
    
    Stephen Leger's avatar
    Stephen Leger committed
        for cls in reversed(classes):
            bpy.utils.unregister_class(cls)
    
    Stephen Leger's avatar
    Stephen Leger committed
        del bpy.types.WindowManager.TrackingTargetError
        del bpy.types.WindowManager.TrackingSmooth
    
    Stephen Leger's avatar
    Stephen Leger committed
    if __name__ == "__main__":