123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641 |
- /*
- * ***** 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.
- *
- * The Original Code is Copyright (C) 2011 Blender Foundation.
- * All rights reserved.
- *
- * Contributor(s): Blender Foundation,
- * Sergey Sharybin
- * Keir Mierle
- * Ichthyostega
- *
- * ***** END GPL LICENSE BLOCK *****
- */
- /** \file blender/blenkernel/intern/tracking_stabilize.c
- * \ingroup bke
- *
- * This file contains implementation of 2D image stabilization.
- */
- #include <limits.h>
- #include "DNA_movieclip_types.h"
- #include "DNA_scene_types.h"
- #include "DNA_anim_types.h"
- #include "RNA_access.h"
- #include "BLI_utildefines.h"
- #include "BLI_sort_utils.h"
- #include "BLI_math_vector.h"
- #include "BLI_math.h"
- #include "BKE_tracking.h"
- #include "BKE_movieclip.h"
- #include "BKE_fcurve.h"
- #include "BLI_ghash.h"
- #include "MEM_guardedalloc.h"
- #include "IMB_imbuf_types.h"
- #include "IMB_imbuf.h"
- /* == Parameterization constants == */
- /* When measuring the scale changes relative to the rotation pivot point, it
- * might happen accidentally that a probe point (tracking point), which doesn't
- * actually move on a circular path, gets very close to the pivot point, causing
- * the measured scale contribution to go toward infinity. We damp this undesired
- * effect by adding a bias (floor) to the measured distances, which will
- * dominate very small distances and thus cause the corresponding track's
- * contribution to diminish.
- * Measurements happen in normalized (0...1) coordinates within a frame.
- */
- static float SCALE_ERROR_LIMIT_BIAS = 0.01f;
- /* When to consider a track as completely faded out.
- * This is used in conjunction with the "disabled" flag of the track
- * to determine start positions, end positions and gaps
- */
- static float EPSILON_WEIGHT = 0.005f;
- /* == private working data == */
- /* Per track baseline for stabilization, defined at reference frame.
- * A track's reference frame is chosen as close as possible to the (global)
- * anchor_frame. Baseline holds the constant part of each track's contribution
- * to the observed movement; it is calculated at initialization pass, using the
- * measurement value at reference frame plus the average contribution to fill
- * the gap between global anchor_frame and the reference frame for this track.
- * This struct with private working data is associated to the local call context
- * via `StabContext::private_track_data`
- */
- typedef struct TrackStabilizationBase {
- float stabilization_offset_base[2];
- /* measured relative to translated pivot */
- float stabilization_rotation_base[2][2];
- /* measured relative to translated pivot */
- float stabilization_scale_base;
- bool is_init_for_stabilization;
- FCurve *track_weight_curve;
- } TrackStabilizationBase;
- /* Tracks are reordered for initialization, starting as close as possible to
- * anchor_frame
- */
- typedef struct TrackInitOrder {
- int sort_value;
- int reference_frame;
- MovieTrackingTrack *data;
- } TrackInitOrder;
- /* Per frame private working data, for accessing possibly animated values. */
- typedef struct StabContext {
- MovieClip *clip;
- MovieTracking *tracking;
- MovieTrackingStabilization *stab;
- GHash *private_track_data;
- FCurve *locinf;
- FCurve *rotinf;
- FCurve *scaleinf;
- FCurve *target_pos[2];
- FCurve *target_rot;
- FCurve *target_scale;
- bool use_animation;
- } StabContext;
- static TrackStabilizationBase *access_stabilization_baseline_data(
- StabContext *ctx,
- MovieTrackingTrack *track)
- {
- return BLI_ghash_lookup(ctx->private_track_data, track);
- }
- static void attach_stabilization_baseline_data(
- StabContext *ctx,
- MovieTrackingTrack *track,
- TrackStabilizationBase *private_data)
- {
- BLI_ghash_insert(ctx->private_track_data, track, private_data);
- }
- static void discard_stabilization_baseline_data(void *val)
- {
- if (val != NULL) {
- MEM_freeN(val);
- }
- }
- /* == access animated values for given frame == */
- static FCurve *retrieve_stab_animation(MovieClip *clip,
- const char *data_path,
- int idx)
- {
- return id_data_find_fcurve(&clip->id,
- &clip->tracking.stabilization,
- &RNA_MovieTrackingStabilization,
- data_path,
- idx,
- NULL);
- }
- static FCurve *retrieve_track_weight_animation(MovieClip *clip,
- MovieTrackingTrack *track)
- {
- return id_data_find_fcurve(&clip->id,
- track,
- &RNA_MovieTrackingTrack,
- "weight_stab",
- 0,
- NULL);
- }
- static float fetch_from_fcurve(FCurve *animationCurve,
- int framenr,
- StabContext *ctx,
- float default_value)
- {
- if (ctx && ctx->use_animation && animationCurve) {
- int scene_framenr = BKE_movieclip_remap_clip_to_scene_frame(ctx->clip,
- framenr);
- return evaluate_fcurve(animationCurve, scene_framenr);
- }
- return default_value;
- }
- static float get_animated_locinf(StabContext *ctx, int framenr)
- {
- return fetch_from_fcurve(ctx->locinf, framenr, ctx, ctx->stab->locinf);
- }
- static float get_animated_rotinf(StabContext *ctx, int framenr)
- {
- return fetch_from_fcurve(ctx->rotinf, framenr, ctx, ctx->stab->rotinf);
- }
- static float get_animated_scaleinf(StabContext *ctx, int framenr)
- {
- return fetch_from_fcurve(ctx->scaleinf, framenr, ctx, ctx->stab->scaleinf);
- }
- static void get_animated_target_pos(StabContext *ctx,
- int framenr,
- float target_pos[2])
- {
- target_pos[0] = fetch_from_fcurve(ctx->target_pos[0],
- framenr,
- ctx,
- ctx->stab->target_pos[0]);
- target_pos[1] = fetch_from_fcurve(ctx->target_pos[1],
- framenr,
- ctx,
- ctx->stab->target_pos[1]);
- }
- static float get_animated_target_rot(StabContext *ctx, int framenr)
- {
- return fetch_from_fcurve(ctx->target_rot,
- framenr,
- ctx,
- ctx->stab->target_rot);
- }
- static float get_animated_target_scale(StabContext *ctx, int framenr)
- {
- return fetch_from_fcurve(ctx->target_scale, framenr, ctx, ctx->stab->scale);
- }
- static float get_animated_weight(StabContext *ctx,
- MovieTrackingTrack *track,
- int framenr)
- {
- TrackStabilizationBase *working_data =
- access_stabilization_baseline_data(ctx, track);
- if (working_data && working_data->track_weight_curve) {
- int scene_framenr = BKE_movieclip_remap_clip_to_scene_frame(ctx->clip,
- framenr);
- return evaluate_fcurve(working_data->track_weight_curve, scene_framenr);
- }
- /* Use weight at global 'current frame' as fallback default. */
- return track->weight_stab;
- }
- static void use_values_from_fcurves(StabContext *ctx, bool toggle)
- {
- if (ctx != NULL) {
- ctx->use_animation = toggle;
- }
- }
- /* Prepare per call private working area.
- * Used for access to possibly animated values: retrieve available F-curves.
- */
- static StabContext *initialize_stabilization_working_context(MovieClip *clip)
- {
- StabContext *ctx = MEM_callocN(sizeof(StabContext),
- "2D stabilization animation runtime data");
- ctx->clip = clip;
- ctx->tracking = &clip->tracking;
- ctx->stab = &clip->tracking.stabilization;
- ctx->private_track_data = BLI_ghash_ptr_new(
- "2D stabilization per track private working data");
- ctx->locinf = retrieve_stab_animation(clip, "influence_location", 0);
- ctx->rotinf = retrieve_stab_animation(clip, "influence_rotation", 0);
- ctx->scaleinf = retrieve_stab_animation(clip, "influence_scale", 0);
- ctx->target_pos[0] = retrieve_stab_animation(clip, "target_pos", 0);
- ctx->target_pos[1] = retrieve_stab_animation(clip, "target_pos", 1);
- ctx->target_rot = retrieve_stab_animation(clip, "target_rot", 0);
- ctx->target_scale = retrieve_stab_animation(clip, "target_zoom", 0);
- ctx->use_animation = true;
- return ctx;
- }
- /* Discard all private working data attached to this call context.
- * NOTE: We allocate the record for the per track baseline contribution
- * locally for each call context (i.e. call to
- * BKE_tracking_stabilization_data_get()
- * Thus it is correct to discard all allocations found within the
- * corresponding _local_ GHash
- */
- static void discard_stabilization_working_context(StabContext *ctx)
- {
- if (ctx != NULL) {
- BLI_ghash_free(ctx->private_track_data,
- NULL,
- discard_stabilization_baseline_data);
- MEM_freeN(ctx);
- }
- }
- static bool is_init_for_stabilization(StabContext *ctx,
- MovieTrackingTrack *track)
- {
- TrackStabilizationBase *working_data =
- access_stabilization_baseline_data(ctx, track);
- return (working_data != NULL && working_data->is_init_for_stabilization);
- }
- static bool is_usable_for_stabilization(StabContext *ctx,
- MovieTrackingTrack *track)
- {
- return (track->flag & TRACK_USE_2D_STAB) &&
- is_init_for_stabilization(ctx, track);
- }
- static bool is_effectively_disabled(StabContext *ctx,
- MovieTrackingTrack *track,
- MovieTrackingMarker *marker)
- {
- return (marker->flag & MARKER_DISABLED) ||
- (EPSILON_WEIGHT > get_animated_weight(ctx, track, marker->framenr));
- }
- static int search_closest_marker_index(MovieTrackingTrack *track,
- int ref_frame)
- {
- MovieTrackingMarker *markers = track->markers;
- int end = track->markersnr;
- int i = track->last_marker;
- i = MAX2(0, i);
- i = MIN2(i, end - 1);
- for ( ; i < end - 1 && markers[i].framenr <= ref_frame; ++i);
- for ( ; 0 < i && markers[i].framenr > ref_frame; --i);
- track->last_marker = i;
- return i;
- }
- static void retrieve_next_higher_usable_frame(StabContext *ctx,
- MovieTrackingTrack *track,
- int i,
- int ref_frame,
- int *next_higher)
- {
- MovieTrackingMarker *markers = track->markers;
- int end = track->markersnr;
- BLI_assert(0 <= i && i < end);
- while (i < end &&
- (markers[i].framenr < ref_frame ||
- is_effectively_disabled(ctx, track, &markers[i])))
- {
- ++i;
- }
- if (i < end && markers[i].framenr < *next_higher) {
- BLI_assert(markers[i].framenr >= ref_frame);
- *next_higher = markers[i].framenr;
- }
- }
- static void retrieve_next_lower_usable_frame(StabContext *ctx,
- MovieTrackingTrack *track,
- int i,
- int ref_frame,
- int *next_lower)
- {
- MovieTrackingMarker *markers = track->markers;
- BLI_assert(0 <= i && i < track->markersnr);
- while (i >= 0 &&
- (markers[i].framenr > ref_frame ||
- is_effectively_disabled(ctx, track, &markers[i])))
- {
- --i;
- }
- if (0 <= i && markers[i].framenr > *next_lower) {
- BLI_assert(markers[i].framenr <= ref_frame);
- *next_lower = markers[i].framenr;
- }
- }
- /* Find closest frames with usable stabilization data.
- * A frame counts as _usable_ when there is at least one track marked for
- * translation stabilization, which has an enabled tracking marker at this very
- * frame. We search both for the next lower and next higher position, to allow
- * the caller to interpolate gaps and to extrapolate at the ends of the
- * definition range.
- *
- * NOTE: Regarding performance note that the individual tracks will cache the
- * last search position.
- */
- static void find_next_working_frames(StabContext *ctx,
- int framenr,
- int *next_lower,
- int *next_higher)
- {
- for (MovieTrackingTrack *track = ctx->tracking->tracks.first;
- track != NULL;
- track = track->next)
- {
- if (is_usable_for_stabilization(ctx, track)) {
- int startpoint = search_closest_marker_index(track, framenr);
- retrieve_next_higher_usable_frame(ctx,
- track,
- startpoint,
- framenr,
- next_higher);
- retrieve_next_lower_usable_frame(ctx,
- track,
- startpoint,
- framenr,
- next_lower);
- }
- }
- }
- /* Find active (enabled) marker closest to the reference frame. */
- static MovieTrackingMarker *get_closest_marker(StabContext *ctx,
- MovieTrackingTrack *track,
- int ref_frame)
- {
- int next_lower = MINAFRAME;
- int next_higher = MAXFRAME;
- int i = search_closest_marker_index(track, ref_frame);
- retrieve_next_higher_usable_frame(ctx, track, i, ref_frame, &next_higher);
- retrieve_next_lower_usable_frame(ctx, track, i, ref_frame, &next_lower);
- if ((next_higher - ref_frame) < (ref_frame - next_lower)) {
- return BKE_tracking_marker_get_exact(track, next_higher);
- }
- else {
- return BKE_tracking_marker_get_exact(track, next_lower);
- }
- }
- /* Retrieve tracking data, if available and applicable for this frame.
- * The returned weight value signals the validity; data recorded for this
- * tracking marker on the exact requested frame is output with the full weight
- * of this track, while gaps in the data sequence cause the weight to go to zero.
- */
- static MovieTrackingMarker *get_tracking_data_point(
- StabContext *ctx,
- MovieTrackingTrack *track,
- int framenr,
- float *r_weight)
- {
- MovieTrackingMarker *marker = BKE_tracking_marker_get_exact(track, framenr);
- if (marker != NULL && !(marker->flag & MARKER_DISABLED)) {
- *r_weight = get_animated_weight(ctx, track, framenr);
- return marker;
- }
- else {
- /* No marker at this frame (=gap) or marker disabled. */
- *r_weight = 0.0f;
- return NULL;
- }
- }
- /* Define the reference point for rotation/scale measurement and compensation.
- * The stabilizator works by assuming the image was distorted by a affine linear
- * transform, i.e. it was rotated and stretched around this reference point
- * (pivot point) and then shifted laterally. Any scale and orientation changes
- * will be picked up relative to this point. And later the image will be
- * stabilized by rotating around this point. The result can only be as
- * accurate as this pivot point actually matches the real rotation center
- * of the actual movements. Thus any scheme to define a pivot point is
- * always guesswork.
- *
- * As a simple default, we use the weighted average of the location markers
- * of the current frame as pivot point. TODO It is planned to add further
- * options, like e.g. anchoring the pivot point at the canvas. Moreover,
- * it is planned to allow for a user controllable offset.
- */
- static void setup_pivot(const float ref_pos[2], float r_pivot[2])
- {
- zero_v2(r_pivot); /* TODO: add an animated offset position here. */
- add_v2_v2(r_pivot, ref_pos);
- }
- /* Calculate the contribution of a single track at the time position (frame) of
- * the given marker. Each track has a local reference frame, which is as close
- * as possible to the global anchor_frame. Thus the translation contribution is
- * comprised of the offset relative to the image position at that reference
- * frame, plus a guess of the contribution for the time span between the
- * anchor_frame and the local reference frame of this track. The constant part
- * of this contribution is precomputed initially. At the anchor_frame, by
- * definition the contribution of all tracks is zero, keeping the frame in place.
- *
- * track_ref is per track baseline contribution at reference frame; filled in at
- * initialization
- * marker is tracking data to use as contribution for current frame.
- * result_offset is a total cumulated contribution of this track,
- * relative to the stabilization anchor_frame,
- * in normalized (0...1) coordinates.
- */
- static void translation_contribution(TrackStabilizationBase *track_ref,
- MovieTrackingMarker *marker,
- float result_offset[2])
- {
- add_v2_v2v2(result_offset,
- track_ref->stabilization_offset_base,
- marker->pos);
- }
- /* Similar to the ::translation_contribution(), the rotation contribution is
- * comprised of the contribution by this individual track, and the averaged
- * contribution from anchor_frame to the ref point of this track.
- * - Contribution is in terms of angles, -pi < angle < +pi, and all averaging
- * happens in this domain.
- * - Yet the actual measurement happens as vector between pivot and the current
- * tracking point
- * - Currently we use the center of frame as approximation for the rotation pivot
- * point.
- * - Moreover, the pivot point has to be compensated for the already determined
- * shift offset, in order to get the pure rotation around the pivot.
- * To turn this into a _contribution_, the likewise corrected angle at the
- * reference frame has to be subtracted, to get only the pure angle difference
- * this tracking point has captured.
- * - To get from vectors to angles, we have to go through an arcus tangens,
- * which involves the issue of the definition range: the resulting angles will
- * flip by 360deg when the measured vector passes from the 2nd to the third
- * quadrant, thus messing up the average calculation. Since _any_ tracking
- * point might be used, these problems are quite common in practice.
- * - Thus we perform the subtraction of the reference and the addition of the
- * baseline contribution in polar coordinates as simple addition of angles;
- * since these parts are fixed, we can bake them into a rotation matrix.
- * With this approach, the border of the arcus tangens definition range will
- * be reached only, when the _whole_ contribution approaches +- 180deg,
- * meaning we've already tilted the frame upside down. This situation is way
- * less common and can be tolerated.
- * - As an additional feature, when activated, also changes in image scale
- * relative to the rotation center can be picked up. To handle those values
- * in the same framework, we average the scales as logarithms.
- *
- * aspect is a total aspect ratio of the undistorted image (includes fame and
- * pixel aspect). The function returns a quality factor, which can be used
- * to damp the contributions of points in close proximity to the pivot point,
- * since such contributions might be dominated by rounding errors and thus
- * poison the calculated average. When the quality factor goes towards zero,
- * the weight of this contribution should be reduced accordingly.
- */
- static float rotation_contribution(TrackStabilizationBase *track_ref,
- MovieTrackingMarker *marker,
- const float aspect,
- const float pivot[2],
- float *result_angle,
- float *result_scale)
- {
- float len, quality;
- float pos[2];
- sub_v2_v2v2(pos, marker->pos, pivot);
- pos[0] *= aspect;
- mul_m2v2(track_ref->stabilization_rotation_base, pos);
- *result_angle = atan2f(pos[1],pos[0]);
- len = len_v2(pos);
- /* prevent points very close to the pivot point from poisoning the result */
- quality = 1 - expf(-len*len / SCALE_ERROR_LIMIT_BIAS*SCALE_ERROR_LIMIT_BIAS);
- len += SCALE_ERROR_LIMIT_BIAS;
- *result_scale = len * track_ref->stabilization_scale_base;
- BLI_assert(0.0 < *result_scale);
- return quality;
- }
- /* Workaround to allow for rotation around an arbitrary pivot point.
- * Currently, the public API functions do not support this flexibility.
- * Rather, rotation will always be applied around a fixed origin.
- * As a workaround, we shift the image after rotation to match the
- * desired rotation centre. And since this offset needs to be applied
- * after the rotation and scaling, we can collapse it with the
- * translation compensation, which is also a lateral shift (offset).
- * The offset to apply is intended_pivot - rotated_pivot
- */
- static void compensate_rotation_center(const int size, float aspect,
- const float angle,
- const float scale,
- const float pivot[2],
- float result_translation[2])
- {
- const float origin[2] = {0.5f*aspect*size, 0.5f*size};
- float intended_pivot[2], rotated_pivot[2];
- float rotation_mat[2][2];
- copy_v2_v2(intended_pivot, pivot);
- copy_v2_v2(rotated_pivot, pivot);
- angle_to_mat2(rotation_mat, +angle);
- sub_v2_v2(rotated_pivot, origin);
- mul_m2v2(rotation_mat, rotated_pivot);
- mul_v2_fl(rotated_pivot, scale);
- add_v2_v2(rotated_pivot, origin);
- add_v2_v2(result_translation, intended_pivot);
- sub_v2_v2(result_translation, rotated_pivot);
- }
- /* Weighted average of the per track cumulated contributions at given frame.
- * Returns truth if all desired calculations could be done and all averages are
- * available.
- *
- * NOTE: Even if the result is not `true`, the returned translation and angle
- * are always sensible and as good as can be. Especially in the
- * initialization phase we might not be able to get any average (yet) or
- * get only a translation value. Since initialization visits tracks in a
- * specific order, starting from anchor_frame, the result is logically
- * correct non the less. But under normal operation conditions,
- * a result of `false` should disable the stabilization function
- */
- static bool average_track_contributions(StabContext *ctx,
- int framenr,
- float aspect,
- float r_translation[2],
- float r_pivot[2],
- float *r_angle,
- float *r_scale_step)
- {
- bool ok;
- float weight_sum;
- MovieTrackingTrack *track;
- MovieTracking *tracking = ctx->tracking;
- MovieTrackingStabilization *stab = &tracking->stabilization;
- float ref_pos[2];
- BLI_assert(stab->flag & TRACKING_2D_STABILIZATION);
- zero_v2(r_translation);
- *r_scale_step = 0.0f; /* logarithm */
- *r_angle = 0.0f;
- zero_v2(ref_pos);
- ok = false;
- weight_sum = 0.0f;
- for (track = tracking->tracks.first; track; track = track->next) {
- if (!is_init_for_stabilization(ctx, track)) {
- continue;
- }
- if (track->flag & TRACK_USE_2D_STAB) {
- float weight = 0.0f;
- MovieTrackingMarker *marker = get_tracking_data_point(ctx,
- track,
- framenr,
- &weight);
- if (marker) {
- TrackStabilizationBase *stabilization_base =
- access_stabilization_baseline_data(ctx, track);
- BLI_assert(stabilization_base != NULL);
- float offset[2];
- weight_sum += weight;
- translation_contribution(stabilization_base, marker, offset);
- r_translation[0] += weight * offset[0];
- r_translation[1] += weight * offset[1];
- ref_pos[0] += weight * marker->pos[0];
- ref_pos[1] += weight * marker->pos[1];
- ok |= (weight_sum > EPSILON_WEIGHT);
- }
- }
- }
- if (!ok) {
- return false;
- }
- ref_pos[0] /= weight_sum;
- ref_pos[1] /= weight_sum;
- r_translation[0] /= weight_sum;
- r_translation[1] /= weight_sum;
- setup_pivot(ref_pos, r_pivot);
- if (!(stab->flag & TRACKING_STABILIZE_ROTATION)) {
- return ok;
- }
- ok = false;
- weight_sum = 0.0f;
- for (track = tracking->tracks.first; track; track = track->next) {
- if (!is_init_for_stabilization(ctx, track)) {
- continue;
- }
- if (track->flag & TRACK_USE_2D_STAB_ROT) {
- float weight = 0.0f;
- MovieTrackingMarker *marker = get_tracking_data_point(ctx,
- track,
- framenr,
- &weight);
- if (marker) {
- TrackStabilizationBase *stabilization_base =
- access_stabilization_baseline_data(ctx, track);
- BLI_assert(stabilization_base != NULL);
- float rotation, scale, quality;
- quality = rotation_contribution(stabilization_base,
- marker,
- aspect,
- r_pivot,
- &rotation,
- &scale);
- weight *= quality;
- weight_sum += weight;
- *r_angle += rotation * weight;
- if (stab->flag & TRACKING_STABILIZE_SCALE) {
- *r_scale_step += logf(scale) * weight;
- }
- else {
- *r_scale_step = 0;
- }
- ok |= (weight_sum > EPSILON_WEIGHT);
- }
- }
- }
- if (ok) {
- *r_scale_step /= weight_sum;
- *r_angle /= weight_sum;
- }
- else {
- /* We reach this point because translation could be calculated,
- * but rotation/scale found no data to work on.
- */
- *r_scale_step = 0.0f;
- *r_angle = 0.0f;
- }
- return true;
- }
- /* Calculate weight center of location tracks for given frame.
- * This function performs similar calculations as average_track_contributions(),
- * but does not require the tracks to be initialized for stabilisation. Moreover,
- * when there is no usable tracking data for the given frame number, data from
- * a neighbouring frame is used. Thus this function can be used to calculate
- * a starting point on initialization.
- */
- static void average_marker_positions(StabContext *ctx, int framenr, float r_ref_pos[2])
- {
- bool ok = false;
- float weight_sum;
- MovieTrackingTrack *track;
- MovieTracking *tracking = ctx->tracking;
- zero_v2(r_ref_pos);
- weight_sum = 0.0f;
- for (track = tracking->tracks.first; track; track = track->next) {
- if (track->flag & TRACK_USE_2D_STAB) {
- float weight = 0.0f;
- MovieTrackingMarker *marker =
- get_tracking_data_point(ctx, track, framenr, &weight);
- if (marker) {
- weight_sum += weight;
- r_ref_pos[0] += weight * marker->pos[0];
- r_ref_pos[1] += weight * marker->pos[1];
- ok |= (weight_sum > EPSILON_WEIGHT);
- }
- }
- }
- if (ok) {
- r_ref_pos[0] /= weight_sum;
- r_ref_pos[1] /= weight_sum;
- }
- else {
- /* No usable tracking data on any track on this frame.
- * Use data from neighbouring frames to extrapolate...
- */
- int next_lower = MINAFRAME;
- int next_higher = MAXFRAME;
- use_values_from_fcurves(ctx, true);
- for (track = tracking->tracks.first; track; track = track->next) {
- /* Note: we deliberately do not care if this track
- * is already initialized for stabilisation */
- if (track->flag & TRACK_USE_2D_STAB) {
- int startpoint = search_closest_marker_index(track, framenr);
- retrieve_next_higher_usable_frame(ctx,
- track,
- startpoint,
- framenr,
- &next_higher);
- retrieve_next_lower_usable_frame(ctx,
- track,
- startpoint,
- framenr,
- &next_lower);
- }
- }
- if (next_lower >= MINFRAME) {
- /* use next usable frame to the left.
- * Also default to this frame when we're in a gap */
- average_marker_positions(ctx, next_lower, r_ref_pos);
- }
- else if (next_higher < MAXFRAME) {
- average_marker_positions(ctx, next_higher, r_ref_pos);
- }
- use_values_from_fcurves(ctx, false);
- }
- }
- /* Linear interpolation of data retrieved at two measurement points.
- * This function is used to fill gaps in the middle of the covered area,
- * at frames without any usable tracks for stabilization.
- *
- * framenr is a position to interpolate for.
- * frame_a is a valid measurement point below framenr
- * frame_b is a valid measurement point above framenr
- * Returns truth if both measurements could actually be retrieved.
- * Otherwise output parameters remain unaltered
- */
- static bool interpolate_averaged_track_contributions(StabContext *ctx,
- int framenr,
- int frame_a,
- int frame_b,
- const float aspect,
- float r_translation[2],
- float r_pivot[2],
- float *r_angle,
- float *r_scale_step)
- {
- float t, s;
- float trans_a[2], trans_b[2];
- float angle_a, angle_b;
- float scale_a, scale_b;
- float pivot_a[2], pivot_b[2];
- bool success = false;
- BLI_assert(frame_a <= frame_b);
- BLI_assert(frame_a <= framenr);
- BLI_assert(framenr <= frame_b);
- t = ((float)framenr - frame_a) / (frame_b - frame_a);
- s = 1.0f - t;
- success = average_track_contributions(ctx, frame_a, aspect, trans_a, pivot_a, &angle_a, &scale_a);
- if (!success) {
- return false;
- }
- success = average_track_contributions(ctx, frame_b, aspect, trans_b, pivot_b, &angle_b, &scale_b);
- if (!success) {
- return false;
- }
- interp_v2_v2v2(r_translation, trans_a, trans_b, t);
- interp_v2_v2v2(r_pivot, pivot_a, pivot_b, t);
- *r_scale_step = s * scale_a + t * scale_b;
- *r_angle = s * angle_a + t * angle_b;
- return true;
- }
- /* Reorder tracks starting with those providing a tracking data frame
- * closest to the global anchor_frame. Tracks with a gap at anchor_frame or
- * starting farer away from anchor_frame altogether will be visited later.
- * This allows to build up baseline contributions incrementally.
- *
- * order is an array for sorting the tracks. Must be of suitable size to hold
- * all tracks.
- * Returns number of actually usable tracks, can be less than the overall number
- * of tracks.
- *
- * NOTE: After returning, the order array holds entries up to the number of
- * usable tracks, appropriately sorted starting with the closest tracks.
- * Initialization includes disabled tracks, since they might be enabled
- * through automation later.
- */
- static int establish_track_initialization_order(StabContext *ctx,
- TrackInitOrder *order)
- {
- size_t tracknr = 0;
- MovieTrackingTrack *track;
- MovieTracking *tracking = ctx->tracking;
- int anchor_frame = tracking->stabilization.anchor_frame;
- for (track = tracking->tracks.first; track != NULL; track = track->next) {
- MovieTrackingMarker *marker;
- order[tracknr].data = track;
- marker = get_closest_marker(ctx, track, anchor_frame);
- if (marker != NULL &&
- (track->flag & (TRACK_USE_2D_STAB | TRACK_USE_2D_STAB_ROT)))
- {
- order[tracknr].sort_value = abs(marker->framenr - anchor_frame);
- order[tracknr].reference_frame = marker->framenr;
- ++tracknr;
- }
- }
- if (tracknr) {
- qsort(order, tracknr, sizeof(TrackInitOrder), BLI_sortutil_cmp_int);
- }
- return tracknr;
- }
- /* Setup the constant part of this track's contribution to the determined frame
- * movement. Tracks usually don't provide tracking data for every frame. Thus,
- * for determining data at a given frame, we split up the contribution into a
- * part covered by actual measurements on this track, and the initial gap
- * between this track's reference frame and the global anchor_frame.
- * The (missing) data for the gap can be substituted by the average offset
- * observed by the other tracks covering the gap. This approximation doesn't
- * introduce wrong data, but it records data with incorrect weight. A totally
- * correct solution would require us to average the contribution per frame, and
- * then integrate stepwise over all frames -- which of course would be way more
- * expensive, especially for longer clips. To the contrary, our solution
- * cumulates the total contribution per track and averages afterwards over all
- * tracks; it can thus be calculated just based on the data of a single frame,
- * plus the "baseline" for the reference frame, which is what we are computing
- * here.
- *
- * Since we're averaging _contributions_, we have to calculate the _difference_
- * of the measured position at current frame and the position at the reference
- * frame. But the "reference" part of this difference is constant and can thus
- * be packed together with the baseline contribution into a single precomputed
- * vector per track.
- *
- * In case of the rotation contribution, the principle is the same, but we have
- * to compensate for the already determined translation and measure the pure
- * rotation, simply because this is how we model the offset: shift plus rotation
- * around the shifted rotation center. To circumvent problems with the
- * definition range of the arcus tangens function, we perform this baseline
- * addition and reference angle subtraction in polar coordinates and bake this
- * operation into a precomputed rotation matrix.
- *
- * track is a track to be initialized to initialize
- * reference_frame is a local frame for this track, the closest pick to the
- * global anchor_frame.
- * aspect is a total aspect ratio of the undistorted image (includes fame and
- * pixel aspect).
- * target_pos is a possibly animated target position as set by the user for
- * the reference_frame
- * average_translation is a value observed by the _other_ tracks for the gap
- * between reference_frame and anchor_frame. This
- * average must not contain contributions of frames
- * not yet initialized
- * average_angle in a similar way, the rotation value observed by the
- * _other_ tracks.
- * average_scale_step is an image scale factor observed on average by the other
- * tracks for this frame. This value is recorded and
- * averaged as logarithm. The recorded scale changes
- * are damped for very small contributions, to limit
- * the effect of probe points approaching the pivot
- * too closely.
- *
- * NOTE: when done, this track is marked as initialized
- */
- static void initialize_track_for_stabilization(StabContext *ctx,
- MovieTrackingTrack *track,
- int reference_frame,
- float aspect,
- const float average_translation[2],
- const float pivot[2],
- const float average_angle,
- const float average_scale_step)
- {
- float pos[2], angle, len;
- TrackStabilizationBase *local_data =
- access_stabilization_baseline_data(ctx, track);
- MovieTrackingMarker *marker =
- BKE_tracking_marker_get_exact(track, reference_frame);
- /* Logic for initialization order ensures there *is* a marker on that
- * very frame.
- */
- BLI_assert(marker != NULL);
- BLI_assert(local_data != NULL);
- /* Per track baseline value for translation. */
- sub_v2_v2v2(local_data->stabilization_offset_base,
- average_translation,
- marker->pos);
- /* Per track baseline value for rotation. */
- sub_v2_v2v2(pos, marker->pos, pivot);
- pos[0] *= aspect;
- angle = average_angle - atan2f(pos[1],pos[0]);
- angle_to_mat2(local_data->stabilization_rotation_base, angle);
- /* Per track baseline value for zoom. */
- len = len_v2(pos) + SCALE_ERROR_LIMIT_BIAS;
- local_data->stabilization_scale_base = expf(average_scale_step) / len;
- local_data->is_init_for_stabilization = true;
- }
- static void initialize_all_tracks(StabContext *ctx, float aspect)
- {
- size_t i, track_cnt = 0;
- MovieClip *clip = ctx->clip;
- MovieTracking *tracking = ctx->tracking;
- MovieTrackingTrack *track;
- TrackInitOrder *order;
- /* Attempt to start initialization at anchor_frame.
- * By definition, offset contribution is zero there.
- */
- int reference_frame = tracking->stabilization.anchor_frame;
- float average_angle=0, average_scale_step=0;
- float average_translation[2], average_pos[2], pivot[2];
- zero_v2(average_translation);
- zero_v2(pivot);
- /* Initialize private working data. */
- for (track = tracking->tracks.first; track != NULL; track = track->next) {
- TrackStabilizationBase *local_data =
- access_stabilization_baseline_data(ctx, track);
- if (!local_data) {
- local_data = MEM_callocN(sizeof(TrackStabilizationBase),
- "2D stabilization per track baseline data");
- attach_stabilization_baseline_data(ctx, track, local_data);
- }
- BLI_assert(local_data != NULL);
- local_data->track_weight_curve = retrieve_track_weight_animation(clip,
- track);
- local_data->is_init_for_stabilization = false;
- ++track_cnt;
- }
- if (!track_cnt) {
- return;
- }
- order = MEM_mallocN(track_cnt * sizeof(TrackInitOrder),
- "stabilization track order");
- if (!order) {
- return;
- }
- track_cnt = establish_track_initialization_order(ctx, order);
- if (track_cnt == 0) {
- goto cleanup;
- }
- /* starting point for pivot, before having initialized any track */
- average_marker_positions(ctx, reference_frame, average_pos);
- setup_pivot(average_pos, pivot);
- for (i = 0; i < track_cnt; ++i) {
- track = order[i].data;
- if (reference_frame != order[i].reference_frame) {
- reference_frame = order[i].reference_frame;
- average_track_contributions(ctx,
- reference_frame,
- aspect,
- average_translation,
- pivot,
- &average_angle,
- &average_scale_step);
- }
- initialize_track_for_stabilization(ctx,
- track,
- reference_frame,
- aspect,
- average_translation,
- pivot,
- average_angle,
- average_scale_step);
- }
- cleanup:
- MEM_freeN(order);
- }
- /* Retrieve the measurement of frame movement by averaging contributions of
- * active tracks.
- *
- * translation is a measurement in normalized 0..1 coordinates.
- * angle is a measurement in radians -pi..+pi counter clockwise relative to
- * translation compensated frame center
- * scale_step is a measurement of image scale changes, in logarithmic scale
- * (zero means scale == 1)
- * Returns calculation enabled and all data retrieved as expected for this frame.
- *
- * NOTE: when returning `false`, output parameters are reset to neutral values.
- */
- static bool stabilization_determine_offset_for_frame(StabContext *ctx,
- int framenr,
- float aspect,
- float r_translation[2],
- float r_pivot[2],
- float *r_angle,
- float *r_scale_step)
- {
- bool success = false;
- /* Early output if stabilization is disabled. */
- if ((ctx->stab->flag & TRACKING_2D_STABILIZATION) == 0) {
- zero_v2(r_translation);
- *r_scale_step = 0.0f;
- *r_angle = 0.0f;
- return false;
- }
- success = average_track_contributions(ctx,
- framenr,
- aspect,
- r_translation,
- r_pivot,
- r_angle,
- r_scale_step);
- if (!success) {
- /* Try to hold extrapolated settings beyond the definition range
- * and to interpolate in gaps without any usable tracking data
- * to prevent sudden jump to image zero position.
- */
- int next_lower = MINAFRAME;
- int next_higher = MAXFRAME;
- use_values_from_fcurves(ctx, true);
- find_next_working_frames(ctx, framenr, &next_lower, &next_higher);
- if (next_lower >= MINFRAME && next_higher < MAXFRAME) {
- success = interpolate_averaged_track_contributions(ctx,
- framenr,
- next_lower,
- next_higher,
- aspect,
- r_translation,
- r_pivot,
- r_angle,
- r_scale_step);
- }
- else if (next_higher < MAXFRAME) {
- /* Before start of stabilized range: extrapolate start point
- * settings.
- */
- success = average_track_contributions(ctx,
- next_higher,
- aspect,
- r_translation,
- r_pivot,
- r_angle,
- r_scale_step);
- }
- else if (next_lower >= MINFRAME) {
- /* After end of stabilized range: extrapolate end point settings. */
- success = average_track_contributions(ctx,
- next_lower,
- aspect,
- r_translation,
- r_pivot,
- r_angle,
- r_scale_step);
- }
- use_values_from_fcurves(ctx, false);
- }
- return success;
- }
- /* Calculate stabilization data (translation, scale and rotation) from given raw
- * measurements. Result is in absolute image dimensions (expanded image, square
- * pixels), includes automatic or manual scaling and compensates for a target
- * frame position, if given.
- *
- * size is a size of the expanded image, the width in pixels is size * aspect.
- * aspect is a ratio (width / height) of the effective canvas (square pixels).
- * do_compensate denotes whether to actually output values necessary to
- * _compensate_ the determined frame movement.
- * Otherwise, the effective target movement is returned.
- */
- static void stabilization_calculate_data(StabContext *ctx,
- int framenr,
- int size,
- float aspect,
- bool do_compensate,
- float scale_step,
- float r_translation[2],
- float r_pivot[2],
- float *r_scale,
- float *r_angle)
- {
- float target_pos[2], target_scale;
- float scaleinf = get_animated_scaleinf(ctx, framenr);
- if (ctx->stab->flag & TRACKING_STABILIZE_SCALE) {
- *r_scale = expf(scale_step * scaleinf); /* Averaged in log scale */
- }
- else {
- *r_scale = 1.0f;
- }
- mul_v2_fl(r_translation, get_animated_locinf(ctx, framenr));
- *r_angle *= get_animated_rotinf(ctx, framenr);
- /* Compensate for a target frame position.
- * This allows to follow tracking / panning shots in a semi manual fashion,
- * when animating the settings for the target frame position.
- */
- get_animated_target_pos(ctx, framenr, target_pos);
- sub_v2_v2(r_translation, target_pos);
- *r_angle -= get_animated_target_rot(ctx, framenr);
- target_scale = get_animated_target_scale(ctx, framenr);
- if (target_scale != 0.0f) {
- *r_scale /= target_scale;
- /* target_scale is an expected/intended reference zoom value */
- }
- /* Convert from relative to absolute coordinates, square pixels. */
- r_translation[0] *= (float)size * aspect;
- r_translation[1] *= (float)size;
- r_pivot[0] *= (float)size * aspect;
- r_pivot[1] *= (float)size;
- /* Output measured data, or inverse of the measured values for
- * compensation?
- */
- if (do_compensate) {
- mul_v2_fl(r_translation, -1.0f);
- *r_angle *= -1.0f;
- if (*r_scale != 0.0f) {
- *r_scale = 1.0f / *r_scale;
- }
- }
- }
- static void stabilization_data_to_mat4(float pixel_aspect,
- const float pivot[2],
- const float translation[2],
- float scale,
- float angle,
- float r_mat[4][4])
- {
- float translation_mat[4][4], rotation_mat[4][4], scale_mat[4][4],
- pivot_mat[4][4], inv_pivot_mat[4][4],
- aspect_mat[4][4], inv_aspect_mat[4][4];
- float scale_vector[3] = {scale, scale, 1.0f};
- unit_m4(translation_mat);
- unit_m4(rotation_mat);
- unit_m4(scale_mat);
- unit_m4(aspect_mat);
- unit_m4(pivot_mat);
- unit_m4(inv_pivot_mat);
- /* aspect ratio correction matrix */
- aspect_mat[0][0] /= pixel_aspect;
- invert_m4_m4(inv_aspect_mat, aspect_mat);
- add_v2_v2(pivot_mat[3], pivot);
- sub_v2_v2(inv_pivot_mat[3], pivot);
- size_to_mat4(scale_mat, scale_vector); /* scale matrix */
- add_v2_v2(translation_mat[3], translation); /* translation matrix */
- rotate_m4(rotation_mat, 'Z', angle); /* rotation matrix */
- /* Compose transformation matrix. */
- mul_m4_series(r_mat, aspect_mat, translation_mat,
- pivot_mat, scale_mat, rotation_mat, inv_pivot_mat,
- inv_aspect_mat);
- }
- /* Calculate scale factor necessary to eliminate black image areas
- * caused by the compensating movements of the stabilizator.
- * This function visits every frame where stabilisation data is
- * available and determines the factor for this frame. The overall
- * largest factor found is returned as result.
- *
- * NOTE: all tracks need to be initialized before calling this function.
- */
- static float calculate_autoscale_factor(StabContext *ctx,
- int size,
- float aspect)
- {
- MovieTrackingStabilization *stab = ctx->stab;
- float pixel_aspect = ctx->tracking->camera.pixel_aspect;
- int height = size, width = aspect*size;
- int sfra = INT_MAX, efra = INT_MIN, cfra;
- float scale = 1.0f, scale_step = 0.0f;
- MovieTrackingTrack *track;
- /* Calculate maximal frame range of tracks where stabilization is active. */
- for (track = ctx->tracking->tracks.first; track; track = track->next) {
- if ((track->flag & TRACK_USE_2D_STAB) ||
- ((stab->flag & TRACKING_STABILIZE_ROTATION) &&
- (track->flag & TRACK_USE_2D_STAB_ROT)))
- {
- int first_frame = track->markers[0].framenr;
- int last_frame = track->markers[track->markersnr - 1].framenr;
- sfra = min_ii(sfra, first_frame);
- efra = max_ii(efra, last_frame);
- }
- }
- use_values_from_fcurves(ctx, true);
- for (cfra = sfra; cfra <= efra; cfra++) {
- float translation[2], pivot[2], angle, tmp_scale;
- float mat[4][4];
- const float points[4][2] = {{0.0f, 0.0f},
- {0.0f, height},
- {width, height},
- {width, 0.0f}};
- const bool do_compensate = true;
- /* Calculate stabilization parameters for the current frame. */
- stabilization_determine_offset_for_frame(ctx,
- cfra,
- aspect,
- translation,
- pivot,
- &angle,
- &scale_step);
- stabilization_calculate_data(ctx,
- cfra,
- size,
- aspect,
- do_compensate,
- scale_step,
- translation,
- pivot,
- &tmp_scale,
- &angle);
- /* Compose transformation matrix. */
- /* NOTE: Here we operate in NON-COMPENSATED coordinates, meaning we have
- * to construct transformation matrix using proper pivot point.
- * Compensation for that will happen later on.
- */
- stabilization_data_to_mat4(pixel_aspect,
- pivot,
- translation,
- tmp_scale,
- angle,
- mat);
- /* Investigate the transformed border lines for this frame;
- * find out, where it cuts the original frame.
- */
- for (int edge_index = 0; edge_index < 4; edge_index++) {
- /* Calculate coordinates of stabilized frame edge points.
- * Use matrix multiplication here so we operate in homogeneous
- * coordinates.
- */
- float stable_edge_p1[3], stable_edge_p2[3];
- copy_v2_v2(stable_edge_p1, points[edge_index]);
- copy_v2_v2(stable_edge_p2, points[(edge_index + 1) % 4]);
- stable_edge_p1[2] = stable_edge_p2[2] = 0.0f;
- mul_m4_v3(mat, stable_edge_p1);
- mul_m4_v3(mat, stable_edge_p2);
- /* Now we iterate over all original frame corners (we call them
- * 'point' here) to see if there's black area between stabilized
- * frame edge and original point.
- */
- for (int point_index = 0; point_index < 4; point_index++) {
- const float point[3] = {points[point_index][0],
- points[point_index][1],
- 0.0f};
- /* Calculate vector which goes from first edge point to
- * second one.
- */
- float stable_edge_vec[3];
- sub_v3_v3v3(stable_edge_vec, stable_edge_p2, stable_edge_p1);
- /* Calculate vector which connects current frame point to
- * first edge point.
- */
- float point_to_edge_start_vec[3];
- sub_v3_v3v3(point_to_edge_start_vec, point, stable_edge_p1);
- /* Use this two vectors to check whether frame point is inside
- * of the stabilized frame or not.
- * If the point is inside, there is no black area happening
- * and no scaling required for it.
- */
- if (cross_v2v2(stable_edge_vec, point_to_edge_start_vec) >= 0.0f) {
- /* We are scaling around motion-compensated pivot point. */
- float scale_pivot[2];
- add_v2_v2v2(scale_pivot, pivot, translation);
- /* Calculate line which goes via `point` and parallel to
- * the stabilized frame edge. This line is coming via
- * `point` and `point2` at the end.
- */
- float point2[2];
- add_v2_v2v2(point2, point, stable_edge_vec);
- /* Calculate actual distance between pivot point and
- * the stabilized frame edge. Then calculate distance
- * between pivot point and line which goes via actual
- * corner and is parallel to the edge.
- *
- * Dividing one by another will give us required scale
- * factor to get rid of black areas.
- */
- float real_dist = dist_to_line_v2(scale_pivot,
- stable_edge_p1,
- stable_edge_p2);
- float required_dist = dist_to_line_v2(scale_pivot,
- point,
- point2);
- const float S = required_dist / real_dist;
- scale = max_ff(scale, S);
- }
- }
- }
- }
- if (stab->maxscale > 0.0f) {
- scale = min_ff(scale, stab->maxscale);
- }
- use_values_from_fcurves(ctx, false);
- return scale;
- }
- /* Prepare working data and determine reference point for each track.
- *
- * NOTE: These calculations _could_ be cached and reused for all frames of the
- * same clip. However, since proper initialization depends on (weight)
- * animation and setup of tracks, ensuring consistency of cached init data
- * turns out to be tricky, hard to maintain and generally not worth the
- * effort. Thus we'll re-initialize on every frame.
- */
- static StabContext *init_stabilizer(MovieClip *clip, int size, float aspect)
- {
- StabContext *ctx = initialize_stabilization_working_context(clip);
- BLI_assert(ctx != NULL);
- initialize_all_tracks(ctx, aspect);
- if (ctx->stab->flag & TRACKING_AUTOSCALE) {
- ctx->stab->scale = 1.0;
- ctx->stab->scale = calculate_autoscale_factor(ctx, size, aspect);
- }
- /* By default, just use values for the global current frame. */
- use_values_from_fcurves(ctx, false);
- return ctx;
- }
- /* === public interface functions === */
- /* Get stabilization data (translation, scaling and angle) for a given frame.
- * Returned data describes how to compensate the detected movement, but with any
- * chosen scale factor already applied and any target frame position already
- * compensated. In case stabilization fails or is disabled, neutral values are
- * returned.
- *
- * framenr is a frame number, relative to the clip (not relative to the scene
- * timeline)
- * width is an effective width of the canvas (square pixels), used to scale the
- * determined translation
- *
- * Outputs:
- * - translation of the lateral shift, absolute canvas coordinates
- * (square pixels).
- * - scale of the scaling to apply
- * - angle of the rotation angle, relative to the frame center
- */
- /* TODO(sergey): Use r_ prefix for output parameters here. */
- void BKE_tracking_stabilization_data_get(MovieClip *clip,
- int framenr,
- int width,
- int height,
- float translation[2],
- float *scale,
- float *angle)
- {
- StabContext *ctx = NULL;
- MovieTracking *tracking = &clip->tracking;
- bool enabled = (tracking->stabilization.flag & TRACKING_2D_STABILIZATION);
- /* Might become a parameter of a stabilization compositor node. */
- bool do_compensate = true;
- float scale_step = 0.0f;
- float pixel_aspect = tracking->camera.pixel_aspect;
- float aspect = (float)width * pixel_aspect / height;
- int size = height;
- float pivot[2];
- if (enabled) {
- ctx = init_stabilizer(clip, size, aspect);
- }
- if (enabled &&
- stabilization_determine_offset_for_frame(ctx,
- framenr,
- aspect,
- translation,
- pivot,
- angle,
- &scale_step))
- {
- stabilization_calculate_data(ctx,
- framenr,
- size,
- aspect,
- do_compensate,
- scale_step,
- translation,
- pivot,
- scale,
- angle);
- compensate_rotation_center(size,
- aspect,
- *angle,
- *scale,
- pivot,
- translation);
- }
- else {
- zero_v2(translation);
- *scale = 1.0f;
- *angle = 0.0f;
- }
- discard_stabilization_working_context(ctx);
- }
- /* Stabilize given image buffer using stabilization data for a specified
- * frame number.
- *
- * NOTE: frame number should be in clip space, not scene space.
- */
- /* TODO(sergey): Use r_ prefix for output parameters here. */
- ImBuf *BKE_tracking_stabilize_frame(MovieClip *clip,
- int framenr,
- ImBuf *ibuf,
- float translation[2],
- float *scale,
- float *angle)
- {
- float tloc[2], tscale, tangle;
- MovieTracking *tracking = &clip->tracking;
- MovieTrackingStabilization *stab = &tracking->stabilization;
- ImBuf *tmpibuf;
- int width = ibuf->x, height = ibuf->y;
- float pixel_aspect = tracking->camera.pixel_aspect;
- float mat[4][4];
- int j, filter = tracking->stabilization.filter;
- void (*interpolation)(struct ImBuf *, struct ImBuf *, float, float, int, int) = NULL;
- int ibuf_flags;
- if (translation)
- copy_v2_v2(tloc, translation);
- if (scale)
- tscale = *scale;
- /* Perform early output if no stabilization is used. */
- if ((stab->flag & TRACKING_2D_STABILIZATION) == 0) {
- if (translation)
- zero_v2(translation);
- if (scale)
- *scale = 1.0f;
- if (angle)
- *angle = 0.0f;
- return ibuf;
- }
- /* Allocate frame for stabilization result. */
- ibuf_flags = 0;
- if (ibuf->rect)
- ibuf_flags |= IB_rect;
- if (ibuf->rect_float)
- ibuf_flags |= IB_rectfloat;
- tmpibuf = IMB_allocImBuf(ibuf->x, ibuf->y, ibuf->planes, ibuf_flags);
- /* Calculate stabilization matrix. */
- BKE_tracking_stabilization_data_get(clip, framenr, width, height, tloc, &tscale, &tangle);
- BKE_tracking_stabilization_data_to_mat4(ibuf->x, ibuf->y, pixel_aspect, tloc, tscale, tangle, mat);
- /* The following code visits each nominal target grid position
- * and picks interpolated data "backwards" from source.
- * thus we need the inverse of the transformation to apply. */
- invert_m4(mat);
- if (filter == TRACKING_FILTER_NEAREST)
- interpolation = nearest_interpolation;
- else if (filter == TRACKING_FILTER_BILINEAR)
- interpolation = bilinear_interpolation;
- else if (filter == TRACKING_FILTER_BICUBIC)
- interpolation = bicubic_interpolation;
- else
- /* fallback to default interpolation method */
- interpolation = nearest_interpolation;
- /* This function is only used for display in clip editor and
- * sequencer only, which would only benefit of using threads
- * here.
- *
- * But need to keep an eye on this if the function will be
- * used in other cases.
- */
- #pragma omp parallel for if (tmpibuf->y > 128)
- for (j = 0; j < tmpibuf->y; j++) {
- int i;
- for (i = 0; i < tmpibuf->x; i++) {
- float vec[3] = {i, j, 0.0f};
- mul_v3_m4v3(vec, mat, vec);
- interpolation(ibuf, tmpibuf, vec[0], vec[1], i, j);
- }
- }
- if (tmpibuf->rect_float)
- tmpibuf->userflags |= IB_RECT_INVALID;
- if (translation)
- copy_v2_v2(translation, tloc);
- if (scale)
- *scale = tscale;
- if (angle)
- *angle = tangle;
- return tmpibuf;
- }
- /* Build a 4x4 transformation matrix based on the given 2D stabilization data.
- * mat is a 4x4 matrix in homogeneous coordinates, adapted to the
- * final image buffer size and compensated for pixel aspect ratio,
- * ready for direct OpenGL drawing.
- *
- * TODO(sergey): The signature of this function should be changed. we actually
- * don't need the dimensions of the image buffer. Instead we
- * should consider to provide the pivot point of the rotation as a
- * further stabilization data parameter.
- */
- void BKE_tracking_stabilization_data_to_mat4(int buffer_width,
- int buffer_height,
- float pixel_aspect,
- float translation[2],
- float scale,
- float angle,
- float r_mat[4][4])
- {
- /* Since we cannot receive the real pivot point coordinates (API limitation),
- * we perform the rotation/scale around the center of frame.
- * Then we correct by an additional shift, which was calculated in
- * compensate_rotation_center() and "sneaked in" as additional offset
- * in the translation parameter. This works, since translation needs to be
- * applied after rotation/scale anyway. Thus effectively the image gets
- * rotated around the desired pivot point
- */
- /* TODO(sergey) pivot shouldn't be calculated here, rather received
- * as a parameter.
- */
- float pivot[2];
- pivot[0] = 0.5f * pixel_aspect * buffer_width;
- pivot[1] = 0.5f * buffer_height;
- /* Compose transformation matrix. */
- stabilization_data_to_mat4(pixel_aspect,
- pivot,
- translation,
- scale,
- angle,
- r_mat);
- }
|