Am 25.04.22 um 00:03 schrieb Thilo Borgmann:
Am 24.04.22 um 19:28 schrieb Thilo Borgmann:
Am 23.04.22 um 15:32 schrieb Thilo Borgmann:
Hi,
v3 updated to current HEAD.
Named blurdetect filter now.
Minor fixes on allocation and removed -f option.
Please make this per plane filtering, with default to measure only first
plane.
done in v4.
(Will add Changelog, version.h and fate test once the filter itself looks ok)
Ping.
v5 according to IRC comments.
v6 (1/2 and 2/2) according to IRC comments. LGTM'd there, applying soon.
Added Changelog, version.h, FATE.
v7: removed useless debug garbage. Still applying soon.
v8: and now even with adapted FATE.
-Thilo
From 72f1631e8994e15eac58c5c9a8c0fcf14ef627ad Mon Sep 17 00:00:00 2001
From: Thilo Borgmann <thilo.borgm...@mail.de>
Date: Mon, 25 Apr 2022 00:07:04 +0200
Subject: [PATCH v8 2/2] lavfi: Add blurdetect filter
---
Changelog | 1 +
doc/filters.texi | 52 +++
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/version.h | 2 +-
libavfilter/vf_blurdetect.c | 395 ++++++++++++++++++++
tests/fate/filter-video.mak | 3 +
tests/ref/fate/filter-refcmp-blurdetect-yuv | 10 +
8 files changed, 464 insertions(+), 1 deletion(-)
create mode 100644 libavfilter/vf_blurdetect.c
create mode 100644 tests/ref/fate/filter-refcmp-blurdetect-yuv
diff --git a/Changelog b/Changelog
index 7a63e3d1ee..4d467eb741 100644
--- a/Changelog
+++ b/Changelog
@@ -13,6 +13,7 @@ version 5.1:
- pixelize video filter
- colormap video filter
- colorchart video source filter
+- blurdetect filter
version 5.0:
diff --git a/doc/filters.texi b/doc/filters.texi
index c8699b9099..499f3adcd9 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -7997,6 +7997,58 @@ tblend=all_mode=grainextract
@subsection Commands
This filter supports same @ref{commands} as options.
+@anchor{blurdetect}
+@section blurdetect
+
+Determines blurriness of frames without altering the input frames.
+
+Based on Marziliano, Pina, et al. "A no-reference perceptual blur metric."
+Allows for a block-based abbreviation.
+
+The filter accepts the following options:
+
+@table @option
+@item low
+@item high
+Set low and high threshold values used by the Canny thresholding
+algorithm.
+
+The high threshold selects the "strong" edge pixels, which are then
+connected through 8-connectivity with the "weak" edge pixels selected
+by the low threshold.
+
+@var{low} and @var{high} threshold values must be chosen in the range
+[0,1], and @var{low} should be lesser or equal to @var{high}.
+
+Default value for @var{low} is @code{20/255}, and default value for @var{high}
+is @code{50/255}.
+
+@item radius
+Define the radius to search around an edge pixel for local maxima.
+
+@item block_pct
+Determine blurriness only for the most significant blocks, given in percentage.
+
+@item block_width
+Determine blurriness for blocks of width @var{block_width}. If set to any
value smaller 1, no blocks are used and the whole image is processed as one no
matter of @var{block_height}.
+
+@item block_height
+Determine blurriness for blocks of height @var{block_height}. If set to any
value smaller 1, no blocks are used and the whole image is processed as one no
matter of @var{block_width}.
+
+@item planes
+Set planes to filter. Default is first only.
+@end table
+
+@subsection Examples
+
+@itemize
+@item
+Determine blur for 80% of most significant 32x32 blocks:
+@example
+blurdetect=block_width=32:block_height=32:block_pct=80
+@end example
+@end itemize
+
@section bm3d
Denoise frames using Block-Matching 3D algorithm.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 38ca379e5a..1db097b464 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -195,6 +195,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_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
OBJS-$(CONFIG_BOXBLUR_OPENCL_FILTER) += vf_avgblur_opencl.o opencl.o \
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 36fa3ae8d7..2ad523fd0f 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_blurdetect;
extern const AVFilter ff_vf_bm3d;
extern const AVFilter ff_vf_boxblur;
extern const AVFilter ff_vf_boxblur_opencl;
diff --git a/libavfilter/version.h b/libavfilter/version.h
index 9add1658e5..8f1b16969a 100644
--- a/libavfilter/version.h
+++ b/libavfilter/version.h
@@ -31,7 +31,7 @@
#include "version_major.h"
-#define LIBAVFILTER_VERSION_MINOR 36
+#define LIBAVFILTER_VERSION_MINOR 37
#define LIBAVFILTER_VERSION_MICRO 100
diff --git a/libavfilter/vf_blurdetect.c b/libavfilter/vf_blurdetect.c
new file mode 100644
index 0000000000..7c0f5ce68a
--- /dev/null
+++ b/libavfilter/vf_blurdetect.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2021 Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ *
+ * 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 blurdetect filter
+ *
+ * Implementing:
+ * Marziliano, Pina, et al. "A no-reference perceptual blur metric."
Proceedings.
+ * International conference on image processing. Vol. 3. IEEE, 2002.
+ *
https://infoscience.epfl.ch/record/111802/files/14%20A%20no-reference%20perceptual%20blur%20metric.pdf
+ *
+ * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixelutils.h"
+#include "libavutil/motion_vector.h"
+#include "libavutil/qsort.h"
+#include "avfilter.h"
+#include "formats.h"
+#include "internal.h"
+#include "video.h"
+#include "edge_common.h"
+
+static int comp(const float *a,const float *b)
+{
+ return FFDIFFSIGN(*a, *b);
+}
+
+typedef struct BLRContext {
+ const AVClass *class;
+
+ int hsub, vsub;
+ int nb_planes;
+
+ float low, high;
+ uint8_t low_u8, high_u8;
+ int radius; // radius during local maxima detection
+ int block_pct; // percentage of "sharpest" blocks in the image to
use for bluriness calculation
+ int block_width; // width for block abbreviation
+ int block_height; // height for block abbreviation
+ int planes; // number of planes to filter
+
+ double blur_total;
+ uint64_t nb_frames;
+
+ float *blks;
+ uint8_t *filterbuf;
+ uint8_t *tmpbuf;
+ uint16_t *gradients;
+ char *directions;
+} BLRContext;
+
+#define OFFSET(x) offsetof(BLRContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+static const AVOption blurdetect_options[] = {
+ { "high", "set high threshold", OFFSET(high), AV_OPT_TYPE_FLOAT,
{.dbl=30/255.}, 0, 1, FLAGS },
+ { "low", "set low threshold", OFFSET(low), AV_OPT_TYPE_FLOAT,
{.dbl=15/255.}, 0, 1, FLAGS },
+ { "radius", "search radius for maxima detection", OFFSET(radius),
AV_OPT_TYPE_INT, {.i64=50}, 1, 100, FLAGS },
+ { "block_pct", "block pooling threshold when calculating blurriness",
OFFSET(block_pct), AV_OPT_TYPE_INT, {.i64=80}, 1, 100, FLAGS },
+ { "block_width", "block size for block-based abbreviation of
blurriness", OFFSET(block_width), AV_OPT_TYPE_INT, {.i64=-1}, -1, INT_MAX,
FLAGS },
+ { "block_height", "block size for block-based abbreviation of
blurriness", OFFSET(block_height), AV_OPT_TYPE_INT, {.i64=-1}, -1, INT_MAX,
FLAGS },
+ { "planes", "set planes to filter", OFFSET(planes),
AV_OPT_TYPE_INT, {.i64=1}, 0, 15, FLAGS },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(blurdetect);
+
+static av_cold int blurdetect_init(AVFilterContext *ctx)
+{
+ BLRContext *s = ctx->priv;
+
+ s->low_u8 = s->low * 255. + .5;
+ s->high_u8 = s->high * 255. + .5;
+
+ return 0;
+}
+
+static int blurdetect_config_input(AVFilterLink *inlink)
+{
+ AVFilterContext *ctx = inlink->dst;
+ BLRContext *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);
+
+ if (s->block_width < 1 || s->block_height < 1) {
+ s->block_width = inlink->w;
+ s->block_height = inlink->h;
+ }
+
+ s->tmpbuf = av_malloc(bufsize);
+ s->filterbuf = av_malloc(bufsize);
+ s->gradients = av_calloc(bufsize, sizeof(*s->gradients));
+ s->directions = av_malloc(bufsize);
+ s->blks = av_calloc((inlink->w / s->block_width) * (inlink->h /
s->block_height),
+ sizeof(*s->blks));
+
+ if (!s->tmpbuf || !s->filterbuf || !s->gradients || !s->directions ||
!s->blks)
+ return AVERROR(ENOMEM);
+
+ return 0;
+}
+
+// edge width is defined as the distance between surrounding maxima of the
edge pixel
+static float edge_width(BLRContext *blr, int i, int j, int8_t dir, int w, int
h,
+ int edge, const uint8_t *src, int src_linesize)
+{
+ float width = 0;
+ int dX, dY;
+ int sign;
+ int tmp;
+ int p1;
+ int p2;
+ int k, x, y;
+ int edge1;
+ int edge2;
+ float luma1 = 0.0; // average luma difference per edge pixel
+ float luma2 = 0.0;
+ int radius = blr->radius;
+
+ switch(dir) {
+ case DIRECTION_HORIZONTAL: dX = 1; dY = 0; break;
+ case DIRECTION_VERTICAL: dX = 0; dY = 1; break;
+ case DIRECTION_45UP: dX = 1; dY = -1; break;
+ case DIRECTION_45DOWN: dX = 1; dY = 1; break;
+ }
+
+ // determines if search in direction dX/dY is looking for a maximum or
minimum
+ sign = src[j * src_linesize + i] > src[(j - dY) * src_linesize + i - dX] ?
1 : -1;
+
+ // search in -(dX/dY) direction
+ for (k = 0; k < radius; k++) {
+ x = i - k*dX;
+ y = j - k*dY;
+ p1 = y * src_linesize + x;
+ x -= dX;
+ y -= dY;
+ p2 = y * src_linesize + x;
+ if (x < 0 || x >= w || y < 0 || y >= h)
+ return 0;
+
+ tmp = (src[p1] - src[p2]) * sign;
+
+ if (tmp <= 0) // local maximum found
+ break;
+
+ luma1 += tmp;
+ }
+ if (k > 0) luma1 /= k;
+ edge1 = k;
+ width += k;
+
+ // search in +(dX/dY) direction
+ for (k = 0; k < radius; k++) {
+ x = i + k * dX;
+ y = j + k * dY;
+ p1 = y * src_linesize + x;
+ x += dX;
+ y += dY;
+ p2 = y * src_linesize + x;
+ if (x < 0 || x >= w || y < 0 || y >= h)
+ return 0;
+
+ tmp = (src[p1] - src[p2]) * sign;
+
+ if (tmp >= 0) // local maximum found
+ break;
+
+ luma2 -= tmp;
+ }
+ if (k > 0) luma2 /= k;
+ edge2 = k;
+ width += k;
+
+ // for 45 degree directions approximate edge width in pixel units: 0.7 ~=
sqrt(2)/2
+ if (dir == DIRECTION_45UP || dir == DIRECTION_45DOWN)
+ width *= 0.7;
+
+ return width;
+}
+
+static float calculate_blur(BLRContext *s, int w, int h, int hsub, int vsub,
+ uint8_t* dir, int dir_linesize,
+ uint8_t* dst, int dst_linesize,
+ uint8_t* src, int src_linesize)
+{
+ float total_width = 0.0;
+ int block_count;
+ double block_total_width;
+
+ int i, j;
+ int blkcnt = 0;
+
+ float *blks = s->blks;
+ float block_pool_threshold = s->block_pct / 100.0;
+
+ int block_width = AV_CEIL_RSHIFT(s->block_width, hsub);
+ int block_height = AV_CEIL_RSHIFT(s->block_height, vsub);
+ int brows = h / block_height;
+ int bcols = w / block_width;
+
+ for (int blkj = 0; blkj < brows; blkj++) {
+ for (int blki = 0; blki < bcols; blki++) {
+ block_total_width = 0.0;
+ block_count = 0;
+ for (int inj = 0; inj < block_height; inj++) {
+ for (int ini = 0; ini < block_width; ini++) {
+ i = blki * block_width + ini;
+ j = blkj * block_height + inj;
+
+ if (dst[j * dst_linesize + i] > 0) {
+ float width = edge_width(s, i, j,
dir[j*dir_linesize+i],
+ w, h, dst[j*dst_linesize+i],
+ src, src_linesize);
+ if (width > 0.001) { // throw away zeros
+ block_count++;
+ block_total_width += width;
+ }
+ }
+ }
+ }
+ // if not enough edge pixels in a block, consider it smooth
+ if (block_total_width >= 2) {
+ blks[blkcnt] = block_total_width / block_count;
+ blkcnt++;
+ }
+ }
+ }
+
+ // simple block pooling by sorting and keeping the sharper blocks
+ AV_QSORT(blks, blkcnt, float, comp);
+ blkcnt = ceil(blkcnt * block_pool_threshold);
+ for (int i = 0; i < blkcnt; i++) {
+ total_width += blks[i];
+ }
+
+ return total_width / blkcnt;
+}
+
+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 blurdetect_filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+ AVFilterContext *ctx = inlink->dst;
+ BLRContext *s = ctx->priv;
+ AVFilterLink *outlink = ctx->outputs[0];
+
+ const int inw = inlink->w;
+ const int inh = inlink->h;
+
+ uint8_t *tmpbuf = s->tmpbuf;
+ uint8_t *filterbuf = s->filterbuf;
+ uint16_t *gradients = s->gradients;
+ int8_t *directions = s->directions;
+
+ float blur = 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++;
+
+ // gaussian filter to reduce noise
+ ff_gaussian_blur(w, h,
+ filterbuf, w,
+ in->data[plane], in->linesize[plane]);
+
+ // compute the 16-bits gradients and directions for the next step
+ ff_sobel(w, h, gradients, w, directions, w, filterbuf, w);
+
+ // non_maximum_suppression() will actually keep & clip what's
necessary and
+ // ignore the rest, so we need a clean output buffer
+ memset(tmpbuf, 0, inw * inh);
+ ff_non_maximum_suppression(w, h, tmpbuf, w, directions, w, gradients,
w);
+
+
+ // keep high values, or low values surrounded by high values
+ ff_double_threshold(s->low_u8, s->high_u8, w, h,
+ tmpbuf, w, tmpbuf, w);
+
+ blur += calculate_blur(s, w, h, hsub, vsub, directions, w,
+ tmpbuf, w, filterbuf, w);
+ }
+
+ if (nplanes)
+ blur /= nplanes;
+
+ s->blur_total += blur;
+
+ // write stats
+ av_log(ctx, AV_LOG_VERBOSE, "blur: %.7f\n", blur);
+
+ set_meta(metadata, "lavfi.blur", blur);
+
+ s->nb_frames = inlink->frame_count_in;
+
+ return ff_filter_frame(outlink, in);
+}
+
+static av_cold void blurdetect_uninit(AVFilterContext *ctx)
+{
+ BLRContext *s = ctx->priv;
+
+ if (s->nb_frames > 0) {
+ av_log(ctx, AV_LOG_INFO, "blur mean: %.7f\n",
+ s->blur_total / s->nb_frames);
+ }
+
+ av_freep(&s->tmpbuf);
+ av_freep(&s->filterbuf);
+ av_freep(&s->gradients);
+ av_freep(&s->directions);
+ av_freep(&s->blks);
+}
+
+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 blurdetect_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .config_props = blurdetect_config_input,
+ .filter_frame = blurdetect_filter_frame,
+ },
+};
+
+static const AVFilterPad blurdetect_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ },
+};
+
+const AVFilter ff_vf_blurdetect = {
+ .name = "blurdetect",
+ .description = NULL_IF_CONFIG_SMALL("Blurdetect filter."),
+ .priv_size = sizeof(BLRContext),
+ .init = blurdetect_init,
+ .uninit = blurdetect_uninit,
+ FILTER_PIXFMTS_ARRAY(pix_fmts),
+ FILTER_INPUTS(blurdetect_inputs),
+ FILTER_OUTPUTS(blurdetect_outputs),
+ .priv_class = &blurdetect_class,
+ .flags = AVFILTER_FLAG_METADATA_ONLY,
+};
+
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index 6c0d8df032..68f4c084f8 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -867,6 +867,9 @@ fate-filter-meta-4560-rotate0: CMD = framecrc
-auto_conversion_filters -flags +b
REFCMP_DEPS = FFMPEG LAVFI_INDEV TESTSRC2_FILTER AVGBLUR_FILTER METADATA_FILTER
+FATE_FILTER-$(call ALLYES, $(REFCMP_DEPS) BLURDETECT_FILTER) +=
fate-filter-refcmp-blurdetect-yuv
+fate-filter-refcmp-blurdetect-yuv: CMD = cmp_metadata blurdetect yuv420p 0.015
+
FATE_FILTER-$(call ALLYES, $(REFCMP_DEPS) PSNR_FILTER) +=
fate-filter-refcmp-psnr-rgb
fate-filter-refcmp-psnr-rgb: CMD = refcmp_metadata psnr rgb24 0.002
diff --git a/tests/ref/fate/filter-refcmp-blurdetect-yuv
b/tests/ref/fate/filter-refcmp-blurdetect-yuv
new file mode 100644
index 0000000000..426321db0c
--- /dev/null
+++ b/tests/ref/fate/filter-refcmp-blurdetect-yuv
@@ -0,0 +1,10 @@
+frame:0 pts:0 pts_time:0
+lavfi.blur=4.499666
+frame:1 pts:1 pts_time:1
+lavfi.blur=4.677492
+frame:2 pts:2 pts_time:2
+lavfi.blur=4.735711
+frame:3 pts:3 pts_time:3
+lavfi.blur=4.532343
+frame:4 pts:4 pts_time:4
+lavfi.blur=4.532660
--
2.20.1 (Apple Git-117)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".