[FFmpeg-devel] [PATCH] video stabilization plugins using vid.stab library

Clément Bœsch ubitux at gmail.com
Mon Mar 18 02:03:51 CET 2013


On Sun, Mar 17, 2013 at 11:59:17PM +0100, Georg Martius wrote:
> Hi,
> 
> here is a patch for adding the filters for video stabilization. They need the 
> vid.stab library installed [1]. I decided to develop the library outside of 
> ffmpeg and only have the thin interfacing plugins in libavfilter.
> I didn't adapt the configure scripts because I am not familiar with them:
> "-lvidstab" is required for linking. 
> 
> [1] https://github.com/georgmartius/vid.stab
> 
> Best regards,
>   Georg

> From e295d7d8b123484a2970b10ebee92faa99d870b6 Mon Sep 17 00:00:00 2001
> From: Georg Martius <martius at mis.mpg.de>
> Date: Sun, 17 Mar 2013 23:36:38 +0100
> Subject: [PATCH] video stabilization plugins using vid.stab library
> 
> ---
>  libavfilter/Makefile       |    4 +-
>  libavfilter/allfilters.c   |    4 +
>  libavfilter/vf_stabilize.c |  367 ++++++++++++++++++++++++++++++++++++++
>  libavfilter/vf_transform.c |  423 ++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 797 insertions(+), 1 deletion(-)
>  create mode 100644 libavfilter/vf_stabilize.c
>  create mode 100644 libavfilter/vf_transform.c
> 
[...]
> +static int config_input(AVFilterLink *inlink)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    StabData *sd = ctx->priv;
> +//    char* filenamecopy, *filebasename;
> +
> +    MotionDetect* md = &(sd->md);
> +    VSFrameInfo fi;
> +    const AVPixFmtDescriptor *desc = &av_pix_fmt_descriptors[inlink->format];
> +
> +    _stab_ctx = ctx; // save context for logging
> +
> +    initFrameInfo(&fi,inlink->w, inlink->h, AV2OurPixelFormat(ctx, inlink->format));
> +    // check
> +    if(fi.bytesPerPixel != av_get_bits_per_pixel(desc)/8)
> +        av_log(ctx, AV_LOG_ERROR, "pixel-format error: wrong bits/per/pixel");
> +    if(fi.log2ChromaW != desc->log2_chroma_w)
> +        av_log(ctx, AV_LOG_ERROR, "pixel-format error: log2_chroma_w");
> +    if(fi.log2ChromaH != desc->log2_chroma_h)
> +        av_log(ctx, AV_LOG_ERROR, "pixel-format error: log2_chroma_h");
> +
> +    if(initMotionDetect(md, &fi, "stabilize") != VS_OK){
> +        av_log(ctx, AV_LOG_ERROR, "initialization of Motion Detection failed");
> +        return AVERROR(EINVAL);
> +    }
> +

> +    sd->result = av_malloc(VS_INPUT_MAXLEN);
> +    snprintf(sd->result, VS_INPUT_MAXLEN, DEFAULT_TRANS_FILE_NAME);
> +

av_strdup() is your friend

Also make sure you free such strings.

