27283590be10bde6be87246b38044199b2bdd86d
[platform/upstream/ffmpeg.git] / libavfilter / vf_blockdetect.c
1 /*
2  * Copyright (c) 2021 Thilo Borgmann <thilo.borgmann _at_ mail.de>
3  *
4  * This file is part of FFmpeg.
5  *
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.
10  *
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.
15  *
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
19  */
20
21 /**
22  * @file
23  * No-reference blockdetect filter
24  *
25  * Implementing:
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
28  *
29  * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
30  */
31
32 #include "libavutil/imgutils.h"
33 #include "libavutil/opt.h"
34 #include "internal.h"
35
36 typedef struct BLKContext {
37     const AVClass *class;
38
39     int hsub, vsub;
40     int nb_planes;
41
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
45
46     double block_total;
47     uint64_t nb_frames;
48
49     float *gradients;
50 } BLKContext;
51
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 },
58     { NULL }
59 };
60
61 AVFILTER_DEFINE_CLASS(blockdetect);
62
63 static int blockdetect_config_input(AVFilterLink *inlink)
64 {
65     AVFilterContext *ctx = inlink->dst;
66     BLKContext      *s   = ctx->priv;
67     const int bufsize    = inlink->w * inlink->h;
68     const AVPixFmtDescriptor *pix_desc;
69
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);
74
75     s->gradients = av_calloc(bufsize, sizeof(*s->gradients));
76
77     if (!s->gradients)
78         return AVERROR(ENOMEM);
79
80     return 0;
81 }
82
83 static float calculate_blockiness(BLKContext *s, int w, int h,
84                                   float *grad, int grad_linesize,
85                                   uint8_t* src, int src_linesize)
86 {
87     float block = 0.0f;
88     float nonblock = 0.0f;
89     int block_count = 0;
90     int nonblock_count = 0;
91     float ret = 0;
92
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.
98
99     // horizontal blockiness (fixed width)
100     for (int j = 1; j < h; j++) {
101         for (int i = 3; i < w - 4; i++) {
102             float temp = 0.0f;
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;
113
114             // use first row to store acculated results
115             grad[i] += grad[j * grad_linesize + i];
116         }
117     }
118
119     // find horizontal period
120     for (int period = s->period_min; period < s->period_max + 1; period++) {
121         float temp;
122         block = 0;
123         nonblock = 0;
124         block_count = 0;
125         nonblock_count = 0;
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]);
129                 block_count++;
130             } else {
131                 nonblock += grad[i];
132                 nonblock_count++;
133             }
134         }
135         if (block_count && nonblock_count) {
136             temp = (block / block_count) / (nonblock / nonblock_count);
137             ret = FFMAX(ret, temp);
138         }
139     }
140
141     // vertical blockiness (fixed height)
142     block_count = 0;
143     for (int j = 3; j < h - 4; j++) {
144         for (int i = 1; i < w; i++) {
145             float temp = 0.0f;
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;
156
157             // use first column to store accumulated results
158             grad[j * grad_linesize] += grad[j * grad_linesize + i];
159         }
160     }
161
162     // find vertical period
163     for (int period = s->period_min; period < s->period_max + 1; period++) {
164         float temp;
165         block = 0;
166         nonblock = 0;
167         block_count = 0;
168         nonblock_count = 0;
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]);
174                 block_count++;
175             } else {
176                 nonblock += grad[j * grad_linesize];
177                 nonblock_count++;
178             }
179         }
180         if (block_count && nonblock_count) {
181             temp = (block / block_count) / (nonblock / nonblock_count);
182             ret = FFMAX(ret, temp);
183         }
184     }
185
186     // return highest value of horz||vert
187     return ret;
188 }
189
190 static void set_meta(AVDictionary **metadata, const char *key, float d)
191 {
192     char value[128];
193     snprintf(value, sizeof(value), "%f", d);
194     av_dict_set(metadata, key, value, 0);
195 }
196
197 static int blockdetect_filter_frame(AVFilterLink *inlink, AVFrame *in)
198 {
199     AVFilterContext *ctx  = inlink->dst;
200     BLKContext *s         = ctx->priv;
201     AVFilterLink *outlink = ctx->outputs[0];
202
203     const int inw = inlink->w;
204     const int inh = inlink->h;
205
206     float *gradients   = s->gradients;
207
208     float block = 0.0f;
209     int nplanes = 0;
210     AVDictionary **metadata;
211     metadata = &in->metadata;
212
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);
218
219         if (!((1 << plane) & s->planes))
220             continue;
221
222         nplanes++;
223
224         block += calculate_blockiness(s, w, h, gradients, w, in->data[plane], in->linesize[plane]);
225     }
226
227     if (nplanes)
228         block /= nplanes;
229
230     s->block_total += block;
231
232     // write stats
233     av_log(ctx, AV_LOG_VERBOSE, "block: %.7f\n", block);
234
235     set_meta(metadata, "lavfi.block", block);
236
237     s->nb_frames = inlink->frame_count_in;
238
239     return ff_filter_frame(outlink, in);
240 }
241
242 static av_cold void blockdetect_uninit(AVFilterContext *ctx)
243 {
244     BLKContext *s = ctx->priv;
245
246     if (s->nb_frames > 0) {
247         av_log(ctx, AV_LOG_INFO, "block mean: %.7f\n",
248                s->block_total / s->nb_frames);
249     }
250
251     av_freep(&s->gradients);
252 }
253
254 static const enum AVPixelFormat pix_fmts[] = {
255     AV_PIX_FMT_GRAY8,
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,
263     AV_PIX_FMT_NONE
264 };
265
266 static const AVFilterPad blockdetect_inputs[] = {
267     {
268         .name         = "default",
269         .type         = AVMEDIA_TYPE_VIDEO,
270         .config_props = blockdetect_config_input,
271         .filter_frame = blockdetect_filter_frame,
272     },
273 };
274
275 static const AVFilterPad blockdetect_outputs[] = {
276     {
277         .name = "default",
278         .type = AVMEDIA_TYPE_VIDEO,
279     },
280 };
281
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,
292 };