Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2019-05-24 10:41:23 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-05-24 10:41:23 +0300
commit5b66741510f753da4839f2f94658025912a7af49 (patch)
tree91f3175763e67c9c2b22c7ef08fb6c72253f307b /space_clip_editor_autotracker.py
parent66e9ff919917c7458ea4a29f47b0da6ca801c415 (diff)
space_clip_editor_autotracker: move to contrib: T63750
Diffstat (limited to 'space_clip_editor_autotracker.py')
-rw-r--r--space_clip_editor_autotracker.py682
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()