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