avfilter/interlace: add complex vertical low-pass filter

This complex (-1 2 6 2 -1) filter slightly less reduces interlace 'twitter' but better retain detail and subjective sharpness impression compared to the linear (1 2 1) filter.

Signed-off-by: Thomas Mundt <tmundt75@gmail.com>
Signed-off-by: James Almer <jamrial@gmail.com>
This commit is contained in:
Thomas Mundt 2017-04-20 23:33:39 +02:00 committed by James Almer
parent 20da413502
commit 2da5bf4c2f
10 changed files with 246 additions and 24 deletions

View File

@ -9158,8 +9158,21 @@ This determines whether the interlaced frame is taken from the even
(tff - default) or odd (bff) lines of the progressive frame.
@item lowpass
Enable (default) or disable the vertical lowpass filter to avoid twitter
interlacing and reduce moire patterns.
Vertical lowpass filter to avoid twitter interlacing and
reduce moire patterns.
@table @samp
@item 0, off
Disable vertical lowpass filter
@item 1, linear
Enable linear filter (default)
@item 2, complex
Enable complex filter. This will slightly less reduce twitter and moire
but better retain detail and subjective sharpness impression.
@end table
@end table
@section kerndeint
@ -13857,16 +13870,22 @@ Available value for @var{flags} is:
@table @option
@item low_pass_filter, vlfp
Enable vertical low-pass filtering in the filter.
Enable linear vertical low-pass filtering in the filter.
Vertical low-pass filtering is required when creating an interlaced
destination from a progressive source which contains high-frequency
vertical detail. Filtering will reduce interlace 'twitter' and Moire
patterning.
@item complex_filter, cvlfp
Enable complex vertical low-pass filtering.
This will slightly less reduce interlace 'twitter' and Moire
patterning but better retain detail and subjective sharpness impression.
@end table
Vertical low-pass filtering can only be enabled for @option{mode}
@var{interleave_top} and @var{interleave_bottom}.
@end table
@end table
@section transpose

View File