> +    {
> +        int ret;
> +        if ((ret = (av_set_options_string(sd, sd->args, "=", ":"))) < 0)
> +            return ret;
> +    }
> +
> +    // display help
> +    /* if(optstr_lookup(sd->options, "help")) { */
> +    /*     av_log(ctx, AV_LOG_INFO, motiondetect_help); */
> +    /*     return AVERROR(EINVAL); */
> +    /* } */
> +
> +    if(configureMotionDetect(md)!= VS_OK){
> +    	av_log(ctx, AV_LOG_ERROR, "configuration of Motion Detection failed\n");

That hidden tab is going to cause trouble when we'll try to push it; make
sure not to forget it when you'll remove the tab in the other places where
Michael point them out.

> +        return AVERROR(EINVAL);
> +    }
> +
> +    av_log(ctx, AV_LOG_INFO, "Image Stabilization Settings:\n");
> +    av_log(ctx, AV_LOG_INFO, "     shakiness = %d\n", md->shakiness);
> +    av_log(ctx, AV_LOG_INFO, "      accuracy = %d\n", md->accuracy);
> +    av_log(ctx, AV_LOG_INFO, "      stepsize = %d\n", md->stepSize);
> +    av_log(ctx, AV_LOG_INFO, "          algo = %d\n", md->algo);
> +    av_log(ctx, AV_LOG_INFO, "   mincontrast = %f\n", md->contrastThreshold);
> +    av_log(ctx, AV_LOG_INFO, "          show = %d\n", md->show);
> +    av_log(ctx, AV_LOG_INFO, "        result = %s\n", sd->result);
> +
> +    sd->f = fopen(sd->result, "w");
> +    if (sd->f == NULL) {
> +        av_log(ctx, AV_LOG_ERROR, "cannot open transform file %s!\n", sd->result);
> +        return AVERROR(EINVAL);
> +    }else{
> +        if(prepareFile(md, sd->f) != VS_OK){
> +            av_log(ctx, AV_LOG_ERROR, "cannot write to transform file %s!\n", sd->result);
> +            return AVERROR(EINVAL);
> +        }
> +    }
> +    return 0;
> +}
> +
> +
> +static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *in)
> +{

We moved to AVFrame since a while now; you'll need to update your code
against a recent version.

[...]
> +AVFilter avfilter_vf_stabilize = {
> +    .name      = "stabilize",
> +    .description = NULL_IF_CONFIG_SMALL("extracts relative transformations of \n\
> +    subsequent frames (used for stabilization together with the\n\
> +    transform filter in a second pass)."),
> +
> +    .priv_size = sizeof(StabData),
> +
> +    .init   = init,
> +    .uninit = uninit,
> +    .query_formats = query_formats,
> +
> +    .inputs    = (const AVFilterPad[]) {{ .name       = "default",
> +                                    .type             = AVMEDIA_TYPE_VIDEO,
> +                                    .get_video_buffer = ff_null_get_video_buffer,
> +                                    .filter_frame     = filter_frame,
> +                                    .config_props     = config_input,
> +                                    .min_perms        = AV_PERM_READ, },
> +                                  { .name = NULL}},
> +    .outputs   = (const AVFilterPad[]) {{ .name       = "default",
> +                                    .type             = AVMEDIA_TYPE_VIDEO, },
> +                                  { .name = NULL}},

Please move these declarations out of here like done in the other filters.

> +};
> +
> +
> +

> +/*************************************************************************/
> +
> +/*
> + * Local variables:
> + *   c-file-style: "stroustrup"
> + *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
> + *   indent-tabs-mode: nil
> + * End:
> + *
> + * vim: expandtab shiftwidth=4:
> + */

You can remove this, there is no reason to make an exception in one of the
file of the code base.

> diff --git a/libavfilter/vf_transform.c b/libavfilter/vf_transform.c
> new file mode 100644
> index 0000000..c409d7f
> --- /dev/null
> +++ b/libavfilter/vf_transform.c
> @@ -0,0 +1,423 @@
> +/*
> + *  vf_transform.c
> + *
> + *  Copyright (C) Georg Martius - Jan 2012
> + *   georg dot martius at web dot de
> + *
> + *  This file is part of vid.stab, video deshaking lib
> + *
> + *  vid.stab 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, or (at your option)
> + *  any later version.
> + *
> + *  vid.stab 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 GNU Make; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + *
> + */
> +
> +/* Typical call:
> + *  ffmpeg -i inp.mpeg -vf transform,unsharp=5:5:0.8:3:3:0.4 inp_s.mpeg
> + *  all parameters are optional
> + */
> +
> +#define DEFAULT_TRANS_FILE_NAME     "transforms.trf"
> +#define VS_INPUT_MAXLEN 1024
> +
> +#include "libavutil/common.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/imgutils.h"
> +// #include "libavcodec/dsputil.h"
> +#include "avfilter.h"
> +#include "internal.h"
> +
> +#include <vid.stab/libvidstab.h>
> +
> +/* private date structure of this filter*/
> +typedef struct _filter_data {
> +    AVClass* class;
> +
> +    TransformData td;
> +
> +    Transformations trans; // transformations
> +    char* args;
> +    char* input;           // name of transform file
> +    int tripod;
> +} FilterData;
> +
> +/*** some conversions from avlib to vid.stab constants and functions ****/
> +
> +/** convert AV's pixelformat to vid.stab pixelformat */
> +static PixelFormat AV2OurPixelFormat(AVFilterContext *ctx, enum AVPixelFormat pf){
> +	switch(pf){
> +    case AV_PIX_FMT_YUV420P: return PF_YUV420P;
> +		case AV_PIX_FMT_RGB24:		return PF_RGB24;
> +		case AV_PIX_FMT_BGR24:		return PF_BGR24;
> +		case AV_PIX_FMT_YUV422P:	return PF_YUV422P;
> +		case AV_PIX_FMT_YUV444P:	return PF_YUV444P;
> +		case AV_PIX_FMT_YUV410P:	return PF_YUV410P;
> +		case AV_PIX_FMT_YUV411P:	return PF_YUV411P;
> +		case AV_PIX_FMT_GRAY8:		return PF_GRAY8;
> +		case AV_PIX_FMT_YUVA420P:return PF_YUVA420P;
> +		case AV_PIX_FMT_RGBA:		return PF_RGBA;
> +	default:
> +		av_log(ctx, AV_LOG_ERROR, "cannot deal with pixel format %i!\n", pf);
> +		return PF_NONE;
> +	}
> +}
> +
> +/// pointer to context for logging
> +void *_trf_ctx = 0;

No way to avoid such global?

> +/** wrapper to log vs_log into av_log */
> +static int av_log_wrapper(int type, const char* tag, const char* format, ...){
> +    va_list ap;
> +    av_log(_trf_ctx, type, "%s: ", tag);
> +    va_start (ap, format);
> +    av_vlog(_trf_ctx, type, format, ap);
> +    va_end (ap);
> +    return VS_OK;
> +}
> +
> +/** sets the memory allocation function and logging constants to av versions */
> +static void setMemAndLogFunctions(void){
> +    vs_malloc  = av_malloc;
> +    vs_zalloc  = av_mallocz;
> +    vs_realloc = av_realloc;
> +    vs_free    = av_free;
> +
> +    VS_ERROR_TYPE = AV_LOG_ERROR;
> +    VS_WARN_TYPE  = AV_LOG_WARNING;
> +    VS_INFO_TYPE  = AV_LOG_INFO;
> +    VS_MSG_TYPE   = AV_LOG_VERBOSE;
> +
> +    vs_log   = av_log_wrapper;
> +
> +    VS_ERROR = 0;
> +    VS_OK    = 1;
> +}
> +
> +/*** Commandline options ****/
> +
> +#define OFFSET(x) offsetof(FilterData, x)
> +#define OFFSETTD(x) (offsetof(FilterData, td)+offsetof(TransformData, x))
> +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
> +
> +static const AVOption transform_options[]= {
> +    {"input",     "path to the file storing the transforms (def:transforms.trf)",
> +     OFFSET(input),    AV_OPT_TYPE_STRING },
> +    {"smoothing", "number of frames*2 + 1 used for lowpass filtering \n"
> +     "                used for stabilizing (def: 10)",
> +     OFFSETTD(smoothing),    AV_OPT_TYPE_INT, {.i64 = 10}, 1, 1000, FLAGS},
> +    {"maxshift",    "maximal number of pixels to translate image\n"
> +     "                (def: -1 no limit)",
> +     OFFSETTD(maxShift),     AV_OPT_TYPE_INT, {.i64 = -1}, -1, 500, FLAGS},
> +    {"maxangle", "maximal angle in rad to rotate image (def: -1 no limit)",
> +     OFFSETTD(maxAngle),  AV_OPT_TYPE_DOUBLE, {.dbl = -1.0}, -1.0, 3.14, FLAGS},
> +    {"crop",    "keep: (def), black",
> +     OFFSETTD(crop),         AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS, "crop"},
> +    { "keep",          "Keep bolder",
> +      0, AV_OPT_TYPE_CONST,  {.i64 = KeepBorder }, 0, 0, FLAGS, "crop" },
> +    { "black",         "Black border",
> +      0, AV_OPT_TYPE_CONST,  {.i64 = CropBorder }, 0, 0, FLAGS, "crop"  },
> +    {"invert",    " 1: invert transforms(def: 0)",
> +     OFFSETTD(invert),       AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, FLAGS},
> +    {"relative",    "consider transforms as 0: absolute, 1: relative (def)",
> +     OFFSETTD(relative),     AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS},
> +    {"zoom", "percentage to zoom >0: zoom in, <0 zoom out (def: 0)",
> +     OFFSETTD(zoom),  AV_OPT_TYPE_DOUBLE, {.dbl = 0}, 0, 100, FLAGS},
> +    {"optzoom",    "0: nothing, 1: determine optimal zoom (def)\n"
> +     "                i.e. no (or only little) border should be visible.\n"
> +     "                Note that the value given at 'zoom' is added to the \n"
> +     "                here calculated one",
> +     OFFSETTD(optZoom),     AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, FLAGS},
> +    {"interpol",    "type of interpolation, no, linear, bilinear (def) , bicubic",
> +     OFFSETTD(interpolType),     AV_OPT_TYPE_INT, {.i64 = 2}, 0, 3, FLAGS, "interpol"},
> +    { "no",          "no interpolation",
> +      0, AV_OPT_TYPE_CONST,  {.i64 = Zero  },    0, 0, FLAGS, "interpol" },
> +    { "linear",       "linear (horizontal)",
> +      0, AV_OPT_TYPE_CONST,  {.i64 = Linear },   0, 0, FLAGS, "interpol"  },
> +    { "bilinear",     "bi-linear",
> +      0, AV_OPT_TYPE_CONST,  {.i64 = BiLinear },   0, 0, FLAGS, "interpol"  },
> +    { "bicubic",       "bi-cubic",
> +      0, AV_OPT_TYPE_CONST,  {.i64 = BiCubic },   0, 0, FLAGS, "interpol"  },
> +    {"tripod",         "if 1: virtual tripod mode (equivalent to relative=0:smoothing=0)",
> +     OFFSET(tripod),     AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS},
> +    {NULL},
> +};
> +
> +AVFILTER_DEFINE_CLASS(transform);
> +
> +
> +/*************************************************************************/
> +
> +/* Module interface routines and data. */
> +
> +/*************************************************************************/
> +
> +static av_cold int init(AVFilterContext *ctx, const char *args)
> +{
> +
> +    FilterData* fd = ctx->priv;
> +    _trf_ctx = ctx;
> +
> +    setMemAndLogFunctions();
> +
> +    if (!fd) {
> +        av_log(ctx, AV_LOG_INFO, "init: out of memory!\n");
> +        return AVERROR(EINVAL);
> +    }

If you are out of memory, the correct error code is AVERROR(ENOMEM), but
this doesn't seem to be the case here. You can safely remove completely
that check.

> +    fd->class = (AVClass*)&transform_class;

Please put the class const and avoid the cast.

> +    av_opt_set_defaults(fd); // the default values are overwritten by initMotiondetect later
> +
> +    av_log(ctx, AV_LOG_INFO, "transform filter: init %s\n", LIBVIDSTAB_VERSION);
> +

AV_LOG_VERBOSE

> +    // save args for later
> +    if(args)
> +        fd->args=av_strdup(args);
> +    else
> +        fd->args=0;
> +

This looks a bit suspicious; is it necessary?

Also, setting to 0 has no point at this point; everything in the context is zeroed.

> +    return 0;
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    FilterData *fd = ctx->priv;
> +    _trf_ctx = ctx;
> +
> +    //  avfilter_unref_buffer(fd->ref);
> +
> +    cleanupTransformData(&fd->td);
> +    cleanupTransformations(&fd->trans);
> +
> +    if(fd->args) av_free(fd->args);

No need to check for pointer before free.

> +}
> +
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    static const enum AVPixelFormat pix_fmts[] = {
> +        AV_PIX_FMT_YUV444P,  AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P,
> +        AV_PIX_FMT_YUV411P,  AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUVA420P,
> +        AV_PIX_FMT_YUV440P,  AV_PIX_FMT_GRAY8,
> +        AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, AV_PIX_FMT_RGBA,
> +        AV_PIX_FMT_NONE
> +    };
> +
> +    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
> +    return 0;
> +}
> +
> +
> +static int config_input(AVFilterLink *inlink)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    FilterData *fd = ctx->priv;
> +    FILE* f;
> +//    char* filenamecopy, *filebasename;
> +
> +    const AVPixFmtDescriptor *desc = &av_pix_fmt_descriptors[inlink->format];
> +

Not using the function for this will cause compilation problems in some
configurations.

[...]

I'll have some more comments later.

-- 
Clément B.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 490 bytes
Desc: not available
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20130318/6ee1b797/attachment.asc>


More information about the ffmpeg-devel mailing list