diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2019-05-24 10:41:23 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2019-05-24 10:41:23 +0300 |
commit | 5b66741510f753da4839f2f94658025912a7af49 (patch) | |
tree | 91f3175763e67c9c2b22c7ef08fb6c72253f307b /space_clip_editor_autotracker.py | |
parent | 66e9ff919917c7458ea4a29f47b0da6ca801c415 (diff) |
space_clip_editor_autotracker: move to contrib: T63750
Diffstat (limited to 'space_clip_editor_autotracker.py')
-rw-r--r-- | space_clip_editor_autotracker.py | 682 |
1 files changed, 0 insertions, 682 deletions
diff --git a/space_clip_editor_autotracker.py b/space_clip_editor_autotracker.py deleted file mode 100644 index 55f66a57..00000000 --- a/space_clip_editor_autotracker.py +++ /dev/null @@ -1,682 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -bl_info = { - "name": "Autotrack", - "author": "Miika Puustinen, Matti Kaihola, Stephen Leger", - "version": (0, 1, 1), - "blender": (2, 78, 0), - "location": "Movie clip Editor > Tools Panel > Autotrack", - "description": "Motion Tracking with automatic feature detection.", - "warning": "", - "wiki_url": "https://github.com/miikapuustinen/blender_autotracker", - "category": "Motion Tracking", - } - -import bpy -import bgl -import blf -from bpy.types import ( - Operator, - Panel, - PropertyGroup, - WindowManager, - ) -from bpy.props import ( - BoolProperty, - FloatProperty, - IntProperty, - EnumProperty, - PointerProperty, - ) - -# for debug purposes -import time - -# set to True to enable debug prints -DEBUG = False - - -# pass variables just like for the regular prints -def debug_print(*args, **kwargs): - global DEBUG - if DEBUG: - print(*args, **kwargs) - - -# http://blenderscripting.blogspot.ch/2011/07/bgl-drawing-with-opengl-onto-blender-25.html -class GlDrawOnScreen(): - black = (0.0, 0.0, 0.0, 0.7) - white = (1.0, 1.0, 1.0, 0.5) - progress_colour = (0.2, 0.7, 0.2, 0.5) - - def String(self, text, x, y, size, colour): - ''' my_string : the text we want to print - pos_x, pos_y : coordinates in integer values - size : font height. - colour : used for defining the colour''' - dpi, font_id = 72, 0 # dirty fast assignment - bgl.glColor4f(*colour) - blf.position(font_id, x, y, 0) - blf.size(font_id, size, dpi) - blf.draw(font_id, text) - - def _end(self): - bgl.glEnd() - bgl.glPopAttrib() - bgl.glLineWidth(1) - bgl.glDisable(bgl.GL_BLEND) - bgl.glColor4f(0.0, 0.0, 0.0, 1.0) - - def _start_line(self, colour, width=2, style=bgl.GL_LINE_STIPPLE): - bgl.glPushAttrib(bgl.GL_ENABLE_BIT) - bgl.glLineStipple(1, 0x9999) - bgl.glEnable(style) - bgl.glEnable(bgl.GL_BLEND) - bgl.glColor4f(*colour) - bgl.glLineWidth(width) - bgl.glBegin(bgl.GL_LINE_STRIP) - - def Rectangle(self, x0, y0, x1, y1, colour, width=2, style=bgl.GL_LINE): - self._start_line(colour, width, style) - bgl.glVertex2i(x0, y0) - bgl.glVertex2i(x1, y0) - bgl.glVertex2i(x1, y1) - bgl.glVertex2i(x0, y1) - bgl.glVertex2i(x0, y0) - self._end() - - def Polygon(self, pts, colour): - bgl.glPushAttrib(bgl.GL_ENABLE_BIT) - bgl.glEnable(bgl.GL_BLEND) - bgl.glColor4f(*colour) - bgl.glBegin(bgl.GL_POLYGON) - for pt in pts: - x, y = pt - bgl.glVertex2f(x, y) - self._end() - - def ProgressBar(self, x, y, width, height, start, percent): - x1, y1 = x + width, y + height - # progress from current point to either start or end - xs = x + (x1 - x) * float(start) - if percent > 0: - # going forward - xi = xs + (x1 - xs) * float(percent) - else: - # going backward - xi = xs - (x - xs) * float(percent) - self.Polygon([(xs, y), (xs, y1), (xi, y1), (xi, y)], self.progress_colour) - self.Rectangle(x, y, x1, y1, self.white, width=1) - - -def draw_callback(self, context): - self.gl.ProgressBar(10, 24, 200, 16, self.start, self.progress) - self.gl.String(str(int(100 * abs(self.progress))) + "% ESC to Stop", 14, 28, 10, self.gl.white) - - -class OP_Tracking_auto_tracker(Operator): - bl_idname = "tracking.auto_track" - bl_label = "AutoTracking" - bl_description = ("Start Autotracking, Press Esc to Stop \n" - "When stopped, the added Track Markers will be kept") - - _timer = None - _draw_handler = None - - gl = GlDrawOnScreen() - progress = 0 - limits = 0 - t = 0 - - def find_track_start(self, track): - for m in track.markers: - if not m.mute: - return m.frame - return track.markers[0].frame - - def find_track_end(self, track): - for m in reversed(track.markers): - if not m.mute: - return m.frame - return track.markers[-1].frame - 1 - - def find_track_length(self, track): - tstart = self.find_track_start(track) - tend = self.find_track_end(track) - return tend - tstart - - def show_tracks(self, context): - clip = context.area.spaces.active.clip - tracks = clip.tracking.tracks - for track in tracks: - track.hide = False - - def get_vars_from_context(self, context): - scene = context.scene - props = context.window_manager.autotracker_props - clip = context.area.spaces.active.clip - tracks = clip.tracking.tracks - current_frame = scene.frame_current - clip_end = clip.frame_start + clip.frame_duration - clip_start = clip.frame_start - if props.track_backwards: - last_frame = min(clip_end, current_frame + props.frame_separation) - else: - last_frame = max(clip_start, current_frame - props.frame_separation) - return scene, props, clip, tracks, current_frame, last_frame - - def delete_tracks(self, to_delete): - bpy.ops.clip.select_all(action='DESELECT') - for track in to_delete: - track.select = True - bpy.ops.clip.delete_track() - - # DETECT FEATURES - def auto_features(self, context): - """ - Detect features - """ - t = time.time() - - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - - selected = [] - old = [] - to_delete = [] - width = clip.size[0] - delete_threshold = float(props.delete_threshold) / 100.0 - - bpy.ops.clip.select_all(action='DESELECT') - - # Detect Features - bpy.ops.clip.detect_features( - threshold=props.df_threshold, - min_distance=props.df_distance / 100.0 * width, - margin=props.df_margin / 100.0 * width, - placement=props.placement_list - ) - - # filter new and old tracks - for track in tracks: - if track.hide or track.lock: - continue - marker = track.markers.find_frame(current_frame) - if marker is not None: - if (not track.select) and (not marker.mute): - old.append(track) - if track.select: - selected.append(track) - - added_tracks = len(selected) - - # Select overlapping new markers - for track_new in selected: - marker0 = track_new.markers.find_frame(current_frame) - for track_old in old: - marker1 = track_old.markers.find_frame(current_frame) - distance = (marker1.co - marker0.co).length - if distance < delete_threshold: - to_delete.append(track_new) - added_tracks -= 1 - break - - # Delete Overlapping Markers - self.delete_tracks(to_delete) - debug_print("auto_features %.4f seconds, add: %s tracks" % (time.time() - t, added_tracks)) - - # AUTOTRACK FRAMES - def track_frames_backward(self): - # INVOKE_DEFAULT to show progress and take account of frame_limit - t = time.time() - res = bpy.ops.clip.track_markers('INVOKE_DEFAULT', backwards=True, sequence=True) - debug_print("track_frames_backward %.2f seconds %s" % (time.time() - t, res)) - - def track_frames_forward(self): - # INVOKE_DEFAULT to show progress and take account of frame_limit - t = time.time() - res = bpy.ops.clip.track_markers('INVOKE_DEFAULT', backwards=False, sequence=True) - debug_print("track_frames_forward %.2f seconds %s" % (time.time() - t, res)) - - def get_active_tracks(self, context): - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - - active_tracks = [] - for track in tracks: - if track.hide or track.lock: - continue - if len(track.markers) < 2: - active_tracks.append(track) - else: - marker = track.markers.find_frame(current_frame) - if (marker is not None) and (not marker.mute): - active_tracks.append(track) - return active_tracks - - def select_active_tracks(self, context): - t = time.time() - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - # Select active trackers for tracking - bpy.ops.clip.select_all(action='DESELECT') - selected = self.get_active_tracks(context) - for track in selected: - track.select = True - debug_print("select_active_tracks %.2f seconds," - " selected: %s" % (time.time() - t, len(selected))) - return selected - - def estimate_motion(self, context, last, frame): - """ - compute mean pixel motion for current frame - TODO: use statistic here to make filtering more efficient - last : last frame number - frame: current frame number - return mean pixel distance error - """ - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - nbtracks = 0 - distance = 0.0 - for track in tracks: - if track.hide or track.lock: - continue - marker0 = track.markers.find_frame(frame) - marker1 = track.markers.find_frame(last) - if marker0 is not None and marker1 is not None: - d = (marker0.co - marker1.co).length - # skip fixed tracks - if d > 0: - distance += d - nbtracks += 1 - if nbtracks > 0: - mean = distance / nbtracks - else: - # arbitrary set to prevent division by 0 error - mean = 10 - - return mean - - # REMOVE SMALL TRACKS - def remove_small(self, context): - t = time.time() - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - to_delete = [] - bpy.ops.clip.select_all(action='DESELECT') - for track in tracks: - if track.hide or track.lock: - continue - if len(track.markers) > 1: - marker = track.markers.find_frame(current_frame) - if marker is None and self.find_track_length(track) < props.small_tracks: - to_delete.append(track) - deleted_tracks = len(to_delete) - self.delete_tracks(to_delete) - debug_print("remove_small %.4f seconds, %s tracks deleted" % (time.time() - t, deleted_tracks)) - - def split_track(self, context, track, split_frame, skip=0): - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - if props.track_backwards: - end = scene.frame_start - step = -1 - else: - end = scene.frame_end - step = 1 - new_track = tracks.new(frame=split_frame) - - for frame in range(split_frame, end, step): - marker = track.markers.find_frame(frame) - if marker is None: - return - # add new marker on new track for frame - if abs(frame - split_frame) >= skip: - new_marker = new_track.markers.find_frame(frame) - if new_marker is None: - new_marker = new_track.markers.insert_frame(frame) - new_marker.co = marker.co - # remove marker on track for frame - if frame == split_frame: - track.hide = True - else: - track.markers.delete_frame(frame) - marker.mute = True - - # REMOVE JUMPING MARKERS - def remove_jumping(self, context): - - t = time.time() - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - - if props.track_backwards: - step = -1 - else: - step = 1 - - to_split = [None for track in tracks] - for frame in range(last_frame, current_frame, step): - - last = frame - step - - # mean motion (normalized [0-1]) distance for tracks between last and current frame - mean = self.estimate_motion(context, last, frame) - - # how much a track is allowed to move - allowed = mean * props.jump_cut - - for i, track in enumerate(tracks): - if track.hide or track.lock: - continue - marker0 = track.markers.find_frame(frame) - marker1 = track.markers.find_frame(last) - if marker0 is not None and marker1 is not None: - distance = (marker0.co - marker1.co).length - # Jump Cut threshold - if distance > allowed: - if to_split[i] is None: - to_split[i] = [frame, frame] - else: - to_split[i][1] = frame - - jumping = 0 - for i, split in enumerate(to_split): - if split is not None: - self.split_track(context, tracks[i], split[0], abs(split[0] - split[1])) - jumping += 1 - - debug_print("remove_jumping: %.4f seconds, %s tracks cut." % (time.time() - t, jumping)) - - def get_frame_range(self, context): - """ - get tracking frames range - use clip limits when clip shorter than scene - else use scene limits - """ - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - frame_start = max(scene.frame_start, clip.frame_start) - frame_end = min(scene.frame_end, clip.frame_start + clip.frame_duration) - frame_duration = frame_end - frame_start - return frame_start, frame_end, frame_duration - - def modal(self, context, event): - - if event.type in {'ESC'}: - self.report({'INFO'}, - "Stopping, up to now added Markers will be kept. Autotracking Finished") - self.cancel(context) - return {'FINISHED'} - - scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) - frame_start, frame_end, frame_duration = self.get_frame_range(context) - - if (((not props.track_backwards) and current_frame >= frame_end) or - (props.track_backwards and current_frame <= frame_start)): - - self.report({'INFO'}, - "Reached the end of the Clip. Autotracking Finished") - self.cancel(context) - return {'FINISHED'} - - # do not run this modal while tracking operator runs - # Known issue, you'll have to keep ESC pressed - if event.type not in {'TIMER'} or context.scene.frame_current != self.next_frame: - return {'PASS_THROUGH'} - - # prevent own TIMER event while running - self.stop_timer(context) - - if props.track_backwards: - self.next_frame = scene.frame_current - props.frame_separation - total = self.start_frame - frame_start - else: - self.next_frame = scene.frame_current + props.frame_separation - total = frame_end - self.start_frame - - if total > 0: - self.progress = (current_frame - self.start_frame) / total - else: - self.progress = 0 - - debug_print("Tracking frame %s" % (scene.frame_current)) - - # Remove bad tracks before adding new ones - self.remove_small(context) - self.remove_jumping(context) - - # add new tracks - self.auto_features(context) - - # Select active trackers for tracking - active_tracks = self.select_active_tracks(context) - - # finish if there is nothing to track - if len(active_tracks) == 0: - self.report({'INFO'}, - "No new tracks created, nothing to track. Autotrack Finished") - self.cancel(context) - return {'FINISHED'} - - # setup frame_limit on tracks - for track in active_tracks: - track.frames_limit = 0 - active_tracks[0].frames_limit = props.frame_separation - - # Forwards or backwards tracking - if props.track_backwards: - self.track_frames_backward() - else: - self.track_frames_forward() - - # setup a timer to broadcast a TIMER event to force modal to - # re-run as fast as possible (not waiting for any mouse or keyboard event) - self.start_timer(context) - - return {'RUNNING_MODAL'} - - def invoke(self, context, event): - scene = context.scene - frame_start, frame_end, frame_duration = self.get_frame_range(context) - - if scene.frame_current > frame_end: - scene.frame_current = frame_end - elif scene.frame_current < frame_start: - scene.frame_current = frame_start - - self.start_frame = scene.frame_current - self.start = (scene.frame_current - frame_start) / (frame_duration) - self.progress = 0 - - # keep track of frame at witch we should detect new features and filter tracks - self.next_frame = scene.frame_current - - # draw progress - args = (self, context) - self._draw_handler = bpy.types.SpaceClipEditor.draw_handler_add( - draw_callback, args, - 'WINDOW', 'POST_PIXEL' - ) - self.start_timer(context) - context.window_manager.modal_handler_add(self) - return {'RUNNING_MODAL'} - - def __init__(self): - self.t = time.time() - - def __del__(self): - debug_print("AutoTrack %.2f seconds" % (time.time() - self.t)) - - def execute(self, context): - debug_print("Autotrack execute called") - return {'FINISHED'} - - def stop_timer(self, context): - context.window_manager.event_timer_remove(self._timer) - - def start_timer(self, context): - self._timer = context.window_manager.event_timer_add(time_step=0.1, window=context.window) - - def cancel(self, context): - self.stop_timer(context) - self.show_tracks(context) - bpy.types.SpaceClipEditor.draw_handler_remove(self._draw_handler, 'WINDOW') - - @classmethod - def poll(cls, context): - return (context.area.spaces.active.clip is not None) - - -class AutotrackerSettings(PropertyGroup): - """Create properties""" - df_margin: FloatProperty( - name="Detect Features Margin", - description="Only consider features from pixels located outside\n" - "the defined margin from the clip borders", - subtype='PERCENTAGE', - default=5, - min=0, - max=100 - ) - df_threshold: FloatProperty( - name="Detect Features Threshold", - description="Threshold level to deem a feature being good enough for tracking", - default=0.3, - min=0.0, - max=1.0 - ) - # Note: merge this one with delete_threshold - df_distance: FloatProperty( - name="Detect Features Distance", - description="Minimal acceptable distance between two features", - subtype='PERCENTAGE', - default=8, - min=1, - max=100 - ) - delete_threshold: FloatProperty( - name="New Marker Threshold", - description="Threshold of how close a new features can appear during tracking", - subtype='PERCENTAGE', - default=8, - min=1, - max=100 - ) - small_tracks: IntProperty( - name="Minimum track length", - description="Delete tracks shorter than this number of frames\n" - "Note: set to 0 for keeping all tracks", - default=50, - min=1, - max=1000 - ) - frame_separation: IntProperty( - name="Frame Separation", - description="How often new features are generated", - default=5, - min=1, - max=100 - ) - jump_cut: FloatProperty( - name="Jump Cut", - description="How much distance a marker can travel before it is considered " - "to be a bad track and cut.\nA new track will be added " - "(factor relative to mean motion)", - default=5.0, - min=0.0, - max=50.0 - ) - track_backwards: BoolProperty( - name="AutoTrack Backwards", - description="Track from the last frame of the selected clip", - default=False - ) - # Dropdown menu - list_items = [ - ("FRAME", "Whole Frame", "", 1), - ("INSIDE_GPENCIL", "Inside Grease Pencil", "", 2), - ("OUTSIDE_GPENCIL", "Outside Grease Pencil", "", 3), - ] - placement_list: EnumProperty( - name="Placement", - description="Feature Placement", - items=list_items - ) - - -""" - NOTE: - All size properties are in percent of the clip size, - so presets do not depend on the clip size -""" - - -class AutotrackerPanel(Panel): - """Creates a Panel in the Render Layer properties window""" - bl_label = "Autotrack" - bl_idname = "autotrack" - bl_space_type = 'CLIP_EDITOR' - bl_region_type = 'TOOLS' - bl_category = "Track" - - @classmethod - def poll(cls, context): - return (context.area.spaces.active.clip is not None) - - def draw(self, context): - layout = self.layout - wm = context.window_manager - - row = layout.row() - row.scale_y = 1.5 - row.operator("tracking.auto_track", text="Autotrack! ", icon='PLAY') - - row = layout.row() - row.prop(wm.autotracker_props, "track_backwards") - - row = layout.row() - col = layout.column(align=True) - col.prop(wm.autotracker_props, "delete_threshold") - col.prop(wm.autotracker_props, "small_tracks") - col.prop(wm.autotracker_props, "frame_separation", text="Frame Separation") - col.prop(wm.autotracker_props, "jump_cut", text="Jump Threshold") - - row = layout.row() - row.label(text="Detect Features Settings:") - col = layout.column(align=True) - col.prop(wm.autotracker_props, "df_margin", text="Margin:") - col.prop(wm.autotracker_props, "df_distance", text="Distance:") - col.prop(wm.autotracker_props, "df_threshold", text="Threshold:") - - row = layout.row() - row.label(text="Feature Placement:") - col = layout.column(align=True) - col.prop(wm.autotracker_props, "placement_list", text="") - - -def register(): - bpy.utils.register_class(AutotrackerSettings) - WindowManager.autotracker_props = PointerProperty( - type=AutotrackerSettings - ) - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_class(AutotrackerSettings) - bpy.utils.unregister_module(__name__) - del WindowManager.autotracker_props - - -if __name__ == "__main__": - register() |