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 blurdetect filter
24 *
25 * Implementing:
26 * Marziliano, Pina, et al. "A no-reference perceptual blur metric." Proceedings.
27 * International conference on image processing. Vol. 3. IEEE, 2002.
28 * https://infoscience.epfl.ch/record/111802/files/14%20A%20no-reference%20perceptual%20blur%20metric.pdf
29 *
30 * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
31 */
32
38
39 static int comp(
const float *
a,
const float *
b)
40 {
42 }
43
46
49
52 int radius;
// radius during local maxima detection
53 int block_pct;
// percentage of "sharpest" blocks in the image to use for bluriness calculation
56 int planes;
// number of planes to filter
57
60
67
68 #define OFFSET(x) offsetof(BLRContext, x)
69 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
74 {
"block_pct",
"block pooling threshold when calculating blurriness",
OFFSET(block_pct),
AV_OPT_TYPE_INT, {.i64=80}, 1, 100,
FLAGS },
75 {
"block_width",
"block size for block-based abbreviation of blurriness",
OFFSET(block_width),
AV_OPT_TYPE_INT, {.i64=-1}, -1, INT_MAX,
FLAGS },
76 {
"block_height",
"block size for block-based abbreviation of blurriness",
OFFSET(block_height),
AV_OPT_TYPE_INT, {.i64=-1}, -1, INT_MAX,
FLAGS },
79 };
80
82
84 {
86
87 s->low_u8 =
s->low * 255. + .5;
88 s->high_u8 =
s->high * 255. + .5;
89
90 return 0;
91 }
92
94 {
99
104
105 if (
s->block_width < 1 ||
s->block_height < 1) {
108 }
109
112 s->gradients =
av_calloc(bufsize,
sizeof(*
s->gradients));
116
117 if (!
s->tmpbuf || !
s->filterbuf || !
s->gradients || !
s->directions || !
s->blks)
119
120 return 0;
121 }
122
123 // edge width is defined as the distance between surrounding maxima of the edge pixel
125 int edge,
const uint8_t *
src,
int src_linesize)
126 {
128 int dX, dY;
129 int sign;
131 int p1;
132 int p2;
133 int k, x, y;
135
136 switch(dir) {
141 default: dX = 1; dY = 1; break;
142 }
143
144 // determines if search in direction dX/dY is looking for a maximum or minimum
145 sign =
src[j * src_linesize +
i] >
src[(j - dY) * src_linesize +
i - dX] ? 1 : -1;
146
147 // search in -(dX/dY) direction
148 for (k = 0; k < radius; k++) {
150 y = j - k*dY;
151 p1 = y * src_linesize + x;
152 x -= dX;
153 y -= dY;
154 p2 = y * src_linesize + x;
155 if (x < 0 || x >=
w || y < 0 || y >=
h)
156 return 0;
157
159
160 if (
tmp <= 0)
// local maximum found
161 break;
162 }
164
165 // search in +(dX/dY) direction
166 for (k = 0; k < radius; k++) {
168 y = j + k * dY;
169 p1 = y * src_linesize + x;
170 x += dX;
171 y += dY;
172 p2 = y * src_linesize + x;
173 if (x < 0 || x >=
w || y < 0 || y >=
h)
174 return 0;
175
177
178 if (
tmp >= 0)
// local maximum found
179 break;
180 }
182
183 // for 45 degree directions approximate edge width in pixel units: 0.7 ~= sqrt(2)/2
186
188 }
189
191 int8_t* dir, int dir_linesize,
192 uint8_t* dst, int dst_linesize,
193 uint8_t*
src,
int src_linesize)
194 {
195 float total_width = 0.0;
196 int block_count;
197 double block_total_width;
198
200 int blkcnt = 0;
201
202 float *blks =
s->blks;
203 float block_pool_threshold =
s->block_pct / 100.0;
204
207 int brows =
h / block_height;
208 int bcols =
w / block_width;
209
210 for (int blkj = 0; blkj < brows; blkj++) {
211 for (int blki = 0; blki < bcols; blki++) {
212 block_total_width = 0.0;
213 block_count = 0;
214 for (int inj = 0; inj < block_height; inj++) {
215 for (int ini = 0; ini < block_width; ini++) {
216 i = blki * block_width + ini;
217 j = blkj * block_height + inj;
218
219 if (dst[j * dst_linesize +
i] > 0) {
221 w,
h, dst[j*dst_linesize+
i],
223 if (
width > 0.001) {
// throw away zeros
224 block_count++;
225 block_total_width +=
width;
226 }
227 }
228 }
229 }
230 // if not enough edge pixels in a block, consider it smooth
231 if (block_total_width >= 2 && block_count) {
232 blks[blkcnt] = block_total_width / block_count;
233 blkcnt++;
234 }
235 }
236 }
237
238 // simple block pooling by sorting and keeping the sharper blocks
240 blkcnt =
ceil(blkcnt * block_pool_threshold);
241 for (
int i = 0;
i < blkcnt;
i++) {
242 total_width += blks[
i];
243 }
244
245 return total_width / blkcnt;
246 }
247
249 {
253 }
254
256 {
260
261 const int inw =
inlink->w;
262 const int inh =
inlink->h;
263
264 uint8_t *tmpbuf =
s->tmpbuf;
265 uint8_t *filterbuf =
s->filterbuf;
266 uint16_t *gradients =
s->gradients;
267 int8_t *directions =
s->directions;
268
270 int nplanes = 0;
273
274 for (
int plane = 0; plane <
s->nb_planes; plane++) {
275 int hsub = plane == 1 || plane == 2 ?
s->hsub : 0;
276 int vsub = plane == 1 || plane == 2 ?
s->vsub : 0;
279
280 if (!((1 << plane) &
s->planes))
281 continue;
282
283 nplanes++;
284
285 // gaussian filter to reduce noise
289
290 // compute the 16-bits gradients and directions for the next step
292
293 // non_maximum_suppression() will actually keep & clip what's necessary and
294 // ignore the rest, so we need a clean output buffer
295 memset(tmpbuf, 0, inw * inh);
297
298
299 // keep high values, or low values surrounded by high values
301 tmpbuf,
w, tmpbuf,
w);
302
304 tmpbuf,
w, filterbuf,
w);
305 }
306
307 if (nplanes)
309
310 s->blur_total +=
blur;
311
312 // write stats
314
316
317 s->nb_frames =
inlink->frame_count_in;
318
320 }
321
323 {
325
326 if (
s->nb_frames > 0) {
328 s->blur_total /
s->nb_frames);
329 }
330
336 }
337
348 };
349
351 {
356 },
357 };
358
360 {
363 },
364 };
365
367 .
name =
"blurdetect",
375 .priv_class = &blurdetect_class,
377 };