vf_dlopen filters: add an "ildetect" filter that detects interlacing

The filter analyzes each frame for combing, and decides at the end
whether the content is likely interlaced, telecined or progressive.
This commit is contained in:
Rudolf Polzer 2013-06-14 14:32:18 +02:00
parent de63e1d15e
commit 7ae4242820
4 changed files with 357 additions and 5 deletions

View File

@ -19,7 +19,7 @@
# 02110-1301 USA
#
FILTERS = showqscale telecine tile rectangle framestep
FILTERS = showqscale telecine tile rectangle framestep ildetect
COMMON = filterutils.o
OBJECTS = $(patsubst %,%.o,$(FILTERS)) $(COMMON)

292
TOOLS/vf_dlopen/ildetect.c Normal file
View File

@ -0,0 +1,292 @@
/*
* Copyright (c) 2012 Rudolf Polzer <divVerent@xonotic.org>
*
* This file is part of mpv's vf_dlopen examples.
*
* mpv's vf_dlopen examples are free software; you can redistribute them and/or
* modify them under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* mpv's vf_dlopen examples are distributed in the hope that they will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv's vf_dlopen examples; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "vf_dlopen.h"
#include "filterutils.h"
/*
* interlacing detector
*
* usage: -vf dlopen=./ildetect.so:<method>:<threshold>
*
* outputs an interlacing detection report at the end
*
* methods:
* 0 = transcode 32detect (default)
* 1 = decomb IsCombed
* 2 = IsCombedTIVTC
* 3 = simple average
*
* threshold:
* normalized at 1
*/
typedef struct {
int method;
double combing_threshold;
double motion_threshold;
double motion_amount;
double yes_threshold;
double no_threshold;
double total_yes_threshold;
double total_no_threshold;
double tc_threshold;
double decision_threshold;
double tc_decision_threshold;
double lastcombed;
int numtotalframes;
int numdecidedframes;
int totalcombedframes;
int numjumpingadjacentframes;
int numdecidedadjacentframes;
unsigned char *buffer_data;
size_t buffer_size;
} ildetect_data_t;
static int il_config(struct vf_dlopen_context *ctx)
{
ctx->out_height -= 4;
ctx->out_d_height = ctx->out_height;
return 1;
}
static int il_decision(struct vf_dlopen_context *ctx,
int p0, int p1, int p2, int p3, int p4)
{
ildetect_data_t *il = ctx->priv;
// model for threshold: p0, p2, p4 = 0; p1, p3 = t
switch (il->method) {
case 0: { // diff-diff (transcode 32detect)
int d12 = p1 - p2; // t
int d13 = p1 - p3; // 0
if (abs(d12) > 15 * il->combing_threshold &&
abs(d13) < 10 * il->combing_threshold)
return 1;
// true for t > 15
break;
}
case 1: { // multiply (decomb IsCombed)
int d12 = p1 - p2; // t
int d32 = p3 - p2; // t
if (d12 * d32 > pow(il->combing_threshold, 2) * (25*25))
return 1;
// true for t > 21
break;
}
case 2: { // blur-blur (IsCombedTIVTC)
int b024 = p0 + 6 * p2 + p4; // 0
int b13 = 4 * p1 + 4 * p3; // 8t
if (abs(b024 - b13) > il->combing_threshold * 8 * 20)
return 1;
// true for t > 20
break;
}
case 3: { // average-average
int d123 = p1 + p3 - 2 * p2; // 2t
int d024 = p0 + p4 - 2 * p2; // 0
if ((abs(d123) - abs(d024)) > il->combing_threshold * 30)
return 1;
// true for t > 15
break;
}
}
return 0;
}
static int il_put_image(struct vf_dlopen_context *ctx)
{
ildetect_data_t *il = ctx->priv;
unsigned int x, y;
int first_frame = 0;
size_t sz = ctx->inpic.planestride[0] * ctx->inpic.planeheight[0];
if (sz != il->buffer_size) {
il->buffer_data = realloc(il->buffer_data, sz);
il->buffer_size = sz;
first_frame = 1;
}
assert(ctx->inpic.planes == 1);
assert(ctx->outpic[0].planes == 1);
assert(ctx->inpic.planewidth[0] == ctx->outpic[0].planewidth[0]);
assert(ctx->inpic.planeheight[0] == ctx->outpic[0].planeheight[0] + 4);
if (first_frame) {
printf("First frame\n");
il->lastcombed = -1;
} else {
// detect interlacing
// for each row of 5 pixels, compare:
// p2 vs (p1 + p3) / 2
// p2 vs (p0 + p4) / 2
unsigned int totalcombedframes = 0; // add 255 per combed pixel
unsigned int totalpixels = 0;
for (y = 0; y < ctx->inpic.planeheight[0] - 4; ++y) {
unsigned char *in_line =
&ctx->inpic.plane[0][ctx->inpic.planestride[0] * y];
unsigned char *buf_line =
&il->buffer_data[ctx->inpic.planestride[0] * y];
unsigned char *out_line =
&ctx->outpic->plane[0][ctx->outpic->planestride[0] * y];
for (x = 0; x < ctx->inpic.planewidth[0]; ++x) {
int b2 = buf_line[x + ctx->inpic.planestride[0] * 2];
int p0 = in_line[x];
int p1 = in_line[x + ctx->inpic.planestride[0]];
int p2 = in_line[x + ctx->inpic.planestride[0] * 2];
int p3 = in_line[x + ctx->inpic.planestride[0] * 3];
int p4 = in_line[x + ctx->inpic.planestride[0] * 4];
int is_moving = abs(b2 - p2) > il->motion_threshold;
if (!is_moving) {
out_line[x] = 128;
continue;
}
++totalpixels;
int combed = il_decision(ctx, p0, p1, p2, p3, p4);
totalcombedframes += combed;
out_line[x] = 255 * combed;
}
}
double avgpixels = totalpixels / (double)
((ctx->inpic.planeheight[0] - 4) * ctx->inpic.planewidth[0]);
if (avgpixels > il->motion_amount) {
double avgcombed = totalcombedframes / (double) totalpixels;
if (il->lastcombed >= 0) {
if (il->lastcombed > il->yes_threshold &&
avgcombed < il->no_threshold)
++il->numjumpingadjacentframes;
if (il->lastcombed < il->no_threshold &&
avgcombed > il->yes_threshold)
++il->numjumpingadjacentframes;
++il->numdecidedadjacentframes;
}
il->lastcombed = avgcombed;
if (avgcombed > il->yes_threshold) {
++il->numdecidedframes;
++il->totalcombedframes;
} else if (avgcombed < il->no_threshold) {
++il->numdecidedframes;
}
} else
il->lastcombed = -1;
}
++il->numtotalframes;
copy_plane(
il->buffer_data, ctx->inpic.planestride[0],
ctx->inpic.plane[0], ctx->inpic.planestride[0],
ctx->inpic.planewidth[0],
ctx->inpic.planeheight[0]);
ctx->outpic[0].pts = ctx->inpic.pts;
return 1;
}
void il_uninit(struct vf_dlopen_context *ctx)
{
ildetect_data_t *il = ctx->priv;
double avgdecided = il->numtotalframes
? il->numdecidedframes / (double) il->numtotalframes : -1;
double avgadjacent = il->numdecidedframes
? il->numdecidedadjacentframes / (double) il->numdecidedframes : -1;
double avgscore = il->numdecidedframes
? il->totalcombedframes / (double) il->numdecidedframes : -1;
double avgjumps = il->numdecidedadjacentframes
? il->numjumpingadjacentframes / (double) il->numdecidedadjacentframes : -1;
printf("ildetect: Avg decided: %f\n", avgdecided);
printf("ildetect: Avg adjacent decided: %f\n", avgadjacent);
printf("ildetect: Avg interlaced decided: %f\n", avgscore);
printf("ildetect: Avg interlaced/progressive adjacent decided: %f\n", avgjumps);
if (avgdecided < il->decision_threshold)
avgadjacent = avgscore = avgjumps = -1;
if (avgadjacent < il->tc_decision_threshold)
avgadjacent = avgjumps = -1;
if (avgscore < 0)
printf("ildetect: Content is probably: undecided\n");
else if (avgscore < il->total_no_threshold)
printf("ildetect: Content is probably: PROGRESSIVE\n");
else if (avgscore > il->total_yes_threshold && avgjumps < 0)
printf("ildetect: Content is probably: INTERLACED (possibly telecined)\n");
else if (avgjumps > il->tc_threshold)
printf("ildetect: Content is probably: TELECINED\n");
else if (avgscore > il->total_yes_threshold)
printf("ildetect: Content is probably: INTERLACED\n");
else
printf("ildetect: Content is probably: unknown\n");
free(ctx->priv);
}
int vf_dlopen_getcontext(struct vf_dlopen_context *ctx, int argc, const char **argv)
{
VF_DLOPEN_CHECK_VERSION(ctx);
(void) argc;
(void) argv;
ildetect_data_t *il = malloc(sizeof(ildetect_data_t));
memset(il, 0, sizeof(*il));
#define A(i,d) ((argc>(i) && *argv[i]) ? atof(argv[i]) : (d))
il->method = A(0, 0);
il->combing_threshold = A(1, 1);
il->motion_threshold = A(2, 6);
il->motion_amount = A(3, 0.1);
il->yes_threshold = A(4, 0.1);
il->no_threshold = A(5, 0.05);
il->total_yes_threshold = A(6, 0.1);
il->total_no_threshold = A(7, 0.05);
il->tc_threshold = A(8, 0.1);
il->decision_threshold = A(9, 0.2);
il->tc_decision_threshold = A(10, 0.2);
static struct vf_dlopen_formatpair map[] = {
{ "y8", "y8" },
{ NULL, NULL }
};
ctx->format_mapping = map;
ctx->config = il_config;
ctx->put_image = il_put_image;
ctx->uninit = il_uninit;
ctx->priv = il;
return 1;
}

