2 * Copyright (c) 2021 Thilo Borgmann <thilo.borgmann _at_ mail.de>
4 * This file is part of FFmpeg.
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 * No-reference blockdetect filter
26 * Remco Muijs and Ihor Kirenko: "A no-reference blocking artifact measure for adaptive video processing." 2005 13th European signal processing conference. IEEE, 2005.
27 * http://www.eurasip.org/Proceedings/Eusipco/Eusipco2005/defevent/papers/cr1042.pdf
29 * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
32 #include "libavutil/imgutils.h"
33 #include "libavutil/opt.h"
36 typedef struct BLKContext {
42 int period_min; // minimum period to search for
43 int period_max; // maximum period to search for
44 int planes; // number of planes to filter
52 #define OFFSET(x) offsetof(BLKContext, x)
53 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
54 static const AVOption blockdetect_options[] = {
55 { "period_min", "Minimum period to search for", OFFSET(period_min), AV_OPT_TYPE_INT, {.i64=3}, 2, 32, FLAGS},
56 { "period_max", "Maximum period to search for", OFFSET(period_max), AV_OPT_TYPE_INT, {.i64=24}, 2, 64, FLAGS},
57 { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=1}, 0, 15, FLAGS },
61 AVFILTER_DEFINE_CLASS(blockdetect);
63 static int blockdetect_config_input(AVFilterLink *inlink)
65 AVFilterContext *ctx = inlink->dst;
66 BLKContext *s = ctx->priv;
67 const int bufsize = inlink->w * inlink->h;
68 const AVPixFmtDescriptor *pix_desc;
70 pix_desc = av_pix_fmt_desc_get(inlink->format);
71 s->hsub = pix_desc->log2_chroma_w;
72 s->vsub = pix_desc->log2_chroma_h;
73 s->nb_planes = av_pix_fmt_count_planes(inlink->format);
75 s->gradients = av_calloc(bufsize, sizeof(*s->gradients));
78 return AVERROR(ENOMEM);
83 static float calculate_blockiness(BLKContext *s, int w, int h,
84 float *grad, int grad_linesize,
85 uint8_t* src, int src_linesize)
88 float nonblock = 0.0f;
90 int nonblock_count = 0;
93 // Calculate BS in horizontal and vertical directions according to (1)(2)(3).
94 // Also try to find integer pixel periods (grids) even for scaled images.
95 // In case of fractional periods, FFMAX of current and neighbor pixels
96 // can help improve the correlation with MQS.
97 // Skip linear correction term (4)(5), as it appears only valid for their own test samples.
99 // horizontal blockiness (fixed width)
100 for (int j = 1; j < h; j++) {
101 for (int i = 3; i < w - 4; i++) {
103 grad[j * grad_linesize + i] =
104 abs(src[j * src_linesize + i + 0] - src[j * src_linesize + i + 1]);
105 temp += abs(src[j * src_linesize + i + 1] - src[j * src_linesize + i + 2]);
106 temp += abs(src[j * src_linesize + i + 2] - src[j * src_linesize + i + 3]);
107 temp += abs(src[j * src_linesize + i + 3] - src[j * src_linesize + i + 4]);
108 temp += abs(src[j * src_linesize + i - 0] - src[j * src_linesize + i - 1]);
109 temp += abs(src[j * src_linesize + i - 1] - src[j * src_linesize + i - 2]);
110 temp += abs(src[j * src_linesize + i - 2] - src[j * src_linesize + i - 3]);
111 temp = FFMAX(1, temp);
112 grad[j * grad_linesize + i] /= temp;
114 // use first row to store acculated results
115 grad[i] += grad[j * grad_linesize + i];
119 // find horizontal period
120 for (int period = s->period_min; period < s->period_max + 1; period++) {
126 for (int i = 3; i < w - 4; i++) {
127 if ((i % period) == (period - 1)) {
128 block += FFMAX(FFMAX(grad[i + 0], grad[i + 1]), grad[i - 1]);
135 if (block_count && nonblock_count) {
136 temp = (block / block_count) / (nonblock / nonblock_count);
137 ret = FFMAX(ret, temp);
141 // vertical blockiness (fixed height)
143 for (int j = 3; j < h - 4; j++) {
144 for (int i = 1; i < w; i++) {
146 grad[j * grad_linesize + i] =
147 abs(src[(j + 0) * src_linesize + i] - src[(j + 1) * src_linesize + i]);
148 temp += abs(src[(j + 1) * src_linesize + i] - src[(j + 2) * src_linesize + i]);
149 temp += abs(src[(j + 2) * src_linesize + i] - src[(j + 3) * src_linesize + i]);
150 temp += abs(src[(j + 3) * src_linesize + i] - src[(j + 4) * src_linesize + i]);
151 temp += abs(src[(j - 0) * src_linesize + i] - src[(j - 1) * src_linesize + i]);
152 temp += abs(src[(j - 1) * src_linesize + i] - src[(j - 2) * src_linesize + i]);
153 temp += abs(src[(j - 2) * src_linesize + i] - src[(j - 3) * src_linesize + i]);
154 temp = FFMAX(1, temp);
155 grad[j * grad_linesize + i] /= temp;
157 // use first column to store accumulated results
158 grad[j * grad_linesize] += grad[j * grad_linesize + i];
162 // find vertical period
163 for (int period = s->period_min; period < s->period_max + 1; period++) {
169 for (int j = 3; j < h - 4; j++) {
170 if ((j % period) == (period - 1)) {
171 block += FFMAX(FFMAX(grad[(j + 0) * grad_linesize],
172 grad[(j + 1) * grad_linesize]),
173 grad[(j - 1) * grad_linesize]);
176 nonblock += grad[j * grad_linesize];
180 if (block_count && nonblock_count) {
181 temp = (block / block_count) / (nonblock / nonblock_count);
182 ret = FFMAX(ret, temp);
186 // return highest value of horz||vert
190 static void set_meta(AVDictionary **metadata, const char *key, float d)
193 snprintf(value, sizeof(value), "%f", d);
194 av_dict_set(metadata, key, value, 0);
197 static int blockdetect_filter_frame(AVFilterLink *inlink, AVFrame *in)
199 AVFilterContext *ctx = inlink->dst;
200 BLKContext *s = ctx->priv;
201 AVFilterLink *outlink = ctx->outputs[0];
203 const int inw = inlink->w;
204 const int inh = inlink->h;
206 float *gradients = s->gradients;
210 AVDictionary **metadata;
211 metadata = &in->metadata;
213 for (int plane = 0; plane < s->nb_planes; plane++) {
214 int hsub = plane == 1 || plane == 2 ? s->hsub : 0;
215 int vsub = plane == 1 || plane == 2 ? s->vsub : 0;
216 int w = AV_CEIL_RSHIFT(inw, hsub);
217 int h = AV_CEIL_RSHIFT(inh, vsub);
219 if (!((1 << plane) & s->planes))
224 block += calculate_blockiness(s, w, h, gradients, w, in->data[plane], in->linesize[plane]);
230 s->block_total += block;
233 av_log(ctx, AV_LOG_VERBOSE, "block: %.7f\n", block);
235 set_meta(metadata, "lavfi.block", block);
237 s->nb_frames = inlink->frame_count_in;
239 return ff_filter_frame(outlink, in);
242 static av_cold void blockdetect_uninit(AVFilterContext *ctx)
244 BLKContext *s = ctx->priv;
246 if (s->nb_frames > 0) {
247 av_log(ctx, AV_LOG_INFO, "block mean: %.7f\n",
248 s->block_total / s->nb_frames);
251 av_freep(&s->gradients);
254 static const enum AVPixelFormat pix_fmts[] = {
256 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP,
257 AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P,
258 AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
259 AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
260 AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ420P,
261 AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P,
262 AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P,
266 static const AVFilterPad blockdetect_inputs[] = {
269 .type = AVMEDIA_TYPE_VIDEO,
270 .config_props = blockdetect_config_input,
271 .filter_frame = blockdetect_filter_frame,
275 static const AVFilterPad blockdetect_outputs[] = {
278 .type = AVMEDIA_TYPE_VIDEO,
282 const AVFilter ff_vf_blockdetect = {
283 .name = "blockdetect",
284 .description = NULL_IF_CONFIG_SMALL("Blockdetect filter."),
285 .priv_size = sizeof(BLKContext),
286 .uninit = blockdetect_uninit,
287 FILTER_PIXFMTS_ARRAY(pix_fmts),
288 FILTER_INPUTS(blockdetect_inputs),
289 FILTER_OUTPUTS(blockdetect_outputs),
290 .priv_class = &blockdetect_class,
291 .flags = AVFILTER_FLAG_METADATA_ONLY,