Support single input for guided filter by adding guidance mode. If the guidance mode is off, single input is required. And edge-preserving smoothing is conducted. If the mode is on, two inputs are needed. The second input serves as the guidance. For this mode, more tasks are supported, such as detail enhancement, dehazing and so on.
Showing the changes to libavfilter (1 of 2 files). Show all
Unified · Split
| @@ -22,6 +22,7 @@ · 21 unmodified lines | |||
| 22 | #include "libavutil/opt.h" | 22 | #include "libavutil/opt.h" |
| 23 | #include "libavutil/pixdesc.h" | 23 | #include "libavutil/pixdesc.h" |
| 24 | #include "avfilter.h" | 24 | #include "avfilter.h" |
| 25 | #include "filters.h" | ||
| 25 | #include "formats.h" | 26 | #include "formats.h" |
| 26 | #include "framesync.h" | 27 | #include "framesync.h" |
| 27 | #include "internal.h" | 28 | #include "internal.h" |
| @@ -33,6 +34,12 @@ enum FilterModes { · 5 unmodified lines | |||
| 33 | NB_MODES, | 34 | NB_MODES, |
| 34 | }; | 35 | }; |
| 35 | 36 | ||
| 37 | enum GuidanceModes { | ||
| 38 | OFF, | ||
| 39 | ON, | ||
| 40 | NB_GUIDANCE_MODES, | ||
| 41 | }; | ||
| 42 | |||
| 36 | typedef struct GuidedContext { | 43 | typedef struct GuidedContext { |
| 37 | const AVClass *class; | 44 | const AVClass *class; |
| 38 | FFFrameSync fs; | 45 | FFFrameSync fs; |
| @@ -41,7 +48,7 @@ typedef struct GuidedContext { · 2 unmodified lines | |||
| 41 | float eps; | 48 | float eps; |
| 42 | int mode; | 49 | int mode; |
| 43 | int sub; | 50 | int sub; |
| 44 | 51 | int guidance; | |
| 45 | int planes; | 52 | int planes; |
| 46 | 53 | ||
| 47 | int width; | 54 | int width; |
| @@ -59,13 +66,16 @@ typedef struct GuidedContext { · 11 unmodified lines | |||
| 59 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM | 66 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM |
| 60 | 67 | ||
| 61 | static const AVOption guided_options[] = { | 68 | static const AVOption guided_options[] = { |
| 62 | { "radius", "set the box radius", OFFSET(radius), AV_OPT_TYPE_INT, {.i64 = 3 }, 1, 20, FLAGS }, | 69 | { "radius", "set the box radius", OFFSET(radius), AV_OPT_TYPE_INT, {.i64 = 3 }, 1, 20, FLAGS }, |
| 63 | { "eps", "set the regularization parameter (with square)", OFFSET(eps), AV_OPT_TYPE_FLOAT, {.dbl = 0.01 }, 0.0, 1, FLAGS }, | 70 | { "eps", "set the regularization parameter (with square)", OFFSET(eps), AV_OPT_TYPE_FLOAT, {.dbl = 0.01 }, 0.0, 1, FLAGS }, |
| 64 | { "mode", "set filtering mode (0: basic mode; 1: fast mode)", OFFSET(mode), AV_OPT_TYPE_INT, {.i64 = BASIC}, BASIC, NB_MODES - 1, FLAGS, "mode" }, | 71 | { "mode", "set filtering mode (0: basic mode; 1: fast mode)", OFFSET(mode), AV_OPT_TYPE_INT, {.i64 = BASIC}, BASIC, NB_MODES - 1, FLAGS, "mode" }, |
| 65 | { "basic", "basic guided filter", 0, AV_OPT_TYPE_CONST, {.i64 = BASIC}, 0, 0, FLAGS, "mode" }, | 72 | { "basic", "basic guided filter", 0, AV_OPT_TYPE_CONST, {.i64 = BASIC}, 0, 0, FLAGS, "mode" }, |
| 66 | { "fast", "fast guided filter", 0, AV_OPT_TYPE_CONST, {.i64 = FAST }, 0, 0, FLAGS, "mode" }, | 73 | { "fast", "fast guided filter", 0, AV_OPT_TYPE_CONST, {.i64 = FAST }, 0, 0, FLAGS, "mode" }, |
| 67 | { "sub", "subsampling ratio for fast mode", OFFSET(sub), AV_OPT_TYPE_INT, {.i64 = 4 }, 2, 64, FLAGS }, | 74 | { "sub", "subsampling ratio for fast mode", OFFSET(sub), AV_OPT_TYPE_INT, {.i64 = 4 }, 2, 64, FLAGS }, |
| 68 | { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=1 }, 0, 0xF, FLAGS }, | 75 | { "guidance", "set guidance mode (0: off mode; 1: on mode)", OFFSET(guidance), AV_OPT_TYPE_INT, {.i64 = OFF }, OFF, NB_GUIDANCE_MODES - 1, FLAGS, "guidance" }, |
| 76 | { "off", "only one input is enabled", 0, AV_OPT_TYPE_CONST, {.i64 = OFF }, 0, 0, FLAGS, "guidance" }, | ||
| 77 | { "on", "two inputs are required", 0, AV_OPT_TYPE_CONST, {.i64 = ON }, 0, 0, FLAGS, "guidance" }, | ||
| 78 | { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64 = 1 }, 0, 0xF, FLAGS }, | ||
| 69 | { NULL } | 79 | { NULL } |
| 70 | }; | 80 | }; |
| 71 | 81 | ||
| @@ -149,16 +159,6 @@ static int config_input(AVFilterLink *inlink) · 77 unmodified lines | |||
| 149 | GuidedContext *s = ctx->priv; | 159 | GuidedContext *s = ctx->priv; |
| 150 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | 160 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
| 151 | 161 | ||
| 152 | if (ctx->inputs[0]->w != ctx->inputs[1]->w || | ||
| 153 | ctx->inputs[0]->h != ctx->inputs[1]->h) { | ||
| 154 | av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n"); | ||
| 155 | return AVERROR(EINVAL); | ||
| 156 | } | ||
| 157 | if (ctx->inputs[0]->format != ctx->inputs[1]->format) { | ||
| 158 | av_log(ctx, AV_LOG_ERROR, "Inputs must be of same pixel format.\n"); | ||
| 159 | return AVERROR(EINVAL); | ||
| 160 | } | ||
| 161 | |||
| 162 | if (s->mode == BASIC) { | 162 | if (s->mode == BASIC) { |
| 163 | s->sub = 1; | 163 | s->sub = 1; |
| 164 | } | 164 | } |
| @@ -230,7 +230,7 @@ static int guided_##name(AVFilterContext *ctx, GuidedContext *s, · 65 unmodified lines | |||
| 230 | meanB = av_calloc(w * h, sizeof(float)); \ | 230 | meanB = av_calloc(w * h, sizeof(float)); \ |
| 231 | \ | 231 | \ |
| 232 | if (!I || !II || !P || !IP || !meanI || !meanII || !meanP || \ | 232 | if (!I || !II || !P || !IP || !meanI || !meanII || !meanP || \ |
| 233 | !meanIP || !A || !B || !meanA || !meanB){ \ | 233 | !meanIP || !A || !B || !meanA || !meanB) { \ |
| 234 | ret = AVERROR(ENOMEM); \ | 234 | ret = AVERROR(ENOMEM); \ |
| 235 | goto end; \ | 235 | goto end; \ |
| 236 | } \ | 236 | } \ |
| @@ -304,47 +304,54 @@ end: · 67 unmodified lines | |||
| 304 | GUIDED(uint8_t, byte) | 304 | GUIDED(uint8_t, byte) |
| 305 | GUIDED(uint16_t, word) | 305 | GUIDED(uint16_t, word) |
| 306 | 306 | ||
| 307 | static int process_frame(FFFrameSync *fs) | 307 | static int filter_frame(AVFilterContext *ctx, AVFrame **out, AVFrame *in, AVFrame *ref) |
| 308 | { | 308 | { |
| 309 | AVFilterContext *ctx = fs->parent; | 309 | GuidedContext *s = ctx->priv; |
| 310 | GuidedContext *s = fs->opaque; | ||
| 311 | AVFilterLink *outlink = ctx->outputs[0]; | 310 | AVFilterLink *outlink = ctx->outputs[0]; |
| 312 | AVFrame *out_frame = NULL, *main_frame = NULL, *ref_frame = NULL; | 311 | *out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| 313 | int ret; | 312 | if (!*out) |
| 314 | |||
| 315 | ret = ff_framesync_dualinput_get(fs, &main_frame, &ref_frame); | ||
| 316 | if (ret < 0) | ||
| 317 | return ret; | ||
| 318 | |||
| 319 | out_frame = ff_get_video_buffer(outlink, outlink->w, outlink->h); | ||
| 320 | if (!out_frame) { | ||
| 321 | av_frame_free(&main_frame); | ||
| 322 | return AVERROR(ENOMEM); | 313 | return AVERROR(ENOMEM); |
| 323 | } | 314 | av_frame_copy_props(*out, in); |
| 324 | av_frame_copy_props(out_frame, main_frame); | ||
| 325 | 315 | ||
| 326 | for (int plane = 0; plane < s->nb_planes; plane++) { | 316 | for (int plane = 0; plane < s->nb_planes; plane++) { |
| 327 | if (!(s->planes & (1 << plane))) { | 317 | if (!(s->planes & (1 << plane))) { |
| 328 | av_image_copy_plane(out_frame->data[plane], out_frame->linesize[plane], | 318 | av_image_copy_plane((*out)->data[plane], (*out)->linesize[plane], |
| 329 | main_frame->data[plane], main_frame->linesize[plane], | 319 | in->data[plane], in->linesize[plane], |
| 330 | s->planewidth[plane] * ((s->depth + 7) / 8), s->planeheight[plane]); | 320 | s->planewidth[plane] * ((s->depth + 7) / 8), s->planeheight[plane]); |
| 331 | continue; | 321 | continue; |
| 332 | } | 322 | } |
| 333 | if (s->depth <= 8) | 323 | if (s->depth <= 8) |
| 334 | guided_byte(ctx, s, main_frame->data[plane], ref_frame->data[plane], out_frame->data[plane], s->radius, s->eps, | 324 | guided_byte(ctx, s, in->data[plane], ref->data[plane], (*out)->data[plane], s->radius, s->eps, |
| 335 | s->planewidth[plane], s->planeheight[plane], | 325 | s->planewidth[plane], s->planeheight[plane], |
| 336 | main_frame->linesize[plane], ref_frame->linesize[plane], out_frame->linesize[plane], (1 << s->depth) - 1.f); | 326 | in->linesize[plane], ref->linesize[plane], (*out)->linesize[plane], (1 << s->depth) - 1.f); |
| 337 | else | 327 | else |
| 338 | guided_word(ctx, s, main_frame->data[plane], ref_frame->data[plane], out_frame->data[plane], s->radius, s->eps, | 328 | guided_word(ctx, s, in->data[plane], ref->data[plane], (*out)->data[plane], s->radius, s->eps, |
| 339 | s->planewidth[plane], s->planeheight[plane], | 329 | s->planewidth[plane], s->planeheight[plane], |
| 340 | main_frame->linesize[plane] / 2, ref_frame->linesize[plane] / 2, out_frame->linesize[plane] / 2, (1 << s->depth) - 1.f); | 330 | in->linesize[plane] / 2, ref->linesize[plane] / 2, (*out)->linesize[plane] / 2, (1 << s->depth) - 1.f); |
| 331 | } | ||
| 332 | |||
| 333 | return 0; | ||
| 334 | } | ||
| 335 | |||
| 336 | static int process_frame(FFFrameSync *fs) | ||
| 337 | { | ||
| 338 | AVFilterContext *ctx = fs->parent; | ||
| 339 | AVFilterLink *outlink = ctx->outputs[0]; | ||
| 340 | AVFrame *out_frame = NULL, *main_frame = NULL, *ref_frame = NULL; | ||
| 341 | int ret; | ||
| 342 | ret = ff_framesync_dualinput_get(fs, &main_frame, &ref_frame); | ||
| 343 | if (ret < 0) | ||
| 344 | return ret; | ||
| 345 | |||
| 346 | ret = filter_frame(ctx, &out_frame, main_frame, ref_frame); | ||
| 347 | if (ret < 0) { | ||
| 348 | return ret; | ||
| 341 | } | 349 | } |
| 342 | av_frame_free(&main_frame); | 350 | av_frame_free(&main_frame); |
| 343 | 351 | ||
| 344 | return ff_filter_frame(outlink, out_frame); | 352 | return ff_filter_frame(outlink, out_frame); |
| 345 | } | 353 | } |
| 346 | 354 | ||
| 347 | |||
| 348 | static int config_output(AVFilterLink *outlink) | 355 | static int config_output(AVFilterLink *outlink) |
| 349 | { | 356 | { |
| 350 | AVFilterContext *ctx = outlink->src; | 357 | AVFilterContext *ctx = outlink->src; |
| @@ -354,12 +361,27 @@ static int config_output(AVFilterLink *outlink) · 3 unmodified lines | |||
| 354 | FFFrameSyncIn *in; | 361 | FFFrameSyncIn *in; |
| 355 | int ret; | 362 | int ret; |
| 356 | 363 | ||
| 364 | if (s->guidance == ON) { | ||
| 365 | if (ctx->inputs[0]->w != ctx->inputs[1]->w || | ||
| 366 | ctx->inputs[0]->h != ctx->inputs[1]->h) { | ||
| 367 | av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n"); | ||
| 368 | return AVERROR(EINVAL); | ||
| 369 | } | ||
| 370 | if (ctx->inputs[0]->format != ctx->inputs[1]->format) { | ||
| 371 | av_log(ctx, AV_LOG_ERROR, "Inputs must be of same pixel format.\n"); | ||
| 372 | return AVERROR(EINVAL); | ||
| 373 | } | ||
| 374 | } | ||
| 357 | 375 | ||
| 358 | outlink->w = mainlink->w; | 376 | outlink->w = mainlink->w; |
| 359 | outlink->h = mainlink->h; | 377 | outlink->h = mainlink->h; |
| 360 | outlink->time_base = mainlink->time_base; | 378 | outlink->time_base = mainlink->time_base; |
| 361 | outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio; | 379 | outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio; |
| 362 | outlink->frame_rate = mainlink->frame_rate; | 380 | outlink->frame_rate = mainlink->frame_rate; |
| 381 | |||
| 382 | if (s->guidance == OFF) | ||
| 383 | return 0; | ||
| 384 | |||
| 363 | if ((ret = ff_framesync_init(&s->fs, ctx, 2)) < 0) | 385 | if ((ret = ff_framesync_init(&s->fs, ctx, 2)) < 0) |
| 364 | return ret; | 386 | return ret; |
| 365 | 387 | ||
| @@ -383,22 +405,66 @@ static int config_output(AVFilterLink *outlink) · 17 unmodified lines | |||
| 383 | static int activate(AVFilterContext *ctx) | 405 | static int activate(AVFilterContext *ctx) |
| 384 | { | 406 | { |
| 385 | GuidedContext *s = ctx->priv; | 407 | GuidedContext *s = ctx->priv; |
| 386 | return ff_framesync_activate(&s->fs); | 408 | AVFrame *frame = NULL; |
| 409 | AVFrame *out = NULL; | ||
| 410 | int ret, status; | ||
| 411 | int64_t pts; | ||
| 412 | if (s->guidance) | ||
| 413 | return ff_framesync_activate(&s->fs); | ||
| 414 | |||
| 415 | FF_FILTER_FORWARD_STATUS_BACK(ctx->outputs[0], ctx->inputs[0]); | ||
| 416 | |||
| 417 | if ((ret = ff_inlink_consume_frame(ctx->inputs[0], &frame)) > 0) { | ||
| 418 | ret = filter_frame(ctx, &out, frame, frame); | ||
| 419 | av_frame_free(&frame); | ||
| 420 | if (ret < 0) | ||
| 421 | return ret; | ||
| 422 | ret = ff_filter_frame(ctx->outputs[0], out); | ||
| 423 | } | ||
| 424 | if (ret < 0) | ||
| 425 | return ret; | ||
| 426 | if (ff_inlink_acknowledge_status(ctx->inputs[0], &status, &pts)) { | ||
| 427 | ff_outlink_set_status(ctx->outputs[0], status, pts); | ||
| 428 | return 0; | ||
| 429 | } | ||
| 430 | if (ff_outlink_frame_wanted(ctx->outputs[0])) | ||
| 431 | ff_inlink_request_frame(ctx->inputs[0]); | ||
| 432 | return 0; | ||
| 387 | } | 433 | } |
| 388 | 434 | ||
| 389 | static av_cold int init(AVFilterContext *ctx) | 435 | static av_cold int init(AVFilterContext *ctx) |
| 390 | { | 436 | { |
| 437 | GuidedContext *s = ctx->priv; | ||
| 438 | AVFilterPad pad = { 0 }; | ||
| 439 | int ret; | ||
| 440 | |||
| 441 | pad.type = AVMEDIA_TYPE_VIDEO; | ||
| 442 | pad.name = "source"; | ||
| 443 | pad.config_props = config_input; | ||
| 444 | |||
| 445 | if ((ret = ff_insert_inpad(ctx, 0, &pad)) < 0) | ||
| 446 | return ret; | ||
| 447 | |||
| 448 | if (s->guidance == ON) { | ||
| 449 | pad.type = AVMEDIA_TYPE_VIDEO; | ||
| 450 | pad.name = "guidance"; | ||
| 451 | pad.config_props = NULL; | ||
| 452 | |||
| 453 | if ((ret = ff_insert_inpad(ctx, 1, &pad)) < 0) | ||
| 454 | return ret; | ||
| 455 | } | ||
| 456 | |||
| 391 | return 0; | 457 | return 0; |
| 392 | } | 458 | } |
| 393 | 459 | ||
| 394 | static av_cold void uninit(AVFilterContext *ctx) | 460 | static av_cold void uninit(AVFilterContext *ctx) |
| 395 | { | 461 | { |
| 396 | GuidedContext *s = ctx->priv; | 462 | GuidedContext *s = ctx->priv; |
| 397 | ff_framesync_uninit(&s->fs); | 463 | if (s->guidance == ON) |
| 464 | ff_framesync_uninit(&s->fs); | ||
| 398 | return; | 465 | return; |
| 399 | } | 466 | } |
| 400 | 467 | ||
| 401 | |||
| 402 | static int process_command(AVFilterContext *ctx, | 468 | static int process_command(AVFilterContext *ctx, |
| 403 | const char *cmd, | 469 | const char *cmd, |
| 404 | const char *arg, | 470 | const char *arg, |
| @@ -414,18 +480,6 @@ static int process_command(AVFilterContext *ctx, · 9 unmodified lines | |||
| 414 | return 0; | 480 | return 0; |
| 415 | } | 481 | } |
| 416 | 482 | ||
| 417 | static const AVFilterPad guided_inputs[] = { | ||
| 418 | { | ||
| 419 | .name = "main", | ||
| 420 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 421 | },{ | ||
| 422 | .name = "reference", | ||
| 423 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 424 | .config_props = config_input, | ||
| 425 | }, | ||
| 426 | { NULL } | ||
| 427 | }; | ||
| 428 | |||
| 429 | static const AVFilterPad guided_outputs[] = { | 483 | static const AVFilterPad guided_outputs[] = { |
| 430 | { | 484 | { |
| 431 | .name = "default", | 485 | .name = "default", |
| @@ -444,7 +498,7 @@ const AVFilter ff_vf_guided = { · 12 unmodified lines | |||
| 444 | .priv_size = sizeof(GuidedContext), | 498 | .priv_size = sizeof(GuidedContext), |
| 445 | .priv_class = &guided_class, | 499 | .priv_class = &guided_class, |
| 446 | .activate = activate, | 500 | .activate = activate, |
| 447 | .inputs = guided_inputs, | 501 | .inputs = NULL, |
| 448 | .outputs = guided_outputs, | 502 | .outputs = guided_outputs, |
| 449 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, | 503 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, |
| 450 | .process_command = process_command, | 504 | .process_command = process_command, |