@ -44,6 +44,12 @@ enum FieldType {
FIELD_LOWER = 1,
};
enum VLPFilter {
VLPF_OFF = 0,
VLPF_LIN = 1,
VLPF_CMP = 2,
};
typedef struct InterlaceContext {
const AVClass *class;
enum ScanMode scan; // top or bottom field first scanning

View File

@ -30,6 +30,10 @@
#include "libavutil/opt.h"
#include "avfilter.h"
#define TINTERLACE_FLAG_VLPF 01
#define TINTERLACE_FLAG_EXACT_TB 2
#define TINTERLACE_FLAG_CVLPF 4
enum TInterlaceMode {
MODE_MERGE = 0,
MODE_DROP_EVEN,

View File

@ -3,6 +3,7 @@
* Copyright (c) 2010 Baptiste Coudurier
* Copyright (c) 2011 Stefano Sabatini
* Copyright (c) 2013 Vittorio Giovara <vittorio.giovara@gmail.com>
* Copyright (c) 2017 Thomas Mundt <tmundt75@gmail.com>
*
* This file is part of FFmpeg.
*
@ -47,7 +48,13 @@ static const AVOption interlace_options[] = {
{ "bff", "bottom field first", 0,
AV_OPT_TYPE_CONST, {.i64 = MODE_BFF }, INT_MIN, INT_MAX, .flags = FLAGS, .unit = "scan" },
{ "lowpass", "set vertical low-pass filter", OFFSET(lowpass),
AV_OPT_TYPE_BOOL, {.i64 = 1 }, 0, 1, .flags = FLAGS },
AV_OPT_TYPE_INT, {.i64 = VLPF_LIN }, 0, 2, .flags = FLAGS, .unit = "lowpass" },
{ "off", "disable vertical low-pass filter", 0,
AV_OPT_TYPE_CONST, {.i64 = VLPF_OFF }, INT_MIN, INT_MAX, .flags = FLAGS, .unit = "lowpass" },
{ "linear", "linear vertical low-pass filter", 0,
AV_OPT_TYPE_CONST, {.i64 = VLPF_LIN }, INT_MIN, INT_MAX, .flags = FLAGS, .unit = "lowpass" },
{ "complex", "complex vertical low-pass filter", 0,
AV_OPT_TYPE_CONST, {.i64 = VLPF_CMP }, INT_MIN, INT_MAX, .flags = FLAGS, .unit = "lowpass" },
{ NULL }
};
@ -68,6 +75,25 @@ static void lowpass_line_c(uint8_t *dstp, ptrdiff_t linesize,
}
}
static void lowpass_line_complex_c(uint8_t *dstp, ptrdiff_t linesize,
const uint8_t *srcp,
ptrdiff_t mref, ptrdiff_t pref)
{
const uint8_t *srcp_above = srcp + mref;
const uint8_t *srcp_below = srcp + pref;
const uint8_t *srcp_above2 = srcp + mref * 2;
const uint8_t *srcp_below2 = srcp + pref * 2;
int i;
for (i = 0; i < linesize; i++) {
// this calculation is an integer representation of
// '0.75 * current + 0.25 * above + 0.25 * below - 0.125 * above2 - 0.125 * below2'
// '4 +' is for rounding.
dstp[i] = av_clip_uint8((4 + (srcp[i] << 2)
+ ((srcp[i] + srcp_above[i] + srcp_below[i]) << 1)
- srcp_above2[i] - srcp_below2[i]) >> 3);
}
}
static const enum AVPixelFormat formats_supported[] = {
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P,
AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUVA420P,
@ -117,7 +143,10 @@ static int config_out_props(AVFilterLink *outlink)
if (s->lowpass) {
s->lowpass_line = lowpass_line_c;
if (s->lowpass == VLPF_LIN)
s->lowpass_line = lowpass_line_c;
else if (s->lowpass == VLPF_CMP)
s->lowpass_line = lowpass_line_complex_c;
if (ARCH_X86)
ff_interlace_init_x86(s);
}
@ -151,7 +180,7 @@ static void copy_picture_field(InterlaceContext *s,
srcp += src_frame->linesize[plane];
dstp += dst_frame->linesize[plane];
}
if (lowpass) {
if (lowpass == VLPF_LIN) {
int srcp_linesize = src_frame->linesize[plane] * 2;
int dstp_linesize = dst_frame->linesize[plane] * 2;
for (j = lines; j > 0; j--) {
@ -165,6 +194,20 @@ static void copy_picture_field(InterlaceContext *s,
dstp += dstp_linesize;
srcp += srcp_linesize;
}
} else if (lowpass == VLPF_CMP) {
int srcp_linesize = src_frame->linesize[plane] * 2;
int dstp_linesize = dst_frame->linesize[plane] * 2;
for (j = lines; j > 0; j--) {
ptrdiff_t pref = src_frame->linesize[plane];
ptrdiff_t mref = -pref;
if (j >= (lines - 1))
mref = 0;
else if (j <= 2)
pref = 0;
s->lowpass_line(dstp, cols, srcp, mref, pref);
dstp += dstp_linesize;
srcp += srcp_linesize;
}
} else {
av_image_copy_plane(dstp, dst_frame->linesize[plane] * 2,
srcp, src_frame->linesize[plane] * 2,

View File

@ -1,4 +1,5 @@
/*
* Copyright (c) 2017 Thomas Mundt <tmundt75@gmail.com>
* Copyright (c) 2011 Stefano Sabatini
* Copyright (c) 2010 Baptiste Coudurier
* Copyright (c) 2003 Michael Zucchi <notzed@ximian.com>
@ -34,8 +35,6 @@
#define OFFSET(x) offsetof(TInterlaceContext, x)
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
#define TINTERLACE_FLAG_VLPF 01
#define TINTERLACE_FLAG_EXACT_TB 2
static const AVOption tinterlace_options[] = {
{"mode", "select interlace mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=MODE_MERGE}, 0, MODE_NB-1, FLAGS, "mode"},
@ -51,6 +50,8 @@ static const AVOption tinterlace_options[] = {
{"flags", "set flags", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0}, 0, INT_MAX, 0, "flags" },
{"low_pass_filter", "enable vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = TINTERLACE_FLAG_VLPF}, INT_MIN, INT_MAX, FLAGS, "flags" },
{"vlpf", "enable vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = TINTERLACE_FLAG_VLPF}, INT_MIN, INT_MAX, FLAGS, "flags" },
{"complex_filter", "enable complex vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = TINTERLACE_FLAG_CVLPF},INT_MIN, INT_MAX, FLAGS, "flags" },
{"cvlpf", "enable complex vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = TINTERLACE_FLAG_CVLPF},INT_MIN, INT_MAX, FLAGS, "flags" },
{"exact_tb", "force a timebase which can represent timestamps exactly", 0, AV_OPT_TYPE_CONST, {.i64 = TINTERLACE_FLAG_EXACT_TB}, INT_MIN, INT_MAX, FLAGS, "flags" },
{NULL}
@ -102,6 +103,24 @@ static void lowpass_line_c(uint8_t *dstp, ptrdiff_t width, const uint8_t *srcp,
}
}
static void lowpass_line_complex_c(uint8_t *dstp, ptrdiff_t width, const uint8_t *srcp,
ptrdiff_t mref, ptrdiff_t pref)
{
const uint8_t *srcp_above = srcp + mref;
const uint8_t *srcp_below = srcp + pref;
const uint8_t *srcp_above2 = srcp + mref * 2;
const uint8_t *srcp_below2 = srcp + pref * 2;
int i;
for (i = 0; i < width; i++) {
// this calculation is an integer representation of
// '0.75 * current + 0.25 * above + 0.25 * below - 0.125 * above2 - 0.125 * below2'
// '4 +' is for rounding.
dstp[i] = av_clip_uint8((4 + (srcp[i] << 2)
+ ((srcp[i] + srcp_above[i] + srcp_below[i]) << 1)
- srcp_above2[i] - srcp_below2[i]) >> 3);
}
}
static av_cold void uninit(AVFilterContext *ctx)
{
TInterlaceContext *tinterlace = ctx->priv;
@ -144,12 +163,14 @@ static int config_out_props(AVFilterLink *outlink)
tinterlace->black_linesize[i] * h);
}
}
if ((tinterlace->flags & TINTERLACE_FLAG_VLPF)
if ((tinterlace->flags & TINTERLACE_FLAG_VLPF
|| tinterlace->flags & TINTERLACE_FLAG_CVLPF)
&& !(tinterlace->mode == MODE_INTERLEAVE_TOP
|| tinterlace->mode == MODE_INTERLEAVE_BOTTOM)) {
av_log(ctx, AV_LOG_WARNING, "low_pass_filter flag ignored with mode %d\n",
av_log(ctx, AV_LOG_WARNING, "low_pass_filter flags ignored with mode %d\n",
tinterlace->mode);
tinterlace->flags &= ~TINTERLACE_FLAG_VLPF;
tinterlace->flags &= ~TINTERLACE_FLAG_CVLPF;
}
tinterlace->preout_time_base = inlink->time_base;
if (tinterlace->mode == MODE_INTERLACEX2) {
@ -172,14 +193,19 @@ static int config_out_props(AVFilterLink *outlink)
(tinterlace->flags & TINTERLACE_FLAG_EXACT_TB))
outlink->time_base = tinterlace->preout_time_base;
if (tinterlace->flags & TINTERLACE_FLAG_VLPF) {
if (tinterlace->flags & TINTERLACE_FLAG_CVLPF) {
tinterlace->lowpass_line = lowpass_line_complex_c;
if (ARCH_X86)
ff_tinterlace_init_x86(tinterlace);
} else if (tinterlace->flags & TINTERLACE_FLAG_VLPF) {
tinterlace->lowpass_line = lowpass_line_c;
if (ARCH_X86)
ff_tinterlace_init_x86(tinterlace);
}
av_log(ctx, AV_LOG_VERBOSE, "mode:%d filter:%s h:%d -> h:%d\n",
tinterlace->mode, (tinterlace->flags & TINTERLACE_FLAG_VLPF) ? "on" : "off",
av_log(ctx, AV_LOG_VERBOSE, "mode:%d filter:%s h:%d -> h:%d\n", tinterlace->mode,
(tinterlace->flags & TINTERLACE_FLAG_CVLPF) ? "complex" :
(tinterlace->flags & TINTERLACE_FLAG_VLPF) ? "linear" : "off",
inlink->h, outlink->h);
return 0;
@ -223,10 +249,23 @@ void copy_picture_field(TInterlaceContext *tinterlace,
srcp += src_linesize[plane];
if (interleave && dst_field == FIELD_LOWER)
dstp += dst_linesize[plane];
if (flags & TINTERLACE_FLAG_VLPF) {
// Low-pass filtering is required when creating an interlaced destination from
// a progressive source which contains high-frequency vertical detail.
// Filtering will reduce interlace 'twitter' and Moire patterning.
// Low-pass filtering is required when creating an interlaced destination from
// a progressive source which contains high-frequency vertical detail.
// Filtering will reduce interlace 'twitter' and Moire patterning.
if (flags & TINTERLACE_FLAG_CVLPF) {
int srcp_linesize = src_linesize[plane] * k;
int dstp_linesize = dst_linesize[plane] * (interleave ? 2 : 1);
for (h = lines; h > 0; h--) {
ptrdiff_t pref = src_linesize[plane];
ptrdiff_t mref = -pref;
if (h >= (lines - 1)) mref = 0;
else if (h <= 2) pref = 0;
tinterlace->lowpass_line(dstp, cols, srcp, mref, pref);
dstp += dstp_linesize;
srcp += srcp_linesize;
}
} else if (flags & TINTERLACE_FLAG_VLPF) {
int srcp_linesize = src_linesize[plane] * k;
int dstp_linesize = dst_linesize[plane] * (interleave ? 2 : 1);
for (h = lines; h > 0; h--) {

View File

@ -3,6 +3,7 @@
;*
;* Copyright (C) 2014 Kieran Kunhya <kierank@obe.tv>
;* Copyright (c) 2014 Michael Niedermayer <michaelni@gmx.at>
;* Copyright (c) 2017 Thomas Mundt <tmundt75@gmail.com>
;*
;* This file is part of FFmpeg.
;*
@ -25,6 +26,8 @@
SECTION_RODATA
pw_4: times 8 dw 4
SECTION .text
%macro LOWPASS_LINE 0
@ -56,6 +59,60 @@ cglobal lowpass_line, 5, 5, 7, dst, h, src, mref, pref
add hq, 2*mmsize
jl .loop
REP_RET
%endmacro
%macro LOWPASS_LINE_COMPLEX 0
cglobal lowpass_line_complex, 5, 5, 7, dst, h, src, mref, pref
pxor m6, m6
.loop:
mova m0, [srcq+mrefq]
mova m2, [srcq+prefq]
mova m1, m0
mova m3, m2
punpcklbw m0, m6
punpcklbw m2, m6
punpckhbw m1, m6
punpckhbw m3, m6
paddw m0, m2
paddw m1, m3
mova m2, [srcq+mrefq*2]
mova m4, [srcq+prefq*2]
mova m3, m2
mova m5, m4
punpcklbw m2, m6
punpcklbw m4, m6
punpckhbw m3, m6
punpckhbw m5, m6
paddw m2, m4
paddw m3, m5
mova m4, [srcq]
mova m5, m4
punpcklbw m4, m6
punpckhbw m5, m6
paddw m0, m4
paddw m1, m5
psllw m0, 1
psllw m1, 1
psllw m4, 2
psllw m5, 2
paddw m0, m4
paddw m1, m5
paddw m0, [pw_4]
paddw m1, [pw_4]
psubusw m0, m2
psubusw m1, m3
psrlw m0, 3
psrlw m1, 3
packuswb m0, m1
mova [dstq], m0
add dstq, mmsize
add srcq, mmsize
sub hd, mmsize
jg .loop
REP_RET
%endmacro
INIT_XMM sse2
@ -63,3 +120,6 @@ LOWPASS_LINE
INIT_XMM avx
LOWPASS_LINE
INIT_XMM sse2
LOWPASS_LINE_COMPLEX

View File

@ -33,12 +33,21 @@ void ff_lowpass_line_avx (uint8_t *dstp, ptrdiff_t linesize,
const uint8_t *srcp,
ptrdiff_t mref, ptrdiff_t pref);
void ff_lowpass_line_complex_sse2(uint8_t *dstp, ptrdiff_t linesize,
const uint8_t *srcp,
ptrdiff_t mref, ptrdiff_t pref);
av_cold void ff_interlace_init_x86(InterlaceContext *s)
{
int cpu_flags = av_get_cpu_flags();
if (EXTERNAL_SSE2(cpu_flags))
s->lowpass_line = ff_lowpass_line_sse2;
if (EXTERNAL_SSE2(cpu_flags)) {
if (s->lowpass == VLPF_LIN)
s->lowpass_line = ff_lowpass_line_sse2;
else if (s->lowpass == VLPF_CMP)
s->lowpass_line = ff_lowpass_line_complex_sse2;
}
if (EXTERNAL_AVX(cpu_flags))
s->lowpass_line = ff_lowpass_line_avx;
if (s->lowpass == VLPF_LIN)
s->lowpass_line = ff_lowpass_line_avx;
}

View File

@ -34,12 +34,21 @@ void ff_lowpass_line_avx (uint8_t *dstp, ptrdiff_t linesize,
const uint8_t *srcp,
ptrdiff_t mref, ptrdiff_t pref);
void ff_lowpass_line_complex_sse2(uint8_t *dstp, ptrdiff_t linesize,
const uint8_t *srcp,
ptrdiff_t mref, ptrdiff_t pref);
av_cold void ff_tinterlace_init_x86(TInterlaceContext *s)
{
int cpu_flags = av_get_cpu_flags();
if (EXTERNAL_SSE2(cpu_flags))
s->lowpass_line = ff_lowpass_line_sse2;
if (EXTERNAL_SSE2(cpu_flags)) {
if (!(s->flags & TINTERLACE_FLAG_CVLPF))
s->lowpass_line = ff_lowpass_line_sse2;
else
s->lowpass_line = ff_lowpass_line_complex_sse2;
}
if (EXTERNAL_AVX(cpu_flags))
s->lowpass_line = ff_lowpass_line_avx;
if (!(s->flags & TINTERLACE_FLAG_CVLPF))
s->lowpass_line = ff_lowpass_line_avx;
}

View File

@ -123,6 +123,9 @@ fate-filter-hqdn3d: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf hqdn3d
FATE_FILTER_VSYNTH-$(CONFIG_INTERLACE_FILTER) += fate-filter-interlace
fate-filter-interlace: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf interlace
FATE_FILTER_VSYNTH-$(CONFIG_INTERLACE_FILTER) += fate-filter-interlace-complex
fate-filter-interlace-complex: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf interlace=lowpass=complex
FATE_FILTER_VSYNTH-$(call ALLYES, NEGATE_FILTER PERMS_FILTER) += fate-filter-negate
fate-filter-negate: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf perms=random,negate

View File

@ -0,0 +1,30 @@
#tb 0: 2/25
#media_type 0: video
#codec_id 0: rawvideo
#dimensions 0: 352x288
#sar 0: 0/1
0, 0, 0, 1, 152064, 0x91290ae6
0, 1, 1, 1, 152064, 0x24f34baf
0, 2, 2, 1, 152064, 0x799fc436
0, 3, 3, 1, 152064, 0xfe42c0a9
0, 4, 4, 1, 152064, 0xb496f879
0, 5, 5, 1, 152064, 0xc43b36c9
0, 6, 6, 1, 152064, 0xb65abbf4
0, 7, 7, 1, 152064, 0xd1806312
0, 8, 8, 1, 152064, 0x0faf56c1
0, 9, 9, 1, 152064, 0x4de73b75
0, 10, 10, 1, 152064, 0xf90f24ce
0, 11, 11, 1, 152064, 0xc1efd6e0
0, 12, 12, 1, 152064, 0xeb8e5894
0, 13, 13, 1, 152064, 0xe8aacabc
0, 14, 14, 1, 152064, 0x8bd2163c
0, 15, 15, 1, 152064, 0xbfc72ac2
0, 16, 16, 1, 152064, 0x1e8f6f56
0, 17, 17, 1, 152064, 0xe3d19450
0, 18, 18, 1, 152064, 0x3872af32
0, 19, 19, 1, 152064, 0xf23be72a
0, 20, 20, 1, 152064, 0x024f8f2b
0, 21, 21, 1, 152064, 0xb49301ea
0, 22, 22, 1, 152064, 0x84f5d056
0, 23, 23, 1, 152064, 0xd2c09ca5
0, 24, 24, 1, 152064, 0xe9b5ddfd