lavfi: add histeq filter

This is a port of virtual dub's histogram equalization filter by Donald
A. Graft. Based on the work by Jérémy Tran <tran.jeremy.av@gmail.com>,
done for SOCIS 2012.
This commit is contained in:
Stefano Sabatini 2012-10-20 07:45:51 +02:00
parent 172505b8bc
commit 0140566359
11 changed files with 350 additions and 1 deletions

View File

@ -56,6 +56,7 @@ version <next>:
- data: URI scheme
- support building on the Plan 9 operating system
- kerndeint filter ported from MPlayer
- histeq filter ported from VirtualDub
version 1.0:

View File

@ -31,6 +31,7 @@ Specifically, the GPL parts of FFmpeg are
- vf_decimate.c
- vf_delogo.c
- vf_geq.c
- vf_histeq.c
- vf_hqdn3d.c
- vf_hue.c
- vf_kerndeint.c

1
configure vendored
View File

@ -1985,6 +1985,7 @@ frei0r_filter_extralibs='$ldl'
frei0r_src_filter_deps="frei0r dlopen"
frei0r_src_filter_extralibs='$ldl'
geq_filter_deps="gpl"
histeq_filter_deps="gpl"
hqdn3d_filter_deps="gpl"
hue_filter_deps="gpl"
kerndeint_filter_deps="gpl"

View File

@ -2664,6 +2664,44 @@ For example to horizontally flip the input video with @command{ffmpeg}:
ffmpeg -i in.avi -vf "hflip" out.avi
@end example
@section histeq
This filter applies a global color histogram equalization on a
per-frame basis.
It can be used to correct video that has a compressed range of pixel
intensities. The filter redistributes the pixel intensities to
equalize their distribution across the intensity range. It may be
viewed as an "automatically adjusting contrast filter". This filter is
useful only for correcting degraded or poorly captured source
video.
The filter accepts parameters as a list of @var{key}=@var{value}
pairs, separated by ":". If the key of the first options is omitted,
the arguments are interpreted according to syntax
@var{strength}:@var{intensity}:@var{antibanding}.
This filter accepts the following named options:
@table @option
@item strength
Determine the amount of equalization to be applied. As the strength
is reduced, the distribution of pixel intensities more-and-more
approaches that of the input frame. The value must be a float number
in the range [0,1] and defaults to 0.200.
@item intensity
Set the maximum intensity that can generated and scale the output
values appropriately. The strength should be set as desired and then
the intensity can be limited if needed to avoid washing-out. The value
must be a float number in the range [0,1] and defaults to 0.210.
@item antibanding
Set the antibanding level. If enabled the filter will randomly vary
the luminance of output pixels by a small amount to avoid banding of
the histogram. Possible values are @code{none}, @code{weak} or
@code{strong}. It defaults to @code{none}.
@end table
@section hqdn3d
High precision/quality 3d denoise filter. This filter aims to reduce

View File

@ -111,6 +111,7 @@ OBJS-$(CONFIG_FREI0R_FILTER) += vf_frei0r.o
OBJS-$(CONFIG_GEQ_FILTER) += vf_geq.o
OBJS-$(CONFIG_GRADFUN_FILTER) += vf_gradfun.o
OBJS-$(CONFIG_HFLIP_FILTER) += vf_hflip.o
OBJS-$(CONFIG_HISTEQ_FILTER) += vf_histeq.o
OBJS-$(CONFIG_HQDN3D_FILTER) += vf_hqdn3d.o
OBJS-$(CONFIG_HUE_FILTER) += vf_hue.o
OBJS-$(CONFIG_IDET_FILTER) += vf_idet.o

View File

@ -105,6 +105,7 @@ void avfilter_register_all(void)
REGISTER_FILTER(GEQ, geq, vf);
REGISTER_FILTER(GRADFUN, gradfun, vf);
REGISTER_FILTER(HFLIP, hflip, vf);
REGISTER_FILTER(HISTEQ, histeq, vf);
REGISTER_FILTER(HQDN3D, hqdn3d, vf);
REGISTER_FILTER(HUE, hue, vf);
REGISTER_FILTER(IDET, idet, vf);

View File

@ -29,7 +29,7 @@
#include "libavutil/avutil.h"
#define LIBAVFILTER_VERSION_MAJOR 3
#define LIBAVFILTER_VERSION_MINOR 31
#define LIBAVFILTER_VERSION_MINOR 32
#define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \

298
libavfilter/vf_histeq.c Normal file
View File

@ -0,0 +1,298 @@
/*
* Copyright (c) 2012 Jeremy Tran
* Copyright (c) 2001 Donald A. Graft
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with FFmpeg; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* @file
* Histogram equalization filter, based on the VirtualDub filter by
* Donald A. Graft <neuron2 AT home DOT com>.
* Implements global automatic contrast adjustment by means of
* histogram equalization.
*/
#include "libavutil/common.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "avfilter.h"
#include "drawutils.h"
#include "formats.h"
#include "internal.h"
#include "video.h"
// #define DEBUG
// Linear Congruential Generator, see "Numerical Recipes"
#define LCG_A 4096
#define LCG_C 150889
#define LCG_M 714025
#define LCG(x) (((x) * LCG_A + LCG_C) % LCG_M)
#define LCG_SEED 739187
enum HisteqAntibanding {
HISTEQ_ANTIBANDING_NONE = 0,
HISTEQ_ANTIBANDING_WEAK = 1,
HISTEQ_ANTIBANDING_STRONG = 2,
HISTEQ_ANTIBANDING_NB,
};
typedef struct {
const AVClass *class;
float strength;
float intensity;
enum HisteqAntibanding antibanding;
char* antibanding_str;
int in_histogram [256]; ///< input histogram
int out_histogram[256]; ///< output histogram
int LUT[256]; ///< lookup table derived from histogram[]
uint8_t rgba_map[4]; ///< components position
int bpp; ///< bytes per pixel
} HisteqContext;
#define OFFSET(x) offsetof(HisteqContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
#define CONST(name, help, val, unit) { name, help, 0, AV_OPT_TYPE_CONST, {.i64=val}, INT_MIN, INT_MAX, FLAGS, unit }
static const AVOption histeq_options[] = {
{ "strength", "set the strength", OFFSET(strength), AV_OPT_TYPE_FLOAT, {.dbl=0.2}, 0, 1, FLAGS },
{ "intensity", "set the intensity", OFFSET(intensity), AV_OPT_TYPE_FLOAT, {.dbl=0.21}, 0, 1, FLAGS },
{ "antibanding", "set the antibanding level", OFFSET(antibanding), AV_OPT_TYPE_INT, {.i64=HISTEQ_ANTIBANDING_NONE}, 0, HISTEQ_ANTIBANDING_NB-1, FLAGS, "antibanding" },
CONST("none", "apply no antibanding", HISTEQ_ANTIBANDING_NONE, "antibanding"),
CONST("weak", "apply weak antibanding", HISTEQ_ANTIBANDING_WEAK, "antibanding"),
CONST("strong", "apply strong antibanding", HISTEQ_ANTIBANDING_STRONG, "antibanding"),
{ NULL }
};
AVFILTER_DEFINE_CLASS(histeq);
static av_cold int init(AVFilterContext *ctx, const char *args)
{
HisteqContext *histeq = ctx->priv;
const char *shorthand[] = { "strength", "intensity", "antibanding", NULL };
int ret;
histeq->class = &histeq_class;
av_opt_set_defaults(histeq);
if ((ret = av_opt_set_from_string(histeq, args, shorthand, "=", ":")) < 0)
return ret;
av_log(ctx, AV_LOG_VERBOSE,
"strength:%0.3f intensity:%0.3f antibanding:%d\n",
histeq->strength, histeq->intensity, histeq->antibanding);
return 0;
}
static av_cold void uninit(AVFilterContext *ctx)
{
HisteqContext *histeq = ctx->priv;
av_opt_free(histeq);
}
static int query_formats(AVFilterContext *ctx)
{
static const enum PixelFormat pix_fmts[] = {
AV_PIX_FMT_ARGB, AV_PIX_FMT_RGBA, AV_PIX_FMT_ABGR, AV_PIX_FMT_BGRA,
AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
AV_PIX_FMT_NONE
};
ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
return 0;
}
static int config_input(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
HisteqContext *histeq = ctx->priv;
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(inlink->format);
histeq->bpp = av_get_bits_per_pixel(pix_desc) / 8;
ff_fill_rgba_map(histeq->rgba_map, inlink->format);
return 0;
}
#define R 0
#define G 1
#define B 2
#define A 3
#define GET_RGB_VALUES(r, g, b, src, map) do { \
r = src[x + map[R]]; \
g = src[x + map[G]]; \
b = src[x + map[B]]; \
} while (0)
static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *inpic)
{
AVFilterContext *ctx = inlink->dst;
HisteqContext *histeq = ctx->priv;
AVFilterLink *outlink = ctx->outputs[0];
int strength = histeq->strength * 1000;
int intensity = histeq->intensity * 1000;
int x, y, i, luthi, lutlo, lut, luma, oluma, m;
AVFilterBufferRef *outpic;
unsigned int r, g, b, jran;
uint8_t *src, *dst;
outpic = ff_get_video_buffer(outlink, AV_PERM_WRITE|AV_PERM_ALIGN, outlink->w, outlink->h);
if (!outpic) {
avfilter_unref_bufferp(&inpic);
return AVERROR(ENOMEM);
}
avfilter_copy_buffer_ref_props(outpic, inpic);
/* Seed random generator for antibanding. */
jran = LCG_SEED;
/* Calculate and store the luminance and calculate the global histogram
based on the luminance. */
memset(histeq->in_histogram, 0, sizeof(histeq->in_histogram));
src = inpic->data[0];
dst = outpic->data[0];
for (y = 0; y < inlink->h; y++) {
for (x = 0; x < inlink->w * histeq->bpp; x += histeq->bpp) {
GET_RGB_VALUES(r, g, b, src, histeq->rgba_map);
luma = (55 * r + 182 * g + 19 * b) >> 8;
dst[x + histeq->rgba_map[A]] = luma;
histeq->in_histogram[luma]++;
}
src += inpic->linesize[0];
dst += outpic->linesize[0];
}
#ifdef DEBUG
for (x = 0; x < 256; x++)
av_dlog(ctx, "in[%d]: %u\n", x, histeq->in_histogram[x]);
#endif
/* Calculate the lookup table. */
histeq->LUT[0] = histeq->in_histogram[0];
/* Accumulate */
for (x = 1; x < 256; x++)
histeq->LUT[x] = histeq->LUT[x-1] + histeq->in_histogram[x];
/* Normalize */
for (x = 0; x < 256; x++)
histeq->LUT[x] = (histeq->LUT[x] * intensity) / (inlink->h * inlink->w);
/* Adjust the LUT based on the selected strength. This is an alpha
mix of the calculated LUT and a linear LUT with gain 1. */
for (x = 0; x < 256; x++)
histeq->LUT[x] = (strength * histeq->LUT[x]) / 255 +
((255 - strength) * x) / 255;
/* Output the equalized frame. */
memset(histeq->out_histogram, 0, sizeof(histeq->out_histogram));
src = inpic->data[0];
dst = outpic->data[0];
for (y = 0; y < inlink->h; y++) {
for (x = 0; x < inlink->w * histeq->bpp; x += histeq->bpp) {
luma = dst[x + histeq->rgba_map[A]];
if (luma == 0) {
for (i = 0; i < histeq->bpp; ++i)
dst[x + i] = 0;
histeq->out_histogram[0]++;
} else {
lut = histeq->LUT[luma];
if (histeq->antibanding != HISTEQ_ANTIBANDING_NONE) {
if (luma > 0) {
lutlo = histeq->antibanding == HISTEQ_ANTIBANDING_WEAK ?
(histeq->LUT[luma] + histeq->LUT[luma - 1]) / 2 :
histeq->LUT[luma - 1];
} else
lutlo = lut;
if (luma < 255) {
luthi = (histeq->antibanding == HISTEQ_ANTIBANDING_WEAK) ?
(histeq->LUT[luma] + histeq->LUT[luma + 1]) / 2 :
histeq->LUT[luma + 1];
} else
luthi = lut;
if (lutlo != luthi) {
jran = LCG(jran);
lut = lutlo + ((luthi - lutlo + 1) * jran) / LCG_M;
}
}
GET_RGB_VALUES(r, g, b, src, histeq->rgba_map);
if (((m = FFMAX3(r, g, b)) * lut) / luma > 255) {
r = (r * 255) / m;
g = (g * 255) / m;
b = (b * 255) / m;
} else {
r = (r * lut) / luma;
g = (g * lut) / luma;
b = (b * lut) / luma;
}
dst[x + histeq->rgba_map[R]] = r;
dst[x + histeq->rgba_map[G]] = g;
dst[x + histeq->rgba_map[B]] = b;
oluma = (55 * r + 182 * g + 19 * b) >> 8;
histeq->out_histogram[oluma]++;
}
}
src += inpic->linesize[0];
dst += outpic->linesize[0];
}
#ifdef DEBUG
for (x = 0; x < 256; x++)
av_dlog(ctx, "out[%d]: %u\n", x, histeq->out_histogram[x]);
#endif
avfilter_unref_bufferp(&inpic);
return ff_filter_frame(outlink, outpic);
}
static const AVFilterPad histeq_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = config_input,
.filter_frame = filter_frame,
.min_perms = AV_PERM_READ,
},
{ NULL }
};
static const AVFilterPad histeq_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
},
{ NULL }
};
AVFilter avfilter_vf_histeq = {
.name = "histeq",
.description = NULL_IF_CONFIG_SMALL("Apply global color histogram equalization."),
.priv_size = sizeof(HisteqContext),
.init = init,
.uninit = uninit,
.query_formats = query_formats,
.inputs = histeq_inputs,
.outputs = histeq_outputs,
.priv_class = &histeq_class,
};

View File

@ -41,6 +41,7 @@ FATE_LAVFI = fate-lavfi-alphaextract_rgb \
FATE_LAVFI-$(CONFIG_GPL) += fate-lavfi-colormatrix1 \
fate-lavfi-colormatrix2 \
fate-lavfi-histeq \
fate-lavfi-hue \
fate-lavfi-pixfmts_super2xsai \
fate-lavfi-tinterlace_merge \

View File

@ -105,6 +105,7 @@ do_lavfi_pixfmts(){
# all these filters have exactly one input and exactly one output
do_lavfi_pixfmts "field" "field" "bottom"
do_lavfi_pixfmts "histeq" "histeq" "antibanding=strong"
do_lavfi_pixfmts "kerndeint" "kerndeint" "" "tinterlace=interleave_top,"
do_lavfi_pixfmts "pixfmts_copy" "copy" ""
do_lavfi_pixfmts "pixfmts_crop" "crop" "100:100:100:100"

6
tests/ref/lavfi/histeq Normal file
View File

@ -0,0 +1,6 @@
abgr a538e1221c94a12fb4e60b47b5358f67
argb d0ef008d603d67a6a7d698d2a8f53d6a
bgr24 9ef3c69a658490c4fbc807272372e73a
bgra 716e70fdf7413d9a3b83e0365c2b0a99
rgb24 8423322bbc66bc5050f6b93fdab23433
rgba a960c9423bbb3925c3511362348b38e2