diff --git a/Changelog b/Changelog index 4d42a0f681..7c485d27d6 100644 --- a/Changelog +++ b/Changelog @@ -13,9 +13,9 @@ version 5.1: - pixelize video filter - colormap video filter - colorchart video source filter -- blurdetect filter - multiply video filter - PGS subtitle frame merge bitstream filter +- blurdetect filter version 5.0: diff --git a/doc/filters.texi b/doc/filters.texi index c243caee20..0e10946cca 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -8131,6 +8131,35 @@ tblend=all_mode=grainextract @subsection Commands This filter supports same @ref{commands} as options. +@anchor{blockdetect} +@section blockdetect + +Determines blockiness of frames without altering the input frames. + +Based on Remco Muijs and Ihor Kirenko: "A no-reference blocking artifact measure for adaptive video processing." 2005 13th European signal processing conference. + +The filter accepts the following options: + +@table @option +@item period_min +@item period_max +Set minimum and maximum values for determining pixel grids (periods). +Default values are [3,24]. + +@item planes +Set planes to filter. Default is first only. +@end table + +@subsection Examples + +@itemize +@item +Determine blockiness for the first plane and search for periods within [8,32]: +@example +blockdetect=period_min=8:period_max=32:planes=1 +@end example +@end itemize + @anchor{blurdetect} @section blurdetect diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 78ccfa37d3..e0e4d0de2c 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -197,6 +197,7 @@ OBJS-$(CONFIG_BLACKDETECT_FILTER) += vf_blackdetect.o OBJS-$(CONFIG_BLACKFRAME_FILTER) += vf_blackframe.o OBJS-$(CONFIG_BLEND_FILTER) += vf_blend.o framesync.o OBJS-$(CONFIG_BLEND_VULKAN_FILTER) += vf_blend_vulkan.o framesync.o vulkan.o vulkan_filter.o +OBJS-$(CONFIG_BLOCKDETECT_FILTER) += vf_blockdetect.o OBJS-$(CONFIG_BLURDETECT_FILTER) += vf_blurdetect.o edge_common.o OBJS-$(CONFIG_BM3D_FILTER) += vf_bm3d.o framesync.o OBJS-$(CONFIG_BOXBLUR_FILTER) += vf_boxblur.o boxblur.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 1946e0a51e..2409964e53 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -183,6 +183,7 @@ extern const AVFilter ff_vf_blackdetect; extern const AVFilter ff_vf_blackframe; extern const AVFilter ff_vf_blend; extern const AVFilter ff_vf_blend_vulkan; +extern const AVFilter ff_vf_blockdetect; extern const AVFilter ff_vf_blurdetect; extern const AVFilter ff_vf_bm3d; extern const AVFilter ff_vf_boxblur; diff --git a/libavfilter/version.h b/libavfilter/version.h index c7ef9f25d7..df23710223 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -31,7 +31,7 @@ #include "version_major.h" -#define LIBAVFILTER_VERSION_MINOR 38 +#define LIBAVFILTER_VERSION_MINOR 39 #define LIBAVFILTER_VERSION_MICRO 100 diff --git a/libavfilter/vf_blockdetect.c b/libavfilter/vf_blockdetect.c new file mode 100644 index 0000000000..624dcfdeda --- /dev/null +++ b/libavfilter/vf_blockdetect.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2021 Thilo Borgmann + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +/** + * @file + * No-reference blockdetect filter + * + * Implementing: + * Remco Muijs and Ihor Kirenko: "A no-reference blocking artifact measure for adaptive video processing." 2005 13th European signal processing conference. IEEE, 2005. + * http://www.eurasip.org/Proceedings/Eusipco/Eusipco2005/defevent/papers/cr1042.pdf + * + * @author Thilo Borgmann + */ + +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "internal.h" + +typedef struct BLKContext { + const AVClass *class; + + int hsub, vsub; + int nb_planes; + + int period_min; // minimum period to search for + int period_max; // maximum period to search for + int planes; // number of planes to filter + + double block_total; + uint64_t nb_frames; + + float *gradients; +} BLKContext; + +#define OFFSET(x) offsetof(BLKContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM +static const AVOption blockdetect_options[] = { + { "period_min", "Minimum period to search for", OFFSET(period_min), AV_OPT_TYPE_INT, {.i64=3}, 2, 32, FLAGS}, + { "period_max", "Maximum period to search for", OFFSET(period_max), AV_OPT_TYPE_INT, {.i64=24}, 2, 64, FLAGS}, + { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=1}, 0, 15, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(blockdetect); + +static av_cold int blockdetect_init(AVFilterContext *ctx) +{ + return 0; +} + +static int blockdetect_config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + BLKContext *s = ctx->priv; + const int bufsize = inlink->w * inlink->h; + const AVPixFmtDescriptor *pix_desc; + + pix_desc = av_pix_fmt_desc_get(inlink->format); + s->hsub = pix_desc->log2_chroma_w; + s->vsub = pix_desc->log2_chroma_h; + s->nb_planes = av_pix_fmt_count_planes(inlink->format); + + s->gradients = av_calloc(bufsize, sizeof(*s->gradients)); + + if (!s->gradients) + return AVERROR(ENOMEM); + + return 0; +} + +static float calculate_blockiness(BLKContext *s, int w, int h, + float *grad, int grad_linesize, + uint8_t* src, int src_linesize) +{ + float block = 0.0f; + float nonblock = 0.0f; + int block_count = 0; + int nonblock_count = 0; + float ret = 0; + + // Calculate BS in horizontal and vertical directions according to (1)(2)(3). + // Also try to find integer pixel periods (grids) even for scaled images. + // In case of fractional periods, FFMAX of current and neighbor pixels + // can help improve the correlation with MQS. + // Skip linear correction term (4)(5), as it appears only valid for their own test samples. + + // horizontal blockiness (fixed width) + for (int j = 1; j < h; j++) { + for (int i = 3; i < w - 4; i++) { + float temp = 0.0f; + grad[j * grad_linesize + i] = + abs(src[j * src_linesize + i + 0] - src[j * src_linesize + i + 1]); + temp += abs(src[j * src_linesize + i + 1] - src[j * src_linesize + i + 2]); + temp += abs(src[j * src_linesize + i + 2] - src[j * src_linesize + i + 3]); + temp += abs(src[j * src_linesize + i + 3] - src[j * src_linesize + i + 4]); + temp += abs(src[j * src_linesize + i - 0] - src[j * src_linesize + i - 1]); + temp += abs(src[j * src_linesize + i - 1] - src[j * src_linesize + i - 2]); + temp += abs(src[j * src_linesize + i - 2] - src[j * src_linesize + i - 3]); + temp = FFMAX(1, temp); + grad[j * grad_linesize + i] /= temp; + + // use first row to store acculated results + grad[i] += grad[j * grad_linesize + i]; + } + } + + // find horizontal period + for (int period = s->period_min; period < s->period_max + 1; period++) { + float temp; + block = 0; + nonblock = 0; + block_count = 0; + nonblock_count = 0; + for (int i = 3; i < w - 4; i++) { + if ((i % period) == (period - 1)) { + block += FFMAX(FFMAX(grad[i + 0], grad[i + 1]), grad[i - 1]); + block_count++; + } else { + nonblock += grad[i]; + nonblock_count++; + } + } + temp = (block / block_count) / (nonblock / nonblock_count); + ret = FFMAX(ret, temp); + } + + // vertical blockiness (fixed height) + block_count = 0; + for (int j = 3; j < h - 4; j++) { + for (int i = 1; i < w; i++) { + float temp = 0.0f; + grad[j * grad_linesize + i] = + abs(src[(j + 0) * src_linesize + i] - src[(j + 1) * src_linesize + i]); + temp += abs(src[(j + 1) * src_linesize + i] - src[(j + 2) * src_linesize + i]); + temp += abs(src[(j + 2) * src_linesize + i] - src[(j + 3) * src_linesize + i]); + temp += abs(src[(j + 3) * src_linesize + i] - src[(j + 4) * src_linesize + i]); + temp += abs(src[(j - 0) * src_linesize + i] - src[(j - 1) * src_linesize + i]); + temp += abs(src[(j - 1) * src_linesize + i] - src[(j - 2) * src_linesize + i]); + temp += abs(src[(j - 2) * src_linesize + i] - src[(j - 3) * src_linesize + i]); + temp = FFMAX(1, temp); + grad[j * grad_linesize + i] /= temp; + + // use first column to store accumulated results + grad[j * grad_linesize] += grad[j * grad_linesize + i]; + } + } + + // find vertical period + for (int period = s->period_min; period < s->period_max + 1; period++) { + float temp; + block = 0; + nonblock = 0; + block_count = 0; + nonblock_count = 0; + for (int j = 3; j < h - 4; j++) { + if ((j % period) == (period - 1)) { + block += FFMAX(FFMAX(grad[(j + 0) * grad_linesize], + grad[(j + 1) * grad_linesize]), + grad[(j - 1) * grad_linesize]); + block_count++; + } else { + nonblock += grad[j * grad_linesize]; + nonblock_count++; + } + } + temp = (block / block_count) / (nonblock / nonblock_count); + ret = FFMAX(ret, temp); + } + + // return highest value of horz||vert + return ret; +} + +static void set_meta(AVDictionary **metadata, const char *key, float d) +{ + char value[128]; + snprintf(value, sizeof(value), "%f", d); + av_dict_set(metadata, key, value, 0); +} + +static int blockdetect_filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + BLKContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + + const int inw = inlink->w; + const int inh = inlink->h; + + float *gradients = s->gradients; + + float block = 0.0f; + int nplanes = 0; + AVDictionary **metadata; + metadata = &in->metadata; + + for (int 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; + int w = AV_CEIL_RSHIFT(inw, hsub); + int h = AV_CEIL_RSHIFT(inh, vsub); + + if (!((1 << plane) & s->planes)) + continue; + + nplanes++; + + block += calculate_blockiness(s, w, h, gradients, w, in->data[plane], in->linesize[plane]); + } + + if (nplanes) + block /= nplanes; + + s->block_total += block; + + // write stats + av_log(ctx, AV_LOG_VERBOSE, "block: %.7f\n", block); + + set_meta(metadata, "lavfi.block", block); + + s->nb_frames = inlink->frame_count_in; + + return ff_filter_frame(outlink, in); +} + +static av_cold void blockdetect_uninit(AVFilterContext *ctx) +{ + BLKContext *s = ctx->priv; + + if (s->nb_frames > 0) { + av_log(ctx, AV_LOG_INFO, "block mean: %.7f\n", + s->block_total / s->nb_frames); + } + + av_freep(&s->gradients); +} + +static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_GRAY8, + AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, + AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, + AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, + AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ420P, + AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, + AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P, + AV_PIX_FMT_NONE +}; + +static const AVFilterPad blockdetect_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = blockdetect_config_input, + .filter_frame = blockdetect_filter_frame, + }, +}; + +static const AVFilterPad blockdetect_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, +}; + +const AVFilter ff_vf_blockdetect = { + .name = "blockdetect", + .description = NULL_IF_CONFIG_SMALL("Blockdetect filter."), + .priv_size = sizeof(BLKContext), + .init = blockdetect_init, + .uninit = blockdetect_uninit, + FILTER_PIXFMTS_ARRAY(pix_fmts), + FILTER_INPUTS(blockdetect_inputs), + FILTER_OUTPUTS(blockdetect_outputs), + .priv_class = &blockdetect_class, + .flags = AVFILTER_FLAG_METADATA_ONLY, +}; diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 97831bdf39..faed832cd4 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -687,6 +687,9 @@ fate-filter-metadata-avf-aphase-meter-out-of-phase: CMD = run $(FILTER_METADATA_ FATE_FILTER_SAMPLES-$(call TRANSCODE, RAWVIDEO H264, MOV, ARESAMPLE_FILTER AAC_FIXED_DECODER) += fate-filter-meta-4560-rotate0 fate-filter-meta-4560-rotate0: CMD = transcode mov $(TARGET_SAMPLES)/filter/sample-in-issue-505.mov mov "-c copy -metadata:s:v:0 rotate=0" "-af aresample" "" "" "-flags +bitexact -c:a aac_fixed" +FATE_FILTER_CMP_METADATA-$(CONFIG_BLOCKDETECT_FILTER) += fate-filter-refcmp-blockdetect-yuv +fate-filter-refcmp-blockdetect-yuv: CMD = cmp_metadata blockdetect yuv420p 0.015 + FATE_FILTER_CMP_METADATA-$(CONFIG_BLURDETECT_FILTER) += fate-filter-refcmp-blurdetect-yuv fate-filter-refcmp-blurdetect-yuv: CMD = cmp_metadata blurdetect yuv420p 0.015 diff --git a/tests/ref/fate/filter-refcmp-blockdetect-yuv b/tests/ref/fate/filter-refcmp-blockdetect-yuv new file mode 100644 index 0000000000..c3b3a3644f --- /dev/null +++ b/tests/ref/fate/filter-refcmp-blockdetect-yuv @@ -0,0 +1,10 @@ +frame:0 pts:0 pts_time:0 +lavfi.block=46.592525 +frame:1 pts:1 pts_time:1 +lavfi.block=40.478703 +frame:2 pts:2 pts_time:2 +lavfi.block=40.858681 +frame:3 pts:3 pts_time:3 +lavfi.block=39.519077 +frame:4 pts:4 pts_time:4 +lavfi.block=38.713215