43
TOOLS/vf_dlopen/ildetect.sh Executable file
View File

@ -0,0 +1,43 @@
#!/bin/sh
case "$0" in
*/*)
MYDIR=${0%/*}
;;
*)
MYDIR=.
;;
esac
set -e
make -C "$MYDIR"
testfun()
{
${MPV:-mpv} "$@" \
-vf dlopen="$MYDIR/ildetect.so" \
-o /dev/null -of nut -ovc rawvideo -no-audio \
| grep "^ildetect:"
}
out=`testfun "$@"`
echo
echo
echo "$out"
echo
echo
case "$out" in
*"probably: PROGRESSIVE"*)
${MPV:-mpv} "$@"
;;
*"probably: INTERLACED"*)
${MPV:-mpv} "$@" -vf-pre yadif
;;
*"probably: TELECINED"*)
${MPV:-mpv} "$@" -vf-pre pullup
;;
*)
false
;;
esac

View File

@ -48,7 +48,7 @@
static struct vf_priv_s {
const char *cfg_dllname;
int cfg_argc;
const char *cfg_argv[4];
const char *cfg_argv[16];
void *dll;
struct vf_dlopen_context filter;
@ -318,11 +318,16 @@ static int vf_open(vf_instance_t *vf, char *args)
vf->priv->filter.minor_version = VF_DLOPEN_MINOR_VERSION;
// count arguments
for (vf->priv->cfg_argc = 0;
vf->priv->cfg_argc < sizeof(vf->priv->cfg_argv) / sizeof(vf->priv->cfg_argv[0]) && vf->priv->cfg_argv[vf->priv->cfg_argc];
++vf->priv->cfg_argc)
for (vf->priv->cfg_argc = sizeof(vf->priv->cfg_argv) / sizeof(vf->priv->cfg_argv[0]);
vf->priv->cfg_argc > 0 && !vf->priv->cfg_argv[vf->priv->cfg_argc - 1];
--vf->priv->cfg_argc)
;
// fix empty arguments
for (i = 0; i < vf->priv->cfg_argc; ++i)
if (vf->priv->cfg_argv[i] == NULL)
vf->priv->cfg_argv[i] = talloc_strdup (vf->priv, "");
if (func(&vf->priv->filter, vf->priv->cfg_argc, vf->priv->cfg_argv) < 0) {
mp_msg(MSGT_VFILTER, MSGL_ERR,
"function did not create a filter: %s\n",
@ -352,6 +357,18 @@ static m_option_t vf_opts_fields[] = {
{"a1", ST_OFF(cfg_argv[1]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a2", ST_OFF(cfg_argv[2]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a3", ST_OFF(cfg_argv[3]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a4", ST_OFF(cfg_argv[4]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a5", ST_OFF(cfg_argv[5]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a6", ST_OFF(cfg_argv[6]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a7", ST_OFF(cfg_argv[7]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a8", ST_OFF(cfg_argv[8]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a9", ST_OFF(cfg_argv[9]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a10", ST_OFF(cfg_argv[10]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a11", ST_OFF(cfg_argv[11]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a12", ST_OFF(cfg_argv[12]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a13", ST_OFF(cfg_argv[13]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a14", ST_OFF(cfg_argv[14]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{"a15", ST_OFF(cfg_argv[15]), CONF_TYPE_STRING, 0, 0, 0, NULL},
{ NULL, NULL, 0, 0, 0, 0, NULL }
};