From c2f4ab489c5a3184e27511dd232602fefccb2790 Mon Sep 17 00:00:00 2001 From: Jakub Ksiezniak Date: Tue, 31 Dec 2013 08:20:31 +0100 Subject: [PATCH] Created a new module to support vid.stab library. --- src/modules/vid.stab/Makefile | 45 ++++ src/modules/vid.stab/common.h | 29 +++ src/modules/vid.stab/configure | 22 ++ src/modules/vid.stab/factory.c | 45 ++++ src/modules/vid.stab/filter_deshake.cpp | 212 ++++++++++++++++++ src/modules/vid.stab/filter_deshake.yml | 172 +++++++++++++++ src/modules/vid.stab/filter_detect.cpp | 220 ++++++++++++++++++ src/modules/vid.stab/filter_detect.yml | 218 ++++++++++++++++++ src/modules/vid.stab/filter_transform.cpp | 258 ++++++++++++++++++++++ src/modules/vid.stab/filter_transform.yml | 206 +++++++++++++++++ 10 files changed, 1427 insertions(+) create mode 100644 src/modules/vid.stab/Makefile create mode 100644 src/modules/vid.stab/common.h create mode 100755 src/modules/vid.stab/configure create mode 100644 src/modules/vid.stab/factory.c create mode 100644 src/modules/vid.stab/filter_deshake.cpp create mode 100644 src/modules/vid.stab/filter_deshake.yml create mode 100644 src/modules/vid.stab/filter_detect.cpp create mode 100644 src/modules/vid.stab/filter_detect.yml create mode 100644 src/modules/vid.stab/filter_transform.cpp create mode 100644 src/modules/vid.stab/filter_transform.yml diff --git a/src/modules/vid.stab/Makefile b/src/modules/vid.stab/Makefile new file mode 100644 index 00000000..24eba4d7 --- /dev/null +++ b/src/modules/vid.stab/Makefile @@ -0,0 +1,45 @@ +CFLAGS += -I../.. + +LDFLAGS += -L../../framework -lmlt -lm + +include ../../../config.mak + +TARGET = ../libmltvidstab$(LIBSUF) + +OBJS = factory.o + +CPPOBJS = filter_deshake.o +CPPOBJS += filter_detect.o +CPPOBJS += filter_transform.o + +CXXFLAGS += -Wno-deprecated $(CFLAGS) +CXXFLAGS += $(shell pkg-config --cflags vidstab) + +LDFLAGS += -L../../mlt++ -lmlt++ +LDFLAGS += $(shell pkg-config --libs vidstab) + + +SRCS := $(OBJS:.o=.c) $(CPPOBJS:.o=.cpp) + +all: $(TARGET) + +$(TARGET): $(OBJS) $(CPPOBJS) + $(CXX) $(SHFLAGS) -o $@ $(OBJS) $(CPPOBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CXX) -MM $(CXXFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend config.h config.mak + +clean: + rm -f $(OBJS) $(TARGET) $(CPPOBJS) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(moduledir)" + install -d "$(DESTDIR)$(mltdatadir)/vid.stab" + install -m 644 *.yml "$(DESTDIR)$(mltdatadir)/vid.stab" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/vid.stab/common.h b/src/modules/vid.stab/common.h new file mode 100644 index 00000000..b373dbdd --- /dev/null +++ b/src/modules/vid.stab/common.h @@ -0,0 +1,29 @@ +/* + * common.h + * + * Created on: 20 gru 2013 + * Author: Jakub Ksiezniak + */ + +#ifndef VIDSTAB_COMMON_H_ +#define VIDSTAB_COMMON_H_ + +extern "C" { +#include +#include +} + +inline VSPixelFormat convertImageFormat(mlt_image_format &format) { + switch (format) { + case mlt_image_rgb24: + return PF_RGB24; + case mlt_image_rgb24a: + return PF_RGBA; + case mlt_image_yuv420p: + return PF_YUV420P; + default: + return PF_NONE; + } +} + +#endif /* VIDSTAB_COMMON_H_ */ diff --git a/src/modules/vid.stab/configure b/src/modules/vid.stab/configure new file mode 100755 index 00000000..7a852a1d --- /dev/null +++ b/src/modules/vid.stab/configure @@ -0,0 +1,22 @@ +#!/bin/sh + +if [ "$help" != "1" ] +then + if ! $(pkg-config vidstab) + then + echo "- vid.stab not found: disabling" + touch ../disable-vidstab + exit 0 + fi + + echo > config.mak + case $targetos in + Darwin) + ;; + MinGW) + ;; + *) + ;; + esac + exit 0 +fi diff --git a/src/modules/vid.stab/factory.c b/src/modules/vid.stab/factory.c new file mode 100644 index 00000000..967e74c5 --- /dev/null +++ b/src/modules/vid.stab/factory.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 Dan Dennedy + * factory.c -- the factory method interfaces + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +// extern mlt_filter filter_vidstab_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); +extern mlt_filter filter_deshake_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); +extern mlt_filter filter_detect_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); +extern mlt_filter filter_transform_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); + +static mlt_properties metadata( mlt_service_type type, const char *id, void *data ) +{ + char file[ PATH_MAX ]; + snprintf( file, PATH_MAX, "%s/vid.stab/filter_%s.yml", mlt_environment( "MLT_DATA" ), id ); + return mlt_properties_parse_yaml( file ); +} + +MLT_REPOSITORY +{ + MLT_REGISTER( filter_type, "vid.stab.deshake", filter_deshake_init ); + MLT_REGISTER( filter_type, "vid.stab.detect", filter_detect_init ); + MLT_REGISTER( filter_type, "vid.stab.transform", filter_transform_init ); + + MLT_REGISTER_METADATA( filter_type, "vid.stab.deshake", metadata, "filter_deshake.yml" ); + MLT_REGISTER_METADATA( filter_type, "vid.stab.detect", metadata, "filter_detect.yml" ); + MLT_REGISTER_METADATA( filter_type, "vid.stab.transform", metadata, "filter_transform.yml" ); +} diff --git a/src/modules/vid.stab/filter_deshake.cpp b/src/modules/vid.stab/filter_deshake.cpp new file mode 100644 index 00000000..b998726f --- /dev/null +++ b/src/modules/vid.stab/filter_deshake.cpp @@ -0,0 +1,212 @@ +/* + * filter_deshake.cpp + * Copyright (C) 2013 + * Marco Gittler + * Jakub Ksiezniak + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +extern "C" +{ +#include +} + +#include +#include +#include +#include "common.h" + +#define FILTER_NAME "vid.stab.deshake" + +typedef struct _deshake_data +{ + bool initialized; + VSMotionDetect md; + VSTransformData td; + VSSlidingAvgTrans avg; + + void *parent; +} DeshakeData; + +int init_deshake(DeshakeData *data, mlt_properties properties, + mlt_image_format *format, int *width, int *height, char* interps) +{ + VSPixelFormat pf = convertImageFormat(*format); + VSFrameInfo fiIn, fiOut; + vsFrameInfoInit(&fiIn, *width, *height, pf); + vsFrameInfoInit(&fiOut, *width, *height, pf); + + VSMotionDetectConfig conf = vsMotionDetectGetDefaultConfig(FILTER_NAME); + conf.shakiness = mlt_properties_get_int(properties, "shakiness"); + conf.accuracy = mlt_properties_get_int(properties, "accuracy"); + conf.stepSize = mlt_properties_get_int(properties, "stepsize"); + conf.algo = mlt_properties_get_int(properties, "algo"); + conf.contrastThreshold = mlt_properties_get_double(properties, "mincontrast"); + conf.show = 0; + + vsMotionDetectInit(&data->md, &conf, &fiIn); + + VSTransformConfig tdconf = vsTransformGetDefaultConfig(FILTER_NAME); + tdconf.smoothing = mlt_properties_get_int(properties, "smoothing"); + tdconf.maxShift = mlt_properties_get_int(properties, "maxshift"); + tdconf.maxAngle = mlt_properties_get_double(properties, "maxangle"); + tdconf.crop = (VSBorderType) mlt_properties_get_int(properties, "crop"); + tdconf.zoom = mlt_properties_get_int(properties, "zoom"); + tdconf.optZoom = mlt_properties_get_int(properties, "optzoom"); + tdconf.relative = 1; + tdconf.invert = 0; + + // by default a bilinear interpolation is selected + tdconf.interpolType = VS_BiLinear; + if (strcmp(interps, "nearest") == 0 || strcmp(interps, "neighbor") == 0) + tdconf.interpolType = VS_Zero; + else if (strcmp(interps, "tiles") == 0 || strcmp(interps, "fast_bilinear") == 0) + tdconf.interpolType = VS_Linear; + + vsTransformDataInit(&data->td, &tdconf, &fiIn, &fiOut); + + data->avg.initialized = 0; + return 0; +} + +void clear_deshake(DeshakeData *data) +{ + if (data->initialized) + { + vsMotionDetectionCleanup(&data->md); + vsTransformDataCleanup(&data->td); + } +} + +static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, + int *width, int *height, int writable) +{ + mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); + mlt_properties properties = MLT_FILTER_PROPERTIES(filter); + + *format = mlt_image_yuv420p; + DeshakeData *data = static_cast(filter->child); + + int error = mlt_frame_get_image(frame, image, format, width, height, 1); + if (!error) + { + // Service locks are for concurrency control + mlt_service_lock(MLT_FILTER_SERVICE(filter)); + + // Handle signal from app to re-init data + if (mlt_properties_get_int(properties, "refresh")) + { + mlt_properties_set(properties, "refresh", NULL); + clear_deshake(data); + data->initialized = false; + } + + if (!data->initialized) + { + char *interps = mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "rescale.interp"); + init_deshake(data, properties, format, width, height, + interps); + data->initialized = true; + } + + VSMotionDetect* md = &data->md; + VSTransformData* td = &data->td; + LocalMotions localmotions; + VSTransform motion; + VSFrame vsFrame; + + vsFrameFillFromBuffer(&vsFrame, *image, &md->fi); + vsMotionDetection(md, &localmotions, &vsFrame); + + motion = vsSimpleMotionsToTransform(td, &localmotions); + vs_vector_del(&localmotions); + + vsTransformPrepare(td, &vsFrame, &vsFrame); + + VSTransform t = vsLowPassTransforms(td, &data->avg, &motion); +// mlt_log_warning(filter, "Trans: det: %f %f %f \n\t\t act: %f %f %f %f", +// motion.x, motion.y, motion.alpha, +// t.x, t.y, t.alpha, t.zoom); + vsDoTransform(td, t); + vsTransformFinish(td); + + mlt_service_unlock(MLT_FILTER_SERVICE(filter)); + } + + return error; +} + +static mlt_frame process_filter(mlt_filter filter, mlt_frame frame) +{ + mlt_frame_push_service(frame, filter); + mlt_frame_push_get_image(frame, get_image); + return frame; +} + +static void close_filter(mlt_filter filter) +{ + DeshakeData *data = static_cast(filter->child); + if (data) + { + clear_deshake(data); + delete data; + filter->child = NULL; + } +} + +extern "C" +{ + +mlt_filter filter_deshake_init(mlt_profile profile, mlt_service_type type, + const char *id, char *arg) +{ + mlt_filter filter = NULL; + + DeshakeData *data = new DeshakeData; + memset(data, 0, sizeof(DeshakeData)); + + if ((filter = mlt_filter_new())) + { + filter->process = process_filter; + filter->close = close_filter; + filter->child = data; + data->parent = filter; + + mlt_properties properties = MLT_FILTER_PROPERTIES(filter); + //properties for stabilize + mlt_properties_set(properties, "shakiness", "4"); + mlt_properties_set(properties, "accuracy", "4"); + mlt_properties_set(properties, "stepsize", "6"); + mlt_properties_set(properties, "algo", "1"); + mlt_properties_set(properties, "mincontrast", "0.3"); + + //properties for transform + mlt_properties_set(properties, "smoothing", "10"); + mlt_properties_set(properties, "maxshift", "-1"); + mlt_properties_set(properties, "maxangle", "-1"); + mlt_properties_set(properties, "crop", "0"); + mlt_properties_set(properties, "zoom", "0"); + mlt_properties_set(properties, "optzoom", "1"); + mlt_properties_set(properties, "sharpen", "0.8"); + + return filter; + } + + delete data; + return NULL; +} + +} diff --git a/src/modules/vid.stab/filter_deshake.yml b/src/modules/vid.stab/filter_deshake.yml new file mode 100644 index 00000000..98c5232c --- /dev/null +++ b/src/modules/vid.stab/filter_deshake.yml @@ -0,0 +1,172 @@ +schema_version: 0.1 +type: filter +identifier: vid.stab.deshake +title: Vid.Stab Deshake +copyright: Jakub Ksiezniak +creator: Georg Martius +version: 1 +license: GPLv2 +language: en +url: http://public.hronopik.de/vid.stab/ +tags: + - Video +description: Stabilize Video (for wiggly/rolling video) +notes: > + Deshakes a video clip by extracting relative transformations + of subsequent frames and transforms the high-frequency away. + This is a single pass verion of stabilize and transform plugin. + +parameters: + - identifier: shakiness + title: Shakiness + type: integer + description: How shaky is the video and how quick is the camera? (analysis) + readonly: no + required: no + minimum: 1 + maximum: 10 + default: 4 + mutable: yes + widget: spinner + + - identifier: accuracy + title: Accuracy + type: integer + description: Accuracy of shakiness detection (analysis) + readonly: no + required: no + minimum: 1 + maximum: 15 + default: 4 + mutable: yes + widget: spinner + + - identifier: stepsize + title: Stepsize + type: integer + description: Step size of search process, region around minimum is scanned with 1 pixel resolution (analysis) + readonly: no + required: no + minimum: 0 + maximum: 100 + default: 6 + mutable: yes + widget: spinner + + - identifier: algo + title: Algorithm + type: integer + description: 0 = brute force (translation only), 1 = small measurement fields (analysis) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 1 + mutable: yes + widget: spinner + + - identifier: mincontrast + title: Minimum Contrast + type: float + description: Below this contrast a field is discarded (analysis) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 0.3 + mutable: yes + widget: spinner + + - identifier: smoothing + title: Smoothing + type: integer + description: number of frames for lowpass filtering (2N + 1 frames) (transform) + readonly: no + required: no + minimum: 0 + maximum: 100 + default: 10 + mutable: yes + widget: spinner + + - identifier: maxshift + title: Maxshift + type: integer + description: maximum translation, -1 = no limit (transform) + unit: pixels + readonly: no + required: no + minimum: -1 + maximum: 1000 + default: -1 + mutable: yes + widget: spinner + + - identifier: maxangle + title: Maxangle + type: float + description: max angle to rotate, -1 = no limit (transform) + unit: radians + readonly: no + required: no + minimum: -1 + maximum: 3.142 + default: -1 + mutable: yes + widget: spinner + + - identifier: crop + title: Crop + type: integer + description: 0 = keep border, 1 = black background (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 0 + mutable: yes + widget: spinner + + - identifier: zoom + title: Zoom + type: integer + description: additional zoom amount (transform) + unit: percent + readonly: no + required: no + minimum: -500 + maximum: 500 + default: 0 + mutable: yes + widget: spinner + + - identifier: optzoom + title: Optimal Zoom + type: integer + description: automatically determine optimal zoom (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 1 + mutable: yes + widget: spinner + + - identifier: sharpen + title: Sharpen Image + type: float + description: amount of sharpening (transform) + readonly: no + required: no + minimum: 0 + maximum: 10 + default: 0.8 + mutable: yes + widget: spinner + + - identifier: refresh + description: > + Applications should set this when it updates a transform parameter. + type: integer + minimum: 0 + maximum: 1 diff --git a/src/modules/vid.stab/filter_detect.cpp b/src/modules/vid.stab/filter_detect.cpp new file mode 100644 index 00000000..c5dc9219 --- /dev/null +++ b/src/modules/vid.stab/filter_detect.cpp @@ -0,0 +1,220 @@ +/* + * filter_detect.cpp + * Copyright (C) 2013 + * Marco Gittler + * Jakub Ksiezniak + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +extern "C" +{ +#include +#include +#include +} + +#include +#include +#include "common.h" + +#define FILTER_NAME "vid.stab.detect" + +typedef struct _stab_data +{ + bool initialized; + VSMotionDetect md; + mlt_animation animation; + + void *parent; +} StabData; + +char* vectors_serializer(mlt_animation animation, int length) +{ + return mlt_animation_serialize(animation); +} + +#include +char* lm_serializer(LocalMotions *lms, int length) +{ + std::ostringstream oss; + int size = vs_vector_size(lms); + for (int i = 0; i < size; ++i) + { + LocalMotion* lm = (LocalMotion*) vs_vector_get(lms, i); + oss << lm->v.x << ' '; + oss << lm->v.y << ' '; + oss << lm->f.x << ' '; + oss << lm->f.y << ' '; + oss << lm->f.size << ' '; + oss << lm->contrast << ' '; + oss << lm->match << ' '; + } + return strdup(oss.str().c_str()); +} + +void lm_destructor(void *lms) +{ + vs_vector_del(static_cast(lms)); +} + +static void serialize_localmotions(StabData* data, LocalMotions &vectors, mlt_position pos) +{ + mlt_animation_item_s item; + + // Initialize animation item + item.is_key = 1; + item.frame = data->md.frameNum; + item.keyframe_type = mlt_keyframe_discrete; + item.property = mlt_property_init(); + + mlt_property_set_data(item.property, &vectors, 1, lm_destructor, (mlt_serialiser) lm_serializer); + mlt_animation_insert(data->animation, &item); + mlt_property_close(item.property); +} + +int init_detect(StabData *data, mlt_properties properties, mlt_image_format *format, int *width, int *height) +{ + VSPixelFormat pf = convertImageFormat(*format); + VSFrameInfo fi; + vsFrameInfoInit(&fi, *width, *height, pf); + + VSMotionDetectConfig conf = vsMotionDetectGetDefaultConfig(FILTER_NAME); + conf.shakiness = mlt_properties_get_int(properties, "shakiness"); + conf.accuracy = mlt_properties_get_int(properties, "accuracy"); + conf.stepSize = mlt_properties_get_int(properties, "stepsize"); + conf.algo = mlt_properties_get_int(properties, "algo"); + conf.contrastThreshold = mlt_properties_get_double(properties, "mincontrast"); + conf.show = mlt_properties_get_int(properties, "show"); + conf.virtualTripod = mlt_properties_get_int(properties, "tripod"); + + vsMotionDetectInit(&data->md, &conf, &fi); + + // change name of the filter, so the vid.stab.transform will be used in 2nd-pass. + mlt_properties_set(properties, "mlt_service", "vid.stab.transform"); + return 0; +} + +static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) +{ + mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); + mlt_properties properties = MLT_FILTER_PROPERTIES(filter); + + *format = mlt_image_yuv420p; + StabData *data = static_cast(filter->child); + + int error = mlt_frame_get_image(frame, image, format, width, height, writable); + if (!error) + { + // Service locks are for concurrency control + mlt_service_lock(MLT_FILTER_SERVICE(filter)); + + if (!data->initialized) + { + init_detect(data, properties, format, width, height); + data->initialized = true; + } + + VSMotionDetect* md = &data->md; + LocalMotions localmotions; + VSFrame vsFrame; + vsFrameFillFromBuffer(&vsFrame, *image, &md->fi); + + // detect and save motions + vsMotionDetection(md, &localmotions, &vsFrame); + mlt_position pos = mlt_filter_get_position( filter, frame ); + serialize_localmotions(data, localmotions, pos); + + mlt_service_unlock(MLT_FILTER_SERVICE(filter)); + } + + return error; +} + +static mlt_frame process_filter(mlt_filter filter, mlt_frame frame) +{ + mlt_frame_push_service(frame, filter); + mlt_frame_push_get_image(frame, get_image); + return frame; +} + +static void close_filter(mlt_filter filter) +{ + StabData *data = static_cast(filter->child); + if (data) + { + if (data->initialized) + { + vsMotionDetectionCleanup(&data->md); + } + delete data; + filter->child = NULL; + } +} + +extern "C" +{ + +mlt_filter filter_detect_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) +{ + mlt_filter filter = NULL; + + StabData *data = new StabData; + memset(data, 0, sizeof(StabData)); + + if ((filter = mlt_filter_new())) + { + filter->process = process_filter; + filter->close = close_filter; + filter->child = data; + + data->animation = mlt_animation_new(); + data->parent = filter; + + mlt_properties properties = MLT_FILTER_PROPERTIES(filter); + mlt_properties_set(properties, "mlt_service", "vid.stab.transform"); + + //properties for stabilize + mlt_properties_set(properties, "shakiness", "4"); + mlt_properties_set(properties, "accuracy", "4"); + mlt_properties_set(properties, "stepsize", "6"); + mlt_properties_set(properties, "algo", "1"); + mlt_properties_set(properties, "mincontrast", "0.3"); + mlt_properties_set(properties, "show", "0"); + mlt_properties_set(properties, "tripod", "0"); + + // properties for transform + mlt_properties_set(properties, "smoothing", "10"); + mlt_properties_set(properties, "maxshift", "-1"); + mlt_properties_set(properties, "maxangle", "-1"); + mlt_properties_set(properties, "crop", "0"); + mlt_properties_set(properties, "invert", "0"); + mlt_properties_set(properties, "relative", "1"); + mlt_properties_set(properties, "zoom", "0"); + mlt_properties_set(properties, "optzoom", "1"); + mlt_properties_set(properties, "sharpen", "0.8"); + + mlt_properties_set(properties, "vid.stab.version", LIBVIDSTAB_VERSION); + mlt_properties_set_data(properties, "vectors", data->animation, 1, (mlt_destructor) mlt_animation_close, + (mlt_serialiser) vectors_serializer); + + return filter; + } + + delete data; + return NULL; +} + +} diff --git a/src/modules/vid.stab/filter_detect.yml b/src/modules/vid.stab/filter_detect.yml new file mode 100644 index 00000000..11865ef7 --- /dev/null +++ b/src/modules/vid.stab/filter_detect.yml @@ -0,0 +1,218 @@ +schema_version: 0.1 +type: filter +identifier: vid.stab.detect +title: Vid.Stab Detect +copyright: Jakub Ksiezniak +creator: Marco Gittler +version: 1 +license: GPL +language: en +url: http://public.hronopik.de/vid.stab/ +tags: + - Video +description: Stabilize Video (for wiggly/rolling video) +notes: > + This filter requires two passes. The first pass performs analysis and stores + the result in the vectors property. The second pass applies the vectors to + the image. + To use with melt, use 'melt ... -consumer xml:output.mlt all=1' for the + first pass. For the second pass, use output.mlt as the input. + +parameters: + - identifier: vectors (transform) + title: Vectors + type: geometry + description: > + A set of X/Y coordinates by which to adjust the image. + When this is not supplied, the filter computes the vectors and stores + them in this property when the last frame has been processed. + + - identifier: shakiness + title: Shakiness + type: integer + description: How shaky is the video (analysis) + readonly: no + required: no + minimum: 1 + maximum: 10 + default: 4 + mutable: yes + widget: spinner + + - identifier: accuracy + title: Accuracy + type: integer + description: Accuracy of shakiness detection (analysis) + readonly: no + required: no + minimum: 1 + maximum: 15 + default: 4 + mutable: yes + widget: spinner + + - identifier: stepsize + title: Stepsize + type: integer + description: Step size of search process (analysis) + readonly: no + required: no + minimum: 0 + maximum: 100 + default: 6 + mutable: yes + widget: spinner + + - identifier: algo + title: Algorithm + type: integer + description: 0 = brute force (translation only), 1 = small measurement fields (analysis) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 1 + mutable: yes + widget: spinner + + - identifier: mincontrast + title: Minimum Contrast + type: float + description: Below this contrast, a field is discarded (analysis) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 0.3 + mutable: yes + widget: spinner + + - identifier: show + title: Show + type: integer + description: 0 = draw nothing, 1 or 2 = show fields and transforms (analysis) + readonly: no + required: no + minimum: 0 + maximum: 2 + default: 0 + mutable: yes + widget: spinner + + - identifier: tripod + title: Tripod + type: integer + description: virtual tripod mode (if >0): motion is compared to a reference frame (frame N is the value) + readonly: no + required: no + minimum: 0 + maximum: 100000 + default: 0 + mutable: yes + widget: spinner + + - identifier: smoothing + title: Smoothing + type: integer + description: number of frames for lowpass filtering (2N + 1 frames) (transform) + readonly: no + required: no + minimum: 0 + maximum: 100 + default: 10 + mutable: yes + widget: spinner + + - identifier: maxshift + title: Maxshift + type: integer + description: maximum translation, -1 = no limit (transform) + unit: pixels + readonly: no + required: no + minimum: -1 + maximum: 1000 + default: -1 + mutable: yes + widget: spinner + + - identifier: maxangle + title: Maxangle + type: float + description: max angle to rotate, -1 = no limit (transform) + unit: radians + readonly: no + required: no + minimum: -1 + maximum: 3.142 + default: -1 + mutable: yes + widget: spinner + + - identifier: crop + title: Crop + type: integer + description: 0 = keep border, 1 = black background (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 0 + mutable: yes + widget: spinner + + - identifier: invert + title: Invert + type: integer + description: Invert transforms (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 0 + mutable: yes + widget: spinner + + - identifier: relative + title: Relative Transform + type: integer + description: 0 = absolute, 1 = relative (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 1 + mutable: yes + widget: spinner + + - identifier: zoom + title: Zoom + type: integer + description: additional zoom amount (transform) + unit: percent + readonly: no + required: no + minimum: -500 + maximum: 500 + default: 0 + mutable: yes + widget: spinner + + - identifier: optzoom + title: Optimal Zoom + type: integer + description: automatically determine optimal zoom (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 1 + mutable: yes + widget: spinner + + - identifier: refresh + description: > + Applications should set this when it updates a transform parameter. + type: integer + minimum: 0 + maximum: 1 diff --git a/src/modules/vid.stab/filter_transform.cpp b/src/modules/vid.stab/filter_transform.cpp new file mode 100644 index 00000000..3b233724 --- /dev/null +++ b/src/modules/vid.stab/filter_transform.cpp @@ -0,0 +1,258 @@ +/* + * filter_transform.cpp + * Copyright (C) 2013 + * Marco Gittler + * Jakub Ksiezniak + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +extern "C" +{ +#include +#include +#include +} + +#include +#include +#include +#include "common.h" + +#define FILTER_NAME "vid.stab.transform" + +typedef struct +{ + bool initialized; + VSTransformData td; + VSTransformations trans; + + void *parent; +} TransformData; + +int lm_deserialize(LocalMotions *lms, mlt_property property) +{ + std::istringstream iss(mlt_property_get_string(property)); + vs_vector_init(lms, 0); + + while (iss.good()) + { + LocalMotion lm; + iss >> lm.v.x >> lm.v.y >> lm.f.x >> lm.f.y >> lm.f.size >> lm.contrast >> lm.match; + if (iss.fail()) + { + break; + } + vs_vector_append_dup(lms, &lm, sizeof(lm)); + } + return 0; +} + +int vectors_deserialize(mlt_animation anim, VSManyLocalMotions *mlms) +{ + int error = 0; + mlt_animation_item_s item; + item.property = mlt_property_init(); + + vs_vector_init(mlms, 1024); // initial number of frames, but it will be increased + + int length = mlt_animation_get_length(anim); + for (int i = 0; i < length; ++i) + { + LocalMotions lms; + + // read lms + mlt_animation_get_item(anim, &item, i + 1); + if ((error = lm_deserialize(&lms, item.property))) + { + break; + } + + vs_vector_set_dup(mlms, i, &lms, sizeof(LocalMotions)); + } + + mlt_property_close(item.property); + return error; +} + +inline int initialize_transforms(TransformData *data, int *width, int *height, mlt_image_format *format, + mlt_properties properties, char* interps) +{ + VSPixelFormat pf = convertImageFormat(*format); + VSFrameInfo fi_src, fi_dst; + vsFrameInfoInit(&fi_src, *width, *height, pf); + vsFrameInfoInit(&fi_dst, *width, *height, pf); + + VSTransformConfig conf = vsTransformGetDefaultConfig(FILTER_NAME); + conf.smoothing = mlt_properties_get_int(properties, "smoothing"); + conf.maxShift = mlt_properties_get_int(properties, "maxshift"); + conf.maxAngle = mlt_properties_get_double(properties, "maxangle"); + conf.crop = (VSBorderType) mlt_properties_get_int(properties, "crop"); + conf.zoom = mlt_properties_get_int(properties, "zoom"); + conf.optZoom = mlt_properties_get_int(properties, "optzoom"); + conf.relative = mlt_properties_get_int(properties, "relative"); + conf.invert = mlt_properties_get_int(properties, "invert"); + if (mlt_properties_get_int(properties, "tripod") != 0) + { + // Virtual tripod mode: relative=False, smoothing=0 + conf.relative = 0; + conf.smoothing = 0; + } + + // by default a bilinear interpolation is selected + conf.interpolType = VS_BiLinear; + if (strcmp(interps, "nearest") == 0 || strcmp(interps, "neighbor") == 0) + conf.interpolType = VS_Zero; + else if (strcmp(interps, "tiles") == 0 || strcmp(interps, "fast_bilinear") == 0) + conf.interpolType = VS_Linear; + + vsTransformDataInit(&data->td, &conf, &fi_src, &fi_dst); + + vsTransformationsInit(&data->trans); + + // load transformations + mlt_animation animation = mlt_animation_new(); + char* strAnim = mlt_properties_get(properties, "vectors"); + if (mlt_animation_parse(animation, strAnim, 0, 0, NULL)) + { + mlt_log_warning(NULL, "parse failed\n"); + return 1; + } + + VSManyLocalMotions mlms; + if (vectors_deserialize(animation, &mlms)) + { + return 1; + } + + mlt_animation_close(animation); + + vsLocalmotions2Transforms(&data->td, &mlms, &data->trans); + vsPreprocessTransforms(&data->td, &data->trans); + return 0; +} + +void clear_transforms(TransformData *data) +{ + if (data->initialized) + { + vsTransformDataCleanup(&data->td); + vsTransformationsCleanup(&data->trans); + } +} + +static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) +{ + int error = 0; + mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); + mlt_properties properties = MLT_FILTER_PROPERTIES(filter); + + *format = mlt_image_yuv420p; + TransformData *data = static_cast(filter->child); + + error = mlt_frame_get_image(frame, image, format, width, height, 1); + if (!error) + { + // Service locks are for concurrency control + mlt_service_lock(MLT_FILTER_SERVICE(filter)); + + // Handle signal from app to re-init data + if (mlt_properties_get_int(properties, "refresh")) + { + mlt_properties_set(properties, "refresh", NULL); + clear_transforms(data); + data->initialized = false; + } + + if (!data->initialized) + { + char *interps = mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "rescale.interp"); + initialize_transforms(data, width, height, format, properties, interps); + data->initialized = true; + } + + VSTransformData* td = &data->td; + VSFrame vsFrame; + vsFrameFillFromBuffer(&vsFrame, *image, vsTransformGetSrcFrameInfo(td)); + + // transform frame + data->trans.current = mlt_filter_get_position(filter, frame); + vsTransformPrepare(td, &vsFrame, &vsFrame); + VSTransform t = vsGetNextTransform(td, &data->trans); + vsDoTransform(td, t); + vsTransformFinish(td); + + mlt_service_unlock(MLT_FILTER_SERVICE(filter)); + } + + return error; +} + +static mlt_frame process_filter(mlt_filter filter, mlt_frame frame) +{ + mlt_frame_push_service(frame, filter); + mlt_frame_push_get_image(frame, get_image); + return frame; +} + +static void close_filter(mlt_filter filter) +{ + TransformData *data = static_cast(filter->child); + if (data) + { + clear_transforms(data); + delete data; + filter->child = NULL; + } +} + +extern "C" +{ + +mlt_filter filter_transform_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) +{ + mlt_filter filter = NULL; + + TransformData *data = new TransformData; + memset(data, 0, sizeof(TransformData)); + + if ((filter = mlt_filter_new())) + { + filter->process = process_filter; + filter->close = close_filter; + filter->child = data; + + data->parent = filter; + + mlt_properties properties = MLT_FILTER_PROPERTIES(filter); + + // properties for transform + mlt_properties_set(properties, "smoothing", "10"); + mlt_properties_set(properties, "maxshift", "-1"); + mlt_properties_set(properties, "maxangle", "-1"); + mlt_properties_set(properties, "crop", "0"); + mlt_properties_set(properties, "invert", "0"); + mlt_properties_set(properties, "relative", "1"); + mlt_properties_set(properties, "zoom", "0"); + mlt_properties_set(properties, "optzoom", "1"); + + return filter; + } + + delete data; + return NULL; +} + +} diff --git a/src/modules/vid.stab/filter_transform.yml b/src/modules/vid.stab/filter_transform.yml new file mode 100644 index 00000000..e8c475aa --- /dev/null +++ b/src/modules/vid.stab/filter_transform.yml @@ -0,0 +1,206 @@ +schema_version: 0.1 +type: filter +identifier: videostab2 +title: Videostab2 +copyright: Copyright (C) 2011 Marco Gittler +creator: Marco Gittler +version: 0.1 +license: GPL +language: en +url: http://public.hronopik.de/vid.stab/ +tags: + - Video +description: Stabilize Video (for wiggly/rolling video) +notes: > + This filter requires two passes. The first pass performs analysis and stores + the result in the vectors property. The second pass applies the vectors to + the image. + To use with melt, use 'melt ... -consumer xml:output.mlt all=1' for the + first pass. For the second pass, use output.mlt as the input. + +parameters: + - identifier: vectors (transform) + title: Vectors + type: geometry + description: > + A set of X/Y coordinates by which to adjust the image. + When this is not supplied, the filter computes the vectors and stores + them in this property when the last frame has been processed. + + - identifier: shakiness + title: Shakiness + type: integer + description: How shaky is the video (analysis) + readonly: no + required: no + minimum: 1 + maximum: 10 + default: 4 + mutable: yes + widget: spinner + + - identifier: accuracy + title: Accuracy + type: integer + description: Accuracy of shakiness detection (analysis) + readonly: no + required: no + minimum: 1 + maximum: 15 + default: 4 + mutable: yes + widget: spinner + + - identifier: stepsize + title: Stepsize + type: integer + description: Step size of search process (analysis) + readonly: no + required: no + minimum: 0 + maximum: 100 + default: 6 + mutable: yes + widget: spinner + + - identifier: algo + title: Algorithm + type: integer + description: 0 = brute force (translation only), 1 = small measurement fields (analysis) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 1 + mutable: yes + widget: spinner + + - identifier: mincontrast + title: Minimum Contrast + type: float + description: Below this contrast, a field is discarded (analysis) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 0.3 + mutable: yes + widget: spinner + + - identifier: show + title: Show + type: integer + description: 0 = draw nothing, 1 or 2 = show fields and transforms (analysis) + readonly: no + required: no + minimum: 0 + maximum: 2 + default: 0 + mutable: yes + widget: spinner + + - identifier: smoothing + title: Smoothing + type: integer + description: number of frames for lowpass filtering (2N + 1 frames) (transform) + readonly: no + required: no + minimum: 0 + maximum: 100 + default: 10 + mutable: yes + widget: spinner + + - identifier: maxshift + title: Maxshift + type: integer + description: maximum translation, -1 = no limit (transform) + unit: pixels + readonly: no + required: no + minimum: -1 + maximum: 1000 + default: -1 + mutable: yes + widget: spinner + + - identifier: maxangle + title: Maxangle + type: float + description: max angle to rotate, -1 = no limit (transform) + unit: radians + readonly: no + required: no + minimum: -1 + maximum: 3.142 + default: -1 + mutable: yes + widget: spinner + + - identifier: crop + title: Crop + type: integer + description: 0 = keep border, 1 = black background (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 0 + mutable: yes + widget: spinner + + - identifier: invert + title: Invert + type: integer + description: Invert transforms (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 0 + mutable: yes + widget: spinner + + - identifier: relative + title: Relative Transform + type: integer + description: 0 = absolute, 1 = relative (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 1 + mutable: yes + widget: spinner + + - identifier: zoom + title: Zoom + type: integer + description: additional zoom amount (transform) + unit: percent + readonly: no + required: no + minimum: -500 + maximum: 500 + default: 0 + mutable: yes + widget: spinner + + - identifier: optzoom + title: Optimal Zoom + type: integer + description: automatically determine optimal zoom (transform) + readonly: no + required: no + minimum: 0 + maximum: 1 + default: 1 + mutable: yes + widget: spinner + + - identifier: refresh + description: > + Applications should set this when it updates a transform parameter. + type: integer + minimum: 0 + maximum: 1 -- 2.39.2