From 80c644593223ac74bba915c698926ffcc8a90d68 Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Fri, 12 Jul 2013 17:50:22 +0000 Subject: [PATCH] lavfi: port perspective filter from libmpcodecs Signed-off-by: Paul B Mahol --- Changelog | 1 + LICENSE | 1 + configure | 1 + doc/filters.texi | 38 ++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/version.h | 4 +- libavfilter/vf_perspective.c | 407 +++++++++++++++++++++++++++++++++++ 8 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 libavfilter/vf_perspective.c diff --git a/Changelog b/Changelog index ecbb39d84a..48149a39b1 100644 --- a/Changelog +++ b/Changelog @@ -4,6 +4,7 @@ releases are sorted from youngest to oldest. version - aecho filter +- perspective filter ported from libmpcodecs version 2.0: diff --git a/LICENSE b/LICENSE index 99f6478c7d..575824e19c 100644 --- a/LICENSE +++ b/LICENSE @@ -39,6 +39,7 @@ Specifically, the GPL parts of FFmpeg are - vf_mp.c - vf_noise.c - vf_owdenoise.c + - vf_perspective.c - vf_pp.c - vf_sab.c - vf_smartblur.c diff --git a/configure b/configure index 1ec803285a..ab3ac2704f 100755 --- a/configure +++ b/configure @@ -2175,6 +2175,7 @@ mpdecimate_filter_deps="gpl avcodec" mptestsrc_filter_deps="gpl" negate_filter_deps="lut_filter" noise_filter_deps="gpl" +perspective_filter_deps="gpl" resample_filter_deps="avresample" ocv_filter_deps="libopencv" owdenoise_filter_deps="gpl" diff --git a/doc/filters.texi b/doc/filters.texi index 6f3994a204..7d4eeea26e 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -5722,6 +5722,44 @@ pad="2*iw:2*ih:ow-iw:oh-ih" @end example @end itemize +@section perspective + +Correct perspective of video not recorded perpendicular to the screen. + +A description of the accepted parameters follows. + +@table @option +@item x0 +@item y0 +@item x1 +@item y1 +@item x2 +@item y2 +@item x3 +@item y3 +Set coordinates expression for top left, top right, bottom left and bottom right corners. +Default values are @code{0:0:W:0:0:H:W:H} with which perspective will remain unchanged. + +The expressions can use the following variables: + +@table @option +@item W +@item H +the width and height of video frame. +@end table + +@item interpolation +Set interpolation for perspective correction. + +It accepts the following values: +@table @samp +@item linear +@item cubic +@end table + +Default value is @samp{linear}. +@end table + @section pixdesctest Pixel format descriptor test filter, mainly useful for internal diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 306b24cb65..7f10409f0e 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -166,6 +166,7 @@ OBJS-$(CONFIG_OVERLAY_FILTER) += vf_overlay.o dualinput.o OBJS-$(CONFIG_OWDENOISE_FILTER) += vf_owdenoise.o OBJS-$(CONFIG_PAD_FILTER) += vf_pad.o OBJS-$(CONFIG_PERMS_FILTER) += f_perms.o +OBJS-$(CONFIG_PERSPECTIVE_FILTER) += vf_perspective.o OBJS-$(CONFIG_PIXDESCTEST_FILTER) += vf_pixdesctest.o OBJS-$(CONFIG_PP_FILTER) += vf_pp.o OBJS-$(CONFIG_PSNR_FILTER) += vf_psnr.o dualinput.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 26472f8dc7..bda6e3cc7f 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -161,6 +161,7 @@ void avfilter_register_all(void) REGISTER_FILTER(OWDENOISE, owdenoise, vf); REGISTER_FILTER(PAD, pad, vf); REGISTER_FILTER(PERMS, perms, vf); + REGISTER_FILTER(PERSPECTIVE, perspective, vf); REGISTER_FILTER(PIXDESCTEST, pixdesctest, vf); REGISTER_FILTER(PP, pp, vf); REGISTER_FILTER(PSNR, psnr, vf); diff --git a/libavfilter/version.h b/libavfilter/version.h index 1a41f92acc..b706fe2c9b 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -30,8 +30,8 @@ #include "libavutil/avutil.h" #define LIBAVFILTER_VERSION_MAJOR 3 -#define LIBAVFILTER_VERSION_MINOR 80 -#define LIBAVFILTER_VERSION_MICRO 101 +#define LIBAVFILTER_VERSION_MINOR 81 +#define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ LIBAVFILTER_VERSION_MINOR, \ diff --git a/libavfilter/vf_perspective.c b/libavfilter/vf_perspective.c new file mode 100644 index 0000000000..35b4ea8c12 --- /dev/null +++ b/libavfilter/vf_perspective.c @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2002 Michael Niedermayer + * Copyright (c) 2013 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/eval.h" +#include "libavutil/imgutils.h" +#include "libavutil/pixdesc.h" +#include "libavutil/opt.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +#define SUB_PIXEL_BITS 8 +#define SUB_PIXELS (1 << SUB_PIXEL_BITS) +#define COEFF_BITS 11 + +#define LINEAR 0 +#define CUBIC 1 + +typedef struct PerspectiveContext { + const AVClass *class; + char *expr_str[4][2]; + double ref[4][2]; + int32_t (*pv)[2]; + int32_t coeff[SUB_PIXELS][4]; + int interpolation; + int linesize[4]; + int height[4]; + int hsub, vsub; + int nb_planes; + + void (*perspective)(struct PerspectiveContext *s, + uint8_t *dst, int dst_linesize, + uint8_t *src, int src_linesize, + int w, int h, int hsub, int vsub); +} PerspectiveContext; + +#define OFFSET(x) offsetof(PerspectiveContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +static const AVOption perspective_options[] = { + { "x0", "set top left x coordinate", OFFSET(expr_str[0][0]), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS }, + { "y0", "set top left y coordinate", OFFSET(expr_str[0][1]), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS }, + { "x1", "set top right x coordinate", OFFSET(expr_str[1][0]), AV_OPT_TYPE_STRING, {.str="W"}, 0, 0, FLAGS }, + { "y1", "set top right y coordinate", OFFSET(expr_str[1][1]), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS }, + { "x2", "set bottom left x coordinate", OFFSET(expr_str[2][0]), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS }, + { "y2", "set bottom left y coordinate", OFFSET(expr_str[2][1]), AV_OPT_TYPE_STRING, {.str="H"}, 0, 0, FLAGS }, + { "x3", "set bottom right x coordinate", OFFSET(expr_str[3][0]), AV_OPT_TYPE_STRING, {.str="W"}, 0, 0, FLAGS }, + { "y3", "set bottom right y coordinate", OFFSET(expr_str[3][1]), AV_OPT_TYPE_STRING, {.str="H"}, 0, 0, FLAGS }, + { "interpolation", "set interpolation", OFFSET(interpolation), AV_OPT_TYPE_INT, {.i64=LINEAR}, 0, 1, FLAGS, "interpolation" }, + { "linear", "", 0, AV_OPT_TYPE_CONST, {.i64=LINEAR}, 0, 0, FLAGS, "interpolation" }, + { "cubic", "", 0, AV_OPT_TYPE_CONST, {.i64=CUBIC}, 0, 0, FLAGS, "interpolation" }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(perspective); + +static int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P, + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ422P,AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P, + AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, + AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE + }; + + ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); + return 0; +} + +static inline double get_coeff(double d) +{ + double coeff, A = -0.60; + + d = fabs(d); + + if (d < 1.0) + coeff = (1.0 - (A + 3.0) * d * d + (A + 2.0) * d * d * d); + else if (d < 2.0) + coeff = (-4.0 * A + 8.0 * A * d - 5.0 * A * d * d + A * d * d * d); + else + coeff = 0.0; + + return coeff; +} + +static const char *const var_names[] = { "W", "H", NULL }; +enum { VAR_W, VAR_H, VAR_VARS_NB }; + +static int config_input(AVFilterLink *inlink) +{ + double x0, x1, x2, x3, x4, x5, x6, x7, q; + AVFilterContext *ctx = inlink->dst; + PerspectiveContext *s = ctx->priv; + double (*ref)[2] = s->ref; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + double values[VAR_VARS_NB] = { [VAR_W] = inlink->w, [VAR_H] = inlink->h }; + int h = inlink->h; + int w = inlink->w; + int x, y, i, j, ret; + + for (i = 0; i < 4; i++) { + for (j = 0; j < 2; j++) { + if (!s->expr_str[i][j]) + return AVERROR(EINVAL); + ret = av_expr_parse_and_eval(&s->ref[i][j], s->expr_str[i][j], + var_names, &values[0], + NULL, NULL, NULL, NULL, + 0, 0, ctx); + if (ret < 0) + return ret; + } + } + + s->hsub = desc->log2_chroma_w; + s->vsub = desc->log2_chroma_h; + s->nb_planes = av_pix_fmt_count_planes(inlink->format); + if ((ret = av_image_fill_linesizes(s->linesize, inlink->format, inlink->w)) < 0) + return ret; + + s->height[1] = s->height[2] = FF_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); + s->height[0] = s->height[3] = inlink->h; + + s->pv = av_realloc_f(s->pv, w * h, 2 * sizeof(*s->pv)); + if (!s->pv) + return AVERROR(ENOMEM); + + x6 = ((ref[0][0] - ref[1][0] - ref[2][0] + ref[3][0]) * + (ref[2][1] - ref[3][1]) - + ( ref[0][1] - ref[1][1] - ref[2][1] + ref[3][1]) * + (ref[2][0] - ref[3][0])) * h; + x7 = ((ref[0][1] - ref[1][1] - ref[2][1] + ref[3][1]) * + (ref[1][0] - ref[3][0]) - + ( ref[0][0] - ref[1][0] - ref[2][0] + ref[3][0]) * + (ref[1][1] - ref[3][1])) * w; + q = ( ref[1][0] - ref[3][0]) * (ref[2][1] - ref[3][1]) - + ( ref[2][0] - ref[3][0]) * (ref[1][1] - ref[3][1]); + + x0 = q * (ref[1][0] - ref[0][0]) * h + x6 * ref[1][0]; + x1 = q * (ref[2][0] - ref[0][0]) * w + x7 * ref[2][0]; + x2 = q * ref[0][0] * w * h; + x3 = q * (ref[1][1] - ref[0][1]) * h + x6 * ref[1][1]; + x4 = q * (ref[2][1] - ref[0][1]) * w + x7 * ref[2][1]; + x5 = q * ref[0][1] * w * h; + + for (y = 0; y < h; y++){ + for (x = 0; x < w; x++){ + int u, v; + + u = (int)floor(SUB_PIXELS * (x0 * x + x1 * y + x2) / + (x6 * x + x7 * y + q * w * h) + 0.5); + v = (int)floor(SUB_PIXELS * (x3 * x + x4 * y + x5) / + (x6 * x + x7 * y + q * w * h) + 0.5); + + s->pv[x + y * w][0] = u; + s->pv[x + y * w][1] = v; + } + } + + for (i = 0; i < SUB_PIXELS; i++){ + double d = i / (double)SUB_PIXELS; + double temp[4]; + double sum = 0; + + for (j = 0; j < 4; j++) + temp[j] = get_coeff(j - d - 1); + + for (j = 0; j < 4; j++) + sum += temp[j]; + + for (j = 0; j < 4; j++) + s->coeff[i][j] = (int)floor((1 << COEFF_BITS) * temp[j] / sum + 0.5); + } + + return 0; +} + +static void resample_cubic(PerspectiveContext *s, + uint8_t *dst, int dst_linesize, + uint8_t *src, int src_linesize, + int w, int h, int hsub, int vsub) +{ + const int linesize = s->linesize[0]; + int x, y; + + for (y = 0; y < h; y++) { + int sy = y << vsub; + for (x = 0; x < w; x++) { + int u, v, subU, subV, sum, sx; + + sx = x << hsub; + u = s->pv[sx + sy * linesize][0] >> hsub; + v = s->pv[sx + sy * linesize][1] >> vsub; + subU = u & (SUB_PIXELS - 1); + subV = v & (SUB_PIXELS - 1); + u >>= SUB_PIXEL_BITS; + v >>= SUB_PIXEL_BITS; + + if (u > 0 && v > 0 && u < w - 2 && v < h - 2){ + const int index = u + v*src_linesize; + const int a = s->coeff[subU][0]; + const int b = s->coeff[subU][1]; + const int c = s->coeff[subU][2]; + const int d = s->coeff[subU][3]; + + sum = s->coeff[subV][0] * (a * src[index - 1 - src_linesize] + b * src[index - 0 - src_linesize] + + c * src[index + 1 - src_linesize] + d * src[index + 2 - src_linesize]) + + s->coeff[subV][1] * (a * src[index - 1 ] + b * src[index - 0 ] + + c * src[index + 1 ] + d * src[index + 2 ]) + + s->coeff[subV][2] * (a * src[index - 1 + src_linesize] + b * src[index - 0 + src_linesize] + + c * src[index + 1 + src_linesize] + d * src[index + 2 + src_linesize]) + + s->coeff[subV][3] * (a * src[index - 1 + 2 * src_linesize] + b * src[index - 0 + 2 * src_linesize] + + c * src[index + 1 + 2 * src_linesize] + d * src[index + 2 + 2 * src_linesize]); + } else { + int dx, dy; + + sum = 0; + + for (dy = 0; dy < 4; dy++) { + int iy = v + dy - 1; + + if (iy < 0) + iy = 0; + else if (iy >= h) + iy = h-1; + for (dx = 0; dx < 4; dx++) { + int ix = u + dx - 1; + + if (ix < 0) + ix = 0; + else if (ix >= w) + ix = w - 1; + + sum += s->coeff[subU][dx] * s->coeff[subV][dy] * src[ ix + iy * src_linesize]; + } + } + } + + sum = (sum + (1<<(COEFF_BITS * 2 - 1))) >> (COEFF_BITS * 2); + sum = av_clip(sum, 0, 255); + dst[x + y * dst_linesize] = sum; + } + } +} + +static void resample_linear(PerspectiveContext *s, + uint8_t *dst, int dst_linesize, + uint8_t *src, int src_linesize, + int w, int h, int hsub, int vsub) +{ + const int linesize = s->linesize[0]; + int x, y; + + for (y = 0; y < h; y++){ + int sy = y << vsub; + for (x = 0; x < w; x++){ + int u, v, subU, subV, sum, sx, index, subUI, subVI; + + sx = x << hsub; + u = s->pv[sx + sy * linesize][0] >> hsub; + v = s->pv[sx + sy * linesize][1] >> vsub; + subU = u & (SUB_PIXELS - 1); + subV = v & (SUB_PIXELS - 1); + u >>= SUB_PIXEL_BITS; + v >>= SUB_PIXEL_BITS; + + index = u + v * src_linesize; + subUI = SUB_PIXELS - subU; + subVI = SUB_PIXELS - subV; + + if ((unsigned)u < (unsigned)(w - 1)){ + if((unsigned)v < (unsigned)(h - 1)){ + sum = subVI * (subUI * src[index] + subU * src[index + 1]) + + subV * (subUI * src[index + src_linesize] + subU * src[index + src_linesize + 1]); + sum = (sum + (1 << (SUB_PIXEL_BITS * 2 - 1)))>> (SUB_PIXEL_BITS * 2); + } else { + if (v < 0) + v = 0; + else + v = h - 1; + index = u + v * src_linesize; + sum = subUI * src[index] + subU * src[index + 1]; + sum = (sum + (1 << (SUB_PIXEL_BITS - 1))) >> SUB_PIXEL_BITS; + } + } else { + if ((unsigned)v < (unsigned)(h - 1)){ + if (u < 0) + u = 0; + else + u = w - 1; + index = u + v * src_linesize; + sum = subVI * src[index] + subV * src[index + src_linesize]; + sum = (sum + (1 << (SUB_PIXEL_BITS - 1))) >> SUB_PIXEL_BITS; + } else { + if (u < 0) + u = 0; + else + u = w - 1; + if (v < 0) + v = 0; + else + v = h - 1; + index = u + v * src_linesize; + sum = src[index]; + } + } + + sum = av_clip(sum, 0, 255); + dst[x + y * dst_linesize] = sum; + } + } +} + +static av_cold int init(AVFilterContext *ctx) +{ + PerspectiveContext *s = ctx->priv; + + switch (s->interpolation) { + case LINEAR: s->perspective = resample_linear; break; + case CUBIC: s->perspective = resample_cubic; break; + } + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + PerspectiveContext *s = ctx->priv; + AVFrame *out; + int plane; + + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!out) { + av_frame_free(&frame); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, frame); + + for (plane = 0; plane < s->nb_planes; plane++) { + int hsub = plane == 1 || plane == 2 ? s->hsub : 0; + int vsub = plane == 1 || plane == 2 ? s->vsub : 0; + s->perspective(s, out->data[plane], out->linesize[plane], + frame->data[plane], frame->linesize[plane], + s->linesize[plane], s->height[plane], hsub, vsub); + } + + av_frame_free(&frame); + return ff_filter_frame(outlink, out); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + PerspectiveContext *s = ctx->priv; + + av_freep(&s->pv); +} + +static const AVFilterPad perspective_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad perspective_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, + { NULL } +}; + +AVFilter avfilter_vf_perspective = { + .name = "perspective", + .description = NULL_IF_CONFIG_SMALL("Correct the perspective of video."), + .priv_size = sizeof(PerspectiveContext), + .init = init, + .uninit = uninit, + .query_formats = query_formats, + .inputs = perspective_inputs, + .outputs = perspective_outputs, + .priv_class = &perspective_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, +};