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:
parent
172505b8bc
commit
0140566359
@ -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:
|
||||
|
1
LICENSE
1
LICENSE
@ -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
1
configure
vendored
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
298
libavfilter/vf_histeq.c
Normal 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,
|
||||
};
|
@ -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 \
|
||||
|
@ -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
6
tests/ref/lavfi/histeq
Normal file
@ -0,0 +1,6 @@
|
||||
abgr a538e1221c94a12fb4e60b47b5358f67
|
||||
argb d0ef008d603d67a6a7d698d2a8f53d6a
|
||||
bgr24 9ef3c69a658490c4fbc807272372e73a
|
||||
bgra 716e70fdf7413d9a3b83e0365c2b0a99
|
||||
rgb24 8423322bbc66bc5050f6b93fdab23433
|
||||
rgba a960c9423bbb3925c3511362348b38e2
|
Loading…
x
Reference in New Issue
Block a user