Line | Branch | Exec | Source |
---|---|---|---|
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 | |||
26 | #include "libavutil/imgutils.h" | ||
27 | #include "libavutil/internal.h" | ||
28 | #include "libavutil/mem.h" | ||
29 | #include "libavutil/opt.h" | ||
30 | #include "libavutil/motion_vector.h" | ||
31 | #include "libavutil/qsort.h" | ||
32 | |||
33 | #include "avfilter.h" | ||
34 | #include "filters.h" | ||
35 | #include "video.h" | ||
36 | #include "edge_common.h" | ||
37 | |||
38 | typedef struct CropDetectContext { | ||
39 | const AVClass *class; | ||
40 | int x1, y1, x2, y2; | ||
41 | float limit; | ||
42 | float limit_upscaled; | ||
43 | int round; | ||
44 | int skip; | ||
45 | int reset_count; | ||
46 | int frame_nb; | ||
47 | int max_pixsteps[4]; | ||
48 | int max_outliers; | ||
49 | int mode; | ||
50 | int window_size; | ||
51 | int mv_threshold; | ||
52 | int bitdepth; | ||
53 | float low, high; | ||
54 | uint8_t low_u8, high_u8; | ||
55 | uint8_t *filterbuf; | ||
56 | uint8_t *tmpbuf; | ||
57 | uint16_t *gradients; | ||
58 | char *directions; | ||
59 | int *bboxes[4]; | ||
60 | } CropDetectContext; | ||
61 | |||
62 | static const enum AVPixelFormat pix_fmts[] = { | ||
63 | AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P, | ||
64 | AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVJ422P, | ||
65 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUVJ444P, | ||
66 | AV_PIX_FMT_YUV411P, AV_PIX_FMT_GRAY8, | ||
67 | AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV410P, | ||
68 | AV_PIX_FMT_YUV420P9 , AV_PIX_FMT_YUV422P9 , AV_PIX_FMT_YUV444P9, | ||
69 | AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, | ||
70 | AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, | ||
71 | AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, | ||
72 | AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, | ||
73 | AV_PIX_FMT_NV12, AV_PIX_FMT_NV21, | ||
74 | AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, | ||
75 | AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, | ||
76 | AV_PIX_FMT_NONE | ||
77 | }; | ||
78 | |||
79 | enum CropMode { | ||
80 | MODE_BLACK, | ||
81 | MODE_MV_EDGES, | ||
82 | MODE_NB | ||
83 | }; | ||
84 | |||
85 | 370 | static int comp(const int *a,const int *b) | |
86 | { | ||
87 | 370 | return FFDIFFSIGN(*a, *b); | |
88 | } | ||
89 | |||
90 | 3676 | static int checkline(void *ctx, const unsigned char *src, int stride, int len, int bpp) | |
91 | { | ||
92 | 3676 | int total = 0; | |
93 | 3676 | int div = len; | |
94 | 3676 | const uint16_t *src16 = (const uint16_t *)src; | |
95 | |||
96 |
1/4✓ Branch 0 taken 3676 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3676 | switch (bpp) { |
97 | 3676 | case 1: | |
98 |
2/2✓ Branch 0 taken 330600 times.
✓ Branch 1 taken 3676 times.
|
334276 | while (len >= 8) { |
99 | 330600 | total += src[ 0] + src[ stride] + src[2*stride] + src[3*stride] | |
100 | 330600 | + src[4*stride] + src[5*stride] + src[6*stride] + src[7*stride]; | |
101 | 330600 | src += 8*stride; | |
102 | 330600 | len -= 8; | |
103 | } | ||
104 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 3676 times.
|
3724 | while (--len >= 0) { |
105 | 48 | total += src[0]; | |
106 | 48 | src += stride; | |
107 | } | ||
108 | 3676 | break; | |
109 | ✗ | case 2: | |
110 | ✗ | stride >>= 1; | |
111 | ✗ | while (len >= 8) { | |
112 | ✗ | total += src16[ 0] + src16[ stride] + src16[2*stride] + src16[3*stride] | |
113 | ✗ | + src16[4*stride] + src16[5*stride] + src16[6*stride] + src16[7*stride]; | |
114 | ✗ | src16 += 8*stride; | |
115 | ✗ | len -= 8; | |
116 | } | ||
117 | ✗ | while (--len >= 0) { | |
118 | ✗ | total += src16[0]; | |
119 | ✗ | src16 += stride; | |
120 | } | ||
121 | ✗ | break; | |
122 | ✗ | case 3: | |
123 | case 4: | ||
124 | ✗ | while (len >= 4) { | |
125 | ✗ | total += src[0] + src[1 ] + src[2 ] | |
126 | ✗ | + src[ stride] + src[1+ stride] + src[2+ stride] | |
127 | ✗ | + src[2*stride] + src[1+2*stride] + src[2+2*stride] | |
128 | ✗ | + src[3*stride] + src[1+3*stride] + src[2+3*stride]; | |
129 | ✗ | src += 4*stride; | |
130 | ✗ | len -= 4; | |
131 | } | ||
132 | ✗ | while (--len >= 0) { | |
133 | ✗ | total += src[0] + src[1] + src[2]; | |
134 | ✗ | src += stride; | |
135 | } | ||
136 | ✗ | div *= 3; | |
137 | ✗ | break; | |
138 | } | ||
139 | 3676 | total /= div; | |
140 | |||
141 | 3676 | av_log(ctx, AV_LOG_DEBUG, "total:%d\n", total); | |
142 | 3676 | return total; | |
143 | } | ||
144 | |||
145 | 2983 | static int checkline_edge(void *ctx, const unsigned char *src, int stride, int len, int bpp) | |
146 | { | ||
147 | 2983 | const uint16_t *src16 = (const uint16_t *)src; | |
148 | |||
149 |
1/4✓ Branch 0 taken 2983 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
2983 | switch (bpp) { |
150 | 2983 | case 1: | |
151 |
2/2✓ Branch 0 taken 442164 times.
✓ Branch 1 taken 107 times.
|
442271 | while (--len >= 0) { |
152 |
2/2✓ Branch 0 taken 2876 times.
✓ Branch 1 taken 439288 times.
|
442164 | if (src[0]) return 0; |
153 | 439288 | src += stride; | |
154 | } | ||
155 | 107 | break; | |
156 | ✗ | case 2: | |
157 | ✗ | stride >>= 1; | |
158 | ✗ | while (--len >= 0) { | |
159 | ✗ | if (src16[0]) return 0; | |
160 | ✗ | src16 += stride; | |
161 | } | ||
162 | ✗ | break; | |
163 | ✗ | case 3: | |
164 | case 4: | ||
165 | ✗ | while (--len >= 0) { | |
166 | ✗ | if (src[0] || src[1] || src[2]) return 0; | |
167 | ✗ | src += stride; | |
168 | } | ||
169 | ✗ | break; | |
170 | } | ||
171 | |||
172 | 107 | return 1; | |
173 | } | ||
174 | |||
175 | 3 | static av_cold int init(AVFilterContext *ctx) | |
176 | { | ||
177 | 3 | CropDetectContext *s = ctx->priv; | |
178 | |||
179 | 3 | s->frame_nb = -1 * s->skip; | |
180 | 3 | s->low_u8 = s->low * 255. + .5; | |
181 | 3 | s->high_u8 = s->high * 255. + .5; | |
182 | |||
183 | 3 | av_log(ctx, AV_LOG_VERBOSE, "limit:%f round:%d skip:%d reset_count:%d\n", | |
184 | 3 | s->limit, s->round, s->skip, s->reset_count); | |
185 | |||
186 | 3 | return 0; | |
187 | } | ||
188 | |||
189 | 3 | static av_cold void uninit(AVFilterContext *ctx) | |
190 | { | ||
191 | 3 | CropDetectContext *s = ctx->priv; | |
192 | |||
193 | 3 | av_freep(&s->tmpbuf); | |
194 | 3 | av_freep(&s->filterbuf); | |
195 | 3 | av_freep(&s->gradients); | |
196 | 3 | av_freep(&s->directions); | |
197 | 3 | av_freep(&s->bboxes[0]); | |
198 | 3 | av_freep(&s->bboxes[1]); | |
199 | 3 | av_freep(&s->bboxes[2]); | |
200 | 3 | av_freep(&s->bboxes[3]); | |
201 | 3 | } | |
202 | |||
203 | 3 | static int config_input(AVFilterLink *inlink) | |
204 | { | ||
205 | 3 | AVFilterContext *ctx = inlink->dst; | |
206 | 3 | CropDetectContext *s = ctx->priv; | |
207 | 3 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
208 | 3 | const int bufsize = inlink->w * inlink->h; | |
209 | |||
210 | 3 | av_image_fill_max_pixsteps(s->max_pixsteps, NULL, desc); | |
211 | |||
212 | 3 | s->bitdepth = desc->comp[0].depth; | |
213 | |||
214 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (s->limit < 1.0) |
215 | 3 | s->limit_upscaled = s->limit * ((1 << s->bitdepth) - 1); | |
216 | else | ||
217 | ✗ | s->limit_upscaled = s->limit; | |
218 | |||
219 | 3 | s->x1 = inlink->w - 1; | |
220 | 3 | s->y1 = inlink->h - 1; | |
221 | 3 | s->x2 = 0; | |
222 | 3 | s->y2 = 0; | |
223 | |||
224 | 3 | s->window_size = FFMAX(s->reset_count, 15); | |
225 | 3 | s->tmpbuf = av_malloc(bufsize); | |
226 | 3 | s->filterbuf = av_malloc(bufsize * s->max_pixsteps[0]); | |
227 | 3 | s->gradients = av_calloc(bufsize, sizeof(*s->gradients)); | |
228 | 3 | s->directions = av_malloc(bufsize); | |
229 | 3 | s->bboxes[0] = av_malloc(s->window_size * sizeof(*s->bboxes[0])); | |
230 | 3 | s->bboxes[1] = av_malloc(s->window_size * sizeof(*s->bboxes[1])); | |
231 | 3 | s->bboxes[2] = av_malloc(s->window_size * sizeof(*s->bboxes[2])); | |
232 | 3 | s->bboxes[3] = av_malloc(s->window_size * sizeof(*s->bboxes[3])); | |
233 | |||
234 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
3 | if (!s->tmpbuf || !s->filterbuf || !s->gradients || !s->directions || |
235 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
|
3 | !s->bboxes[0] || !s->bboxes[1] || !s->bboxes[2] || !s->bboxes[3]) |
236 | ✗ | return AVERROR(ENOMEM); | |
237 | |||
238 | 3 | return 0; | |
239 | } | ||
240 | |||
241 | #define SET_META(key, value) \ | ||
242 | av_dict_set_int(metadata, key, value, 0) | ||
243 | |||
244 | 50 | static int filter_frame(AVFilterLink *inlink, AVFrame *frame) | |
245 | { | ||
246 | 50 | AVFilterContext *ctx = inlink->dst; | |
247 | 50 | CropDetectContext *s = ctx->priv; | |
248 | 50 | int bpp = s->max_pixsteps[0]; | |
249 | int w, h, x, y, shrink_by, i; | ||
250 | AVDictionary **metadata; | ||
251 | int outliers, last_y; | ||
252 | 50 | int limit_upscaled = lrint(s->limit_upscaled); | |
253 | char limit_str[22]; | ||
254 | |||
255 | 50 | const int inw = inlink->w; | |
256 | 50 | const int inh = inlink->h; | |
257 | 50 | uint8_t *tmpbuf = s->tmpbuf; | |
258 | 50 | uint8_t *filterbuf = s->filterbuf; | |
259 | 50 | uint16_t *gradients = s->gradients; | |
260 | 50 | int8_t *directions = s->directions; | |
261 | 50 | const AVFrameSideData *sd = NULL; | |
262 | int scan_w, scan_h, bboff; | ||
263 | |||
264 | 50 | void (*sobel)(int w, int h, uint16_t *dst, int dst_linesize, | |
265 | int8_t *dir, int dir_linesize, | ||
266 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 50 times.
|
50 | const uint8_t *src, int src_linesize, int src_stride) = (bpp == 2) ? &ff_sobel_16 : &ff_sobel_8; |
267 | 50 | void (*gaussian_blur)(int w, int h, | |
268 | uint8_t *dst, int dst_linesize, | ||
269 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 50 times.
|
50 | 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 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 6 times.
|
50 | if (++s->frame_nb > 0) { |
274 | 44 | metadata = &frame->metadata; | |
275 | |||
276 | // Reset the crop area every reset_count frames, if reset_count is > 0 | ||
277 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
44 | if (s->reset_count > 0 && s->frame_nb > s->reset_count) { |
278 | ✗ | s->x1 = frame->width - 1; | |
279 | ✗ | s->y1 = frame->height - 1; | |
280 | ✗ | s->x2 = 0; | |
281 | ✗ | s->y2 = 0; | |
282 | ✗ | s->frame_nb = 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 | |||
297 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 14 times.
|
44 | if (s->mode == MODE_BLACK) { |
298 |
6/6✓ Branch 1 taken 62 times.
✓ Branch 2 taken 1772 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 61 times.
✓ Branch 5 taken 1834 times.
✓ Branch 6 taken 29 times.
|
1863 | FIND(s->y1, 0, y < s->y1, +1, frame->linesize[0], bpp, frame->width); |
299 |
6/6✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1830 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 1834 times.
✓ Branch 6 taken 29 times.
|
1863 | FIND(s->y2, frame->height - 1, y > FFMAX(s->y2, s->y1), -1, frame->linesize[0], bpp, frame->width); |
300 |
5/6✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 29 times.
|
33 | FIND(s->x1, 0, y < s->x1, +1, bpp, frame->linesize[0], frame->height); |
301 |
5/6✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 29 times.
|
33 | FIND(s->x2, frame->width - 1, y > FFMAX(s->x2, s->x1), -1, bpp, frame->linesize[0], frame->height); |
302 | } else { // MODE_MV_EDGES | ||
303 | 14 | sd = av_frame_get_side_data(frame, AV_FRAME_DATA_MOTION_VECTORS); | |
304 | 14 | s->x1 = 0; | |
305 | 14 | s->y1 = 0; | |
306 | 14 | s->x2 = inw - 1; | |
307 | 14 | s->y2 = inh - 1; | |
308 | |||
309 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (!sd) { |
310 | ✗ | av_log(ctx, AV_LOG_WARNING, "Cannot detect: no motion vectors available"); | |
311 | } else { | ||
312 | // gaussian filter to reduce noise | ||
313 | 14 | gaussian_blur(inw, inh, | |
314 | filterbuf, inw*bpp, | ||
315 | 14 | frame->data[0], frame->linesize[0], bpp); | |
316 | |||
317 | // compute the 16-bits gradients and directions for the next step | ||
318 | 14 | 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 | 14 | memset(tmpbuf, 0, inw * inh); | |
323 | 14 | ff_non_maximum_suppression(inw, inh, tmpbuf, inw, directions, inw, gradients, inw); | |
324 | |||
325 | |||
326 | // keep high values, or low values surrounded by high values | ||
327 | 14 | ff_double_threshold(s->low_u8, s->high_u8, inw, inh, | |
328 | tmpbuf, inw, tmpbuf, inw); | ||
329 | |||
330 | // scan all MVs and store bounding box | ||
331 | 14 | s->x1 = inw - 1; | |
332 | 14 | s->y1 = inh - 1; | |
333 | 14 | s->x2 = 0; | |
334 | 14 | s->y2 = 0; | |
335 |
2/2✓ Branch 0 taken 178640 times.
✓ Branch 1 taken 14 times.
|
178654 | for (i = 0; i < sd->size / sizeof(AVMotionVector); i++) { |
336 | 178640 | const AVMotionVector *mv = (const AVMotionVector*)sd->data + i; | |
337 | 178640 | const int mx = mv->dst_x - mv->src_x; | |
338 | 178640 | const int my = mv->dst_y - mv->src_y; | |
339 | |||
340 |
2/4✓ Branch 0 taken 178640 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178640 times.
✗ Branch 3 not taken.
|
178640 | if (mv->dst_x >= 0 && mv->dst_x < inw && |
341 |
2/4✓ Branch 0 taken 178640 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178640 times.
✗ Branch 3 not taken.
|
178640 | mv->dst_y >= 0 && mv->dst_y < inh && |
342 |
2/4✓ Branch 0 taken 178640 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178640 times.
✗ Branch 3 not taken.
|
178640 | mv->src_x >= 0 && mv->src_x < inw && |
343 |
2/4✓ Branch 0 taken 178640 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 178640 times.
✗ Branch 3 not taken.
|
178640 | mv->src_y >= 0 && mv->src_y < inh && |
344 |
2/2✓ Branch 0 taken 263 times.
✓ Branch 1 taken 178377 times.
|
178640 | mx * mx + my * my >= s->mv_threshold * s->mv_threshold) { |
345 | 263 | s->x1 = mv->dst_x < s->x1 ? mv->dst_x : s->x1; | |
346 | 263 | s->y1 = mv->dst_y < s->y1 ? mv->dst_y : s->y1; | |
347 | 263 | s->x2 = mv->dst_x > s->x2 ? mv->dst_x : s->x2; | |
348 | 263 | s->y2 = mv->dst_y > s->y2 ? mv->dst_y : s->y2; | |
349 | } | ||
350 | } | ||
351 | |||
352 | // assert x1<x2, y1<y2 | ||
353 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (s->x1 > s->x2) FFSWAP(int, s->x1, s->x2); |
354 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (s->y1 > s->y2) FFSWAP(int, s->y1, s->y2); |
355 | |||
356 | // scan outward looking for 0-edge-lines in edge image | ||
357 | 14 | scan_w = s->x2 - s->x1; | |
358 | 14 | 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 |
6/8✓ Branch 1 taken 31 times.
✓ Branch 2 taken 464 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 495 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 14 times.
|
495 | FIND_EDGE(s->y1, s->y1, y >= 0, -1, inw, bpp, scan_w); |
375 |
6/8✓ Branch 1 taken 28 times.
✓ Branch 2 taken 433 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 461 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 14 times.
|
461 | FIND_EDGE(s->y2, s->y2, y < inh, +1, inw, bpp, scan_w); |
376 |
8/8✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1009 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 1033 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 10 times.
|
1037 | FIND_EDGE(s->x1, s->x1, y >= 0, -1, bpp, inw, scan_h); |
377 |
8/8✓ Branch 1 taken 24 times.
✓ Branch 2 taken 970 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 994 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 10 times.
|
998 | FIND_EDGE(s->x2, s->x2, y < inw, +1, bpp, inw, scan_h); |
378 | |||
379 | // queue bboxes | ||
380 | 14 | bboff = (s->frame_nb - 1) % s->window_size; | |
381 | 14 | s->bboxes[0][bboff] = s->x1; | |
382 | 14 | s->bboxes[1][bboff] = s->x2; | |
383 | 14 | s->bboxes[2][bboff] = s->y1; | |
384 | 14 | s->bboxes[3][bboff] = s->y2; | |
385 | |||
386 | // sort queue | ||
387 | 14 | bboff = FFMIN(s->frame_nb, s->window_size); | |
388 |
43/44✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 11 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 10 times.
✓ Branch 12 taken 3 times.
✓ Branch 13 taken 9 times.
✓ Branch 14 taken 3 times.
✓ Branch 15 taken 9 times.
✓ Branch 16 taken 11 times.
✓ Branch 17 taken 1 times.
✓ Branch 19 taken 1 times.
✓ Branch 20 taken 10 times.
✓ Branch 21 taken 15 times.
✓ Branch 22 taken 2 times.
✓ Branch 24 taken 6 times.
✓ Branch 25 taken 9 times.
✓ Branch 26 taken 9 times.
✓ Branch 27 taken 2 times.
✓ Branch 28 taken 11 times.
✓ Branch 29 taken 9 times.
✓ Branch 30 taken 7 times.
✓ Branch 31 taken 2 times.
✓ Branch 32 taken 5 times.
✓ Branch 33 taken 2 times.
✓ Branch 34 taken 4 times.
✓ Branch 35 taken 1 times.
✓ Branch 36 taken 26 times.
✓ Branch 37 taken 5 times.
✓ Branch 39 taken 25 times.
✓ Branch 40 taken 1 times.
✓ Branch 41 taken 5 times.
✓ Branch 42 taken 1 times.
✓ Branch 43 taken 1 times.
✓ Branch 44 taken 3 times.
✓ Branch 46 taken 3 times.
✓ Branch 47 taken 2 times.
✓ Branch 48 taken 17 times.
✓ Branch 49 taken 5 times.
✓ Branch 50 taken 18 times.
✓ Branch 51 taken 14 times.
|
88 | AV_QSORT(s->bboxes[0], bboff, int, comp); |
389 |
43/44✓ Branch 0 taken 13 times.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 9 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 3 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 9 times.
✓ Branch 12 taken 7 times.
✓ Branch 13 taken 6 times.
✓ Branch 14 taken 3 times.
✓ Branch 15 taken 10 times.
✓ Branch 16 taken 12 times.
✓ Branch 17 taken 1 times.
✓ Branch 19 taken 3 times.
✓ Branch 20 taken 9 times.
✓ Branch 21 taken 17 times.
✓ Branch 22 taken 4 times.
✓ Branch 24 taken 11 times.
✓ Branch 25 taken 6 times.
✓ Branch 26 taken 6 times.
✓ Branch 27 taken 4 times.
✓ Branch 28 taken 10 times.
✓ Branch 29 taken 10 times.
✓ Branch 30 taken 5 times.
✓ Branch 31 taken 5 times.
✓ Branch 32 taken 3 times.
✓ Branch 33 taken 2 times.
✓ Branch 34 taken 2 times.
✓ Branch 35 taken 1 times.
✓ Branch 36 taken 15 times.
✓ Branch 37 taken 2 times.
✓ Branch 39 taken 13 times.
✓ Branch 40 taken 2 times.
✓ Branch 41 taken 2 times.
✓ Branch 42 taken 2 times.
✓ Branch 43 taken 1 times.
✓ Branch 44 taken 7 times.
✓ Branch 46 taken 4 times.
✓ Branch 47 taken 5 times.
✓ Branch 48 taken 22 times.
✓ Branch 49 taken 8 times.
✓ Branch 50 taken 22 times.
✓ Branch 51 taken 14 times.
|
91 | AV_QSORT(s->bboxes[1], bboff, int, comp); |
390 |
43/44✓ Branch 0 taken 13 times.
✓ Branch 1 taken 7 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 11 times.
✓ Branch 12 taken 4 times.
✓ Branch 13 taken 9 times.
✓ Branch 14 taken 3 times.
✓ Branch 15 taken 10 times.
✓ Branch 16 taken 10 times.
✓ Branch 17 taken 2 times.
✓ Branch 19 taken 2 times.
✓ Branch 20 taken 8 times.
✓ Branch 21 taken 18 times.
✓ Branch 22 taken 3 times.
✓ Branch 24 taken 11 times.
✓ Branch 25 taken 7 times.
✓ Branch 26 taken 7 times.
✓ Branch 27 taken 3 times.
✓ Branch 28 taken 10 times.
✓ Branch 29 taken 10 times.
✓ Branch 30 taken 7 times.
✓ Branch 31 taken 3 times.
✓ Branch 32 taken 5 times.
✓ Branch 33 taken 2 times.
✓ Branch 34 taken 4 times.
✓ Branch 35 taken 1 times.
✓ Branch 36 taken 21 times.
✓ Branch 37 taken 4 times.
✓ Branch 39 taken 19 times.
✓ Branch 40 taken 2 times.
✓ Branch 41 taken 4 times.
✓ Branch 42 taken 2 times.
✓ Branch 43 taken 2 times.
✓ Branch 44 taken 4 times.
✓ Branch 46 taken 4 times.
✓ Branch 47 taken 3 times.
✓ Branch 48 taken 20 times.
✓ Branch 49 taken 6 times.
✓ Branch 50 taken 20 times.
✓ Branch 51 taken 14 times.
|
92 | AV_QSORT(s->bboxes[2], bboff, int, comp); |
391 |
37/44✓ Branch 0 taken 13 times.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 10 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 9 times.
✓ Branch 12 taken 5 times.
✓ Branch 13 taken 8 times.
✓ Branch 14 taken 4 times.
✓ Branch 15 taken 9 times.
✓ Branch 16 taken 13 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 4 times.
✓ Branch 20 taken 9 times.
✓ Branch 21 taken 16 times.
✓ Branch 22 taken 4 times.
✓ Branch 24 taken 11 times.
✓ Branch 25 taken 5 times.
✓ Branch 26 taken 5 times.
✓ Branch 27 taken 4 times.
✓ Branch 28 taken 9 times.
✓ Branch 29 taken 9 times.
✓ Branch 30 taken 4 times.
✓ Branch 31 taken 5 times.
✓ Branch 32 taken 1 times.
✓ Branch 33 taken 3 times.
✓ Branch 34 taken 1 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 18 times.
✓ Branch 37 taken 4 times.
✓ Branch 39 taken 18 times.
✗ Branch 40 not taken.
✓ Branch 41 taken 4 times.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✓ Branch 44 taken 5 times.
✗ Branch 46 not taken.
✓ Branch 47 taken 4 times.
✓ Branch 48 taken 17 times.
✓ Branch 49 taken 7 times.
✓ Branch 50 taken 19 times.
✓ Branch 51 taken 14 times.
|
89 | AV_QSORT(s->bboxes[3], bboff, int, comp); |
392 | |||
393 | // return median of window_size elems | ||
394 | 14 | s->x1 = s->bboxes[0][bboff/2]; | |
395 | 14 | s->x2 = s->bboxes[1][bboff/2]; | |
396 | 14 | s->y1 = s->bboxes[2][bboff/2]; | |
397 | 14 | 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! | ||
403 | 44 | x = (s->x1+1) & ~1; | |
404 | 44 | y = (s->y1+1) & ~1; | |
405 | |||
406 | 44 | w = s->x2 - x + 1; | |
407 | 44 | h = s->y2 - y + 1; | |
408 | |||
409 | // w and h must be divisible by 2 as well because of yuv | ||
410 | // colorspace problems. | ||
411 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | if (s->round <= 1) |
412 | ✗ | s->round = 16; | |
413 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | if (s->round % 2) |
414 | ✗ | s->round *= 2; | |
415 | |||
416 | 44 | shrink_by = w % s->round; | |
417 | 44 | w -= shrink_by; | |
418 | 44 | x += (shrink_by/2 + 1) & ~1; | |
419 | |||
420 | 44 | shrink_by = h % s->round; | |
421 | 44 | h -= shrink_by; | |
422 | 44 | y += (shrink_by/2 + 1) & ~1; | |
423 | |||
424 | 44 | SET_META("lavfi.cropdetect.x1", s->x1); | |
425 | 44 | SET_META("lavfi.cropdetect.x2", s->x2); | |
426 | 44 | SET_META("lavfi.cropdetect.y1", s->y1); | |
427 | 44 | SET_META("lavfi.cropdetect.y2", s->y2); | |
428 | 44 | SET_META("lavfi.cropdetect.w", w); | |
429 | 44 | SET_META("lavfi.cropdetect.h", h); | |
430 | 44 | SET_META("lavfi.cropdetect.x", x); | |
431 | 44 | SET_META("lavfi.cropdetect.y", y); | |
432 | |||
433 | 44 | snprintf(limit_str, sizeof(limit_str), "%f", s->limit); | |
434 | 44 | av_dict_set(metadata, "lavfi.cropdetect.limit", limit_str, 0); | |
435 | |||
436 | 44 | av_log(ctx, AV_LOG_INFO, | |
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, | ||
439 | 44 | frame->pts == AV_NOPTS_VALUE ? -1 : frame->pts * av_q2d(inlink->time_base), | |
440 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | s->limit, w, h, x, y); |
441 | } | ||
442 | |||
443 | 50 | return ff_filter_frame(inlink->dst->outputs[0], frame); | |
444 | } | ||
445 | |||
446 | ✗ | static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, | |
447 | char *res, int res_len, int flags) | ||
448 | { | ||
449 | ✗ | CropDetectContext *s = ctx->priv; | |
450 | ✗ | float old_limit = s->limit; | |
451 | int ret; | ||
452 | |||
453 | ✗ | if ((ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags)) < 0) | |
454 | ✗ | return ret; | |
455 | |||
456 | ✗ | if (old_limit != s->limit) { | |
457 | ✗ | if (s->limit < 1.0) | |
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 | |||
471 | static const AVOption cropdetect_options[] = { | ||
472 | { "limit", "Threshold below which the pixel is considered black", OFFSET(limit), AV_OPT_TYPE_FLOAT, { .dbl = 24.0/255 }, 0, 65535, TFLAGS }, | ||
473 | { "round", "Value by which the width/height should be divisible", OFFSET(round), AV_OPT_TYPE_INT, { .i64 = 16 }, 0, INT_MAX, FLAGS }, | ||
474 | { "reset", "Recalculate the crop area after this many frames", OFFSET(reset_count), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, | ||
475 | { "skip", "Number of initial frames to skip", OFFSET(skip), AV_OPT_TYPE_INT, { .i64 = 2 }, 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 }, | ||
477 | { "max_outliers", "Threshold count of outliers", OFFSET(max_outliers),AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, | ||
478 | { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=MODE_BLACK}, 0, MODE_NB-1, FLAGS, .unit = "mode" }, | ||
479 | { "black", "detect black pixels surrounding the video", 0, AV_OPT_TYPE_CONST, {.i64=MODE_BLACK}, INT_MIN, INT_MAX, FLAGS, .unit = "mode" }, | ||
480 | { "mvedges", "detect motion and edged surrounding the video", 0, AV_OPT_TYPE_CONST, {.i64=MODE_MV_EDGES}, INT_MIN, INT_MAX, FLAGS, .unit = "mode" }, | ||
481 | { "high", "Set high threshold for edge detection", OFFSET(high), AV_OPT_TYPE_FLOAT, {.dbl=25/255.}, 0, 1, FLAGS }, | ||
482 | { "low", "Set low threshold for edge detection", OFFSET(low), AV_OPT_TYPE_FLOAT, {.dbl=15/255.}, 0, 1, FLAGS }, | ||
483 | { "mv_threshold", "motion vector threshold when estimating video window size", OFFSET(mv_threshold), AV_OPT_TYPE_INT, {.i64=8}, 0, 100, FLAGS}, | ||
484 | { NULL } | ||
485 | }; | ||
486 | |||
487 | AVFILTER_DEFINE_CLASS(cropdetect); | ||
488 | |||
489 | static const AVFilterPad avfilter_vf_cropdetect_inputs[] = { | ||
490 | { | ||
491 | .name = "default", | ||
492 | .type = AVMEDIA_TYPE_VIDEO, | ||
493 | .config_props = config_input, | ||
494 | .filter_frame = filter_frame, | ||
495 | }, | ||
496 | }; | ||
497 | |||
498 | const AVFilter ff_vf_cropdetect = { | ||
499 | .name = "cropdetect", | ||
500 | .description = NULL_IF_CONFIG_SMALL("Auto-detect crop size."), | ||
501 | .priv_size = sizeof(CropDetectContext), | ||
502 | .priv_class = &cropdetect_class, | ||
503 | .init = init, | ||
504 | .uninit = uninit, | ||
505 | FILTER_INPUTS(avfilter_vf_cropdetect_inputs), | ||
506 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
507 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
508 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_METADATA_ONLY, | ||
509 | .process_command = process_command, | ||
510 | }; | ||
511 |