1 /*
2 * Copyright (c) 2002 A'rpi
3 * This file is part of FFmpeg.
4 *
5 * FFmpeg is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * FFmpeg is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 /**
21 * @file
22 * border detection filter
23 * Ported from MPlayer libmpcodecs/vf_cropdetect.c.
24 */
25
32
37
61
77 };
78
83 };
84
85 static int comp(
const int *
a,
const int *
b)
86 {
88 }
89
91 {
92 int total = 0;
94 const uint16_t *src16 = (
const uint16_t *)
src;
95
96 switch (bpp) {
97 case 1:
103 }
107 }
108 break;
109 case 2:
116 }
118 total += src16[0];
120 }
121 break;
122 case 3:
123 case 4:
131 }
135 }
136 div *= 3;
137 break;
138 }
139 total /= div;
140
142 return total;
143 }
144
146 {
147 const uint16_t *src16 = (
const uint16_t *)
src;
148
149 switch (bpp) {
150 case 1:
152 if (
src[0])
return 0;
154 }
155 break;
156 case 2:
159 if (src16[0]) return 0;
161 }
162 break;
163 case 3:
164 case 4:
166 if (
src[0] ||
src[1] ||
src[2])
return 0;
168 }
169 break;
170 }
171
172 return 1;
173 }
174
176 {
178
179 s->frame_nb = -1 *
s->skip;
180 s->low_u8 =
s->low * 255. + .5;
181 s->high_u8 =
s->high * 255. + .5;
182
184 s->limit,
s->round,
s->skip,
s->reset_count);
185
186 return 0;
187 }
188
190 {
192
201 }
202
204 {
209
211
212 s->bitdepth =
desc->comp[0].depth;
213
215 s->limit_upscaled =
s->limit * ((1 <<
s->bitdepth) - 1);
216 else
217 s->limit_upscaled =
s->limit;
218
223
224 s->window_size =
FFMAX(
s->reset_count, 15);
226 s->filterbuf =
av_malloc(bufsize *
s->max_pixsteps[0]);
227 s->gradients =
av_calloc(bufsize,
sizeof(*
s->gradients));
229 s->bboxes[0] =
av_malloc(
s->window_size *
sizeof(*
s->bboxes[0]));
230 s->bboxes[1] =
av_malloc(
s->window_size *
sizeof(*
s->bboxes[1]));
231 s->bboxes[2] =
av_malloc(
s->window_size *
sizeof(*
s->bboxes[2]));
232 s->bboxes[3] =
av_malloc(
s->window_size *
sizeof(*
s->bboxes[3]));
233
234 if (!
s->tmpbuf || !
s->filterbuf || !
s->gradients || !
s->directions ||
235 !
s->bboxes[0] || !
s->bboxes[1] || !
s->bboxes[2] || !
s->bboxes[3])
237
238 return 0;
239 }
240
241 #define SET_META(key, value) \
242 av_dict_set_int(metadata, key, value, 0)
243
245 {
248 int bpp =
s->max_pixsteps[0];
249 int w,
h, x, y, shrink_by,
i;
251 int outliers, last_y;
252 int limit_upscaled =
lrint(
s->limit_upscaled);
253 char limit_str[22];
254
255 const int inw =
inlink->w;
256 const int inh =
inlink->h;
257 uint8_t *tmpbuf =
s->tmpbuf;
258 uint8_t *filterbuf =
s->filterbuf;
259 uint16_t *gradients =
s->gradients;
260 int8_t *directions =
s->directions;
262 int scan_w, scan_h, bboff;
263
264 void (*
sobel)(
int w,
int h, uint16_t *
dst,
int dst_linesize,
265 int8_t *dir, int dir_linesize,
266 const uint8_t *
src,
int src_linesize,
int src_stride) = (bpp == 2) ? &ff_sobel_16 : &ff_sobel_8;
268 uint8_t *
dst,
int dst_linesize,
269 const uint8_t *
src,
int src_linesize,
int src_stride) = (bpp == 2) ? &ff_gaussian_blur_16 : &ff_gaussian_blur_8;
270
271
272 // ignore first s->skip frames
273 if (++
s->frame_nb > 0) {
274 metadata = &
frame->metadata;
275
276 // Reset the crop area every reset_count frames, if reset_count is > 0
277 if (
s->reset_count > 0 &&
s->frame_nb >
s->reset_count) {
279 s->y1 =
frame->height - 1;
283 }
284
285 #define FIND(DST, FROM, NOEND, INC, STEP0, STEP1, LEN) \
286 outliers = 0;\
287 for (last_y = y = FROM; NOEND; y = y INC) {\
288 if (checkline(ctx, frame->data[0] + STEP0 * y, STEP1, LEN, bpp) > limit_upscaled) {\
289 if (++outliers > s->max_outliers) { \
290 DST = last_y;\
291 break;\
292 }\
293 } else\
294 last_y = y INC;\
295 }
296
302 } else { // MODE_MV_EDGES
308
309 if (!sd) {
311 } else {
312 // gaussian filter to reduce noise
314 filterbuf, inw*bpp,
316
317 // compute the 16-bits gradients and directions for the next step
318 sobel(inw, inh, gradients, inw, directions, inw, filterbuf, inw*bpp, bpp);
319
320 // non_maximum_suppression() will actually keep & clip what's necessary and
321 // ignore the rest, so we need a clean output buffer
322 memset(tmpbuf, 0, inw * inh);
324
325
326 // keep high values, or low values surrounded by high values
328 tmpbuf, inw, tmpbuf, inw);
329
330 // scan all MVs and store bounding box
337 const int mx =
mv->dst_x -
mv->src_x;
338 const int my =
mv->dst_y -
mv->src_y;
339
340 if (
mv->dst_x >= 0 &&
mv->dst_x < inw &&
341 mv->dst_y >= 0 &&
mv->dst_y < inh &&
342 mv->src_x >= 0 &&
mv->src_x < inw &&
343 mv->src_y >= 0 &&
mv->src_y < inh &&
344 mx *
mx +
my *
my >=
s->mv_threshold *
s->mv_threshold) {
345 s->x1 =
mv->dst_x <
s->x1 ?
mv->dst_x :
s->x1;
346 s->y1 =
mv->dst_y <
s->y1 ?
mv->dst_y :
s->y1;
347 s->x2 =
mv->dst_x >
s->x2 ?
mv->dst_x :
s->x2;
348 s->y2 =
mv->dst_y >
s->y2 ?
mv->dst_y :
s->y2;
349 }
350 }
351
352 // assert x1<x2, y1<y2
355
356 // scan outward looking for 0-edge-lines in edge image
357 scan_w =
s->x2 -
s->x1;
358 scan_h =
s->y2 -
s->y1;
359
360 #define FIND_EDGE(DST, FROM, NOEND, INC, STEP0, STEP1, LEN) \
361 for (last_y = y = FROM; NOEND; y = y INC) { \
362 if (checkline_edge(ctx, tmpbuf + STEP0 * y, STEP1, LEN, bpp)) { \
363 if (last_y INC == y) { \
364 DST = y; \
365 break; \
366 } else \
367 last_y = y; \
368 } \
369 } \
370 if (!(NOEND)) { \
371 DST = y -(INC); \
372 }
373
374 FIND_EDGE(
s->y1,
s->y1, y >= 0, -1, inw, bpp, scan_w);
375 FIND_EDGE(
s->y2,
s->y2, y < inh, +1, inw, bpp, scan_w);
376 FIND_EDGE(
s->x1,
s->x1, y >= 0, -1, bpp, inw, scan_h);
377 FIND_EDGE(
s->x2,
s->x2, y < inw, +1, bpp, inw, scan_h);
378
379 // queue bboxes
380 bboff = (
s->frame_nb - 1) %
s->window_size;
381 s->bboxes[0][bboff] =
s->x1;
382 s->bboxes[1][bboff] =
s->x2;
383 s->bboxes[2][bboff] =
s->y1;
384 s->bboxes[3][bboff] =
s->y2;
385
386 // sort queue
387 bboff =
FFMIN(
s->frame_nb,
s->window_size);
392
393 // return median of window_size elems
394 s->x1 =
s->bboxes[0][bboff/2];
395 s->x2 =
s->bboxes[1][bboff/2];
396 s->y1 =
s->bboxes[2][bboff/2];
397 s->y2 =
s->bboxes[3][bboff/2];
398 }
399 }
400
401 // round x and y (up), important for yuv colorspaces
402 // make sure they stay rounded!
405
408
409 // w and h must be divisible by 2 as well because of yuv
410 // colorspace problems.
415
416 shrink_by =
w %
s->round;
418 x += (shrink_by/2 + 1) & ~1;
419
420 shrink_by =
h %
s->round;
422 y += (shrink_by/2 + 1) & ~1;
423
432
433 snprintf(limit_str,
sizeof(limit_str),
"%f",
s->limit);
434 av_dict_set(metadata,
"lavfi.cropdetect.limit", limit_str, 0);
435
437 "x1:%d x2:%d y1:%d y2:%d w:%d h:%d x:%d y:%d pts:%"PRId64" t:%f limit:%f crop=%d:%d:%d:%d\n",
438 s->x1,
s->x2,
s->y1,
s->y2,
w,
h, x, y,
frame->pts,
440 s->limit,
w,
h, x, y);
441 }
442
444 }
445
447 char *res,
int res_len,
int flags)
448 {
450 float old_limit =
s->limit;
452
455
456 if (old_limit !=
s->limit) {
458 s->limit_upscaled =
s->limit * ((1 <<
s->bitdepth) - 1);
459 else
460 s->limit_upscaled =
s->limit;
461 s->frame_nb =
s->reset_count;
462 }
463
464 return 0;
465 }
466
467 #define OFFSET(x) offsetof(CropDetectContext, x)
468 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
469 #define TFLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_RUNTIME_PARAM
470
474 {
"reset",
"Recalculate the crop area after this many frames",
OFFSET(reset_count),
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX,
FLAGS },
476 {
"reset_count",
"Recalculate the crop area after this many frames",
OFFSET(reset_count),
AV_OPT_TYPE_INT,{ .i64 = 0 }, 0, INT_MAX,
FLAGS },
483 {
"mv_threshold",
"motion vector threshold when estimating video window size",
OFFSET(mv_threshold),
AV_OPT_TYPE_INT, {.i64=8}, 0, 100,
FLAGS},
485 };
486
488
490 {
495 },
496 };
497
499 .
name =
"cropdetect",
502 .priv_class = &cropdetect_class,
510 };