Signed-off-by: Paul B Mahol <one...@gmail.com> --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_retinex.c | 503 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 505 insertions(+) create mode 100644 libavfilter/vf_retinex.c
diff --git a/libavfilter/Makefile b/libavfilter/Makefile index a8b3a4d..0e3b871 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -188,6 +188,7 @@ OBJS-$(CONFIG_RANDOM_FILTER) += vf_random.o OBJS-$(CONFIG_REMOVEGRAIN_FILTER) += vf_removegrain.o OBJS-$(CONFIG_REMOVELOGO_FILTER) += bbox.o lswsutils.o lavfutils.o vf_removelogo.o OBJS-$(CONFIG_REPEATFIELDS_FILTER) += vf_repeatfields.o +OBJS-$(CONFIG_RETINEX_FILTER) += vf_retinex.o OBJS-$(CONFIG_REVERSE_FILTER) += f_reverse.o OBJS-$(CONFIG_ROTATE_FILTER) += vf_rotate.o OBJS-$(CONFIG_SEPARATEFIELDS_FILTER) += vf_separatefields.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 427585f..b69a011 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -209,6 +209,7 @@ void avfilter_register_all(void) REGISTER_FILTER(REMOVEGRAIN, removegrain, vf); REGISTER_FILTER(REMOVELOGO, removelogo, vf); REGISTER_FILTER(REPEATFIELDS, repeatfields, vf); + REGISTER_FILTER(RETINEX, retinex, vf); REGISTER_FILTER(REVERSE, reverse, vf); REGISTER_FILTER(ROTATE, rotate, vf); REGISTER_FILTER(SAB, sab, vf); diff --git a/libavfilter/vf_retinex.c b/libavfilter/vf_retinex.c new file mode 100644 index 0000000..896c68b --- /dev/null +++ b/libavfilter/vf_retinex.c @@ -0,0 +1,503 @@ +/* + * Copyright (c) 2014 mawen1250 + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include <float.h> + +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +typedef struct RetinexContext { + const AVClass *class; + + float lower_thr; + float upper_thr; + int full; + float chroma_protect; + + int nb_planes; + int planewidth[4]; + int planeheight[4]; + + float *in_data; + float *out_data; + float *gauss; + + void (*retinex)(struct RetinexContext *s, AVFrame *in, AVFrame *out); +} RetinexContext; + +#define OFFSET(x) offsetof(RetinexContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption retinex_options[] = { + { "lower", "set lower threshold", OFFSET(lower_thr), AV_OPT_TYPE_FLOAT, {.dbl=0.001}, 0, 1, FLAGS }, + { "upper", "set upper threshold", OFFSET(upper_thr), AV_OPT_TYPE_FLOAT, {.dbl=0.001}, 0, 1, FLAGS }, + { "full", "set full range", OFFSET(full), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS }, + { "chroma_protect", "set chroma protect", OFFSET(chroma_protect), AV_OPT_TYPE_FLOAT, {.dbl=1.2}, 1, FLT_MAX, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(retinex); + +static int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pixel_fmts[] = { + AV_PIX_FMT_GRAY8, + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUV444P, + AV_PIX_FMT_NONE + }; + AVFilterFormats *formats = ff_make_format_list(pixel_fmts); + if (!formats) + return AVERROR(ENOMEM); + return ff_set_common_formats(ctx, formats); +} + +static void recursive_gaussian_parameters(const double sigma, float *B, float *B1, + float *B2, float *B3) +{ + const double q = sigma < 2.5 ? 3.97156 - 4.14554*sqrt(1 - 0.26891*sigma) : 0.98711*sigma - 0.96330; + + const double b0 = 1.57825 + 2.44413*q + 1.4281*q*q + 0.422205*q*q*q; + const double b1 = 2.44413*q + 2.85619*q*q + 1.26661*q*q*q; + const double b2 = -(1.4281*q*q + 1.26661*q*q*q); + const double b3 = 0.422205*q*q*q; + + *B = 1. - (b1 + b2 + b3) / b0; + *B1 = b1 / b0; + *B2 = b2 / b0; + *B3 = b3 / b0; +} + +static void recursive_gaussian2d_horizontal(float *output, const float *input, + int h, int w, int stride, + const float B, const float B1, + const float B2, const float B3) +{ + int i, j, lower, upper; + float p0, p1, p2, p3; + + for (j = 0; j < h; j++) { + lower = stride * j; + upper = lower + w; + + i = lower; + output[i] = p3 = p2 = p1 = input[i]; + + for (i++; i < upper; i++) { + p0 = B*input[i] + B1*p1 + B2*p2 + B3*p3; + p3 = p2; + p2 = p1; + p1 = p0; + output[i] = p0; + } + + i--; + p3 = p2 = p1 = output[i]; + + for (i--; i >= lower; i--) { + p0 = B*output[i] + B1*p1 + B2*p2 + B3*p3; + p3 = p2; + p2 = p1; + p1 = p0; + output[i] = p0; + } + } +} + +static void recursive_gaussian2d_vertical(float *output, const float *input, + int h, int w, int stride, const float B, + const float B1, const float B2, const float B3) +{ + int i0, i1, i2, i3, j, lower, upper; + float p0, p1, p2, p3; + + if (output != input) + memcpy(output, input, sizeof(float) * w); + + for (j = 0; j < h; j++) { + lower = stride * j; + upper = lower + w; + + i0 = lower; + i1 = j < 1 ? i0 : i0 - stride; + i2 = j < 2 ? i1 : i1 - stride; + i3 = j < 3 ? i2 : i2 - stride; + + for (; i0 < upper; i0++, i1++, i2++, i3++) { + p3 = output[i3]; + p2 = output[i2]; + p1 = output[i1]; + p0 = input[i0]; + output[i0] = B*p0 + B1*p1 + B2*p2 + B3*p3; + } + } + + for (j = h - 1; j >= 0; j--) { + lower = stride * j; + upper = lower + w; + + i0 = lower; + i1 = j >= h - 1 ? i0 : i0 + stride; + i2 = j >= h - 2 ? i1 : i1 + stride; + i3 = j >= h - 3 ? i2 : i2 + stride; + + for (; i0 < upper; i0++, i1++, i2++, i3++) { + p3 = output[i3]; + p2 = output[i2]; + p1 = output[i1]; + p0 = output[i0]; + output[i0] = B*p0 + B1*p1 + B2*p2 + B3*p3; + } + } +} + +static const uint8_t sigma[3] = { 20, 80, 250 }; + +static void msr_kernel(float *out_data, float *in_data, float *gauss, int w, int h) +{ + int x, y, s, upper; + float *out, b, b1, b2, b3; + + for (y = 0; y < h; y++) { + out = out_data + y * w; + + for (x = 0; x < w; x++) + out[x] = 1; + } + + for (s = 0; s < sizeof(sigma); s++) { + recursive_gaussian_parameters(sigma[s], &b, &b1, &b2, &b3); + recursive_gaussian2d_horizontal(gauss, in_data, h, w, w, b, b1, b2, b3); + recursive_gaussian2d_vertical(gauss, gauss, h, w, w, b, b1, b2, b3); + + for (y = 0; y < h; y++) { + x = y * w; + for (upper = x + w; x < upper; x++) + out_data[x] *= gauss[x] <= 0 ? 1 : in_data[x] / gauss[x] + 1; + } + } + + for (y = 0; y < h; y++) { + x = y * w; + for (upper = x + w; x < upper; x++) + out_data[x] = log(out_data[x]) / sizeof(sigma); + } + +} + +static void simplest_color_balance(RetinexContext *s, float *odata, + const float *idata, + int width, int height) +{ + int i, j, upper; + float offset, gain; + float min = FLT_MAX; + float max = FLT_MIN; + float FloorFL = 0; + float CeilFL = 1; + float Histogram[4096] = {0}; + + for (j = 0; j < height; j++) { + i = width * j; + for (upper = i + width; i < upper; i++) { + min = FFMIN(min, odata[i]); + max = FFMAX(max, odata[i]); + } + } + + if (max <= min) { + memcpy(odata, idata, sizeof(float)*width*height); + return; + } + + if (s->lower_thr > 0 || s->upper_thr > 0) { + int h, HistBins = 4096; + int Count, MaxCount; + + gain = (HistBins - 1) / (max - min); + offset = -min * gain; + + for (j = 0; j < height; j++) { + i = width * j; + for (upper = i + width; i < upper; i++) { + Histogram[(int)(odata[i] * gain + offset)]++; + } + } + + gain = (max - min) / (HistBins - 1); + offset = min; + + Count = 0; + MaxCount = (int)(width*height*s->lower_thr + 0.5); + + for (h = 0; h < HistBins; h++) { + Count += Histogram[h]; + if (Count > MaxCount) break; + } + + min = h * gain + offset; + + Count = 0; + MaxCount = (int)(width*height*s->upper_thr + 0.5); + + for (h = HistBins - 1; h >= 0; h--) { + Count += Histogram[h]; + if (Count > MaxCount) break; + } + + max = h * gain + offset; + } + + gain = (CeilFL - FloorFL) / (max - min); + offset = -min * gain + FloorFL; + + if (s->lower_thr > 0 || s->upper_thr > 0) { + for (j = 0; j < height; j++) { + i = width * j; + for (upper = i + width; i < upper; i++) + odata[i] = av_clipf(odata[i] * gain + offset, FloorFL, CeilFL); + } + } else { + for (j = 0; j < height; j++) { + i = width * j; + for (upper = i + width; i < upper; i++) + odata[i] = odata[i] * gain + offset; + } + } +} + +static void msrcp_gray(RetinexContext *s, AVFrame *in, AVFrame *out) +{ + float range = s->full ? 255 : 219; + float gain = 1. / range; + int x, y; + + if (s->full) { + for (y = 0; y < s->planeheight[0]; y++) { + float *dst = s->in_data + y * s->planewidth[0]; + const uint8_t *src = in->data[0] + y * in->linesize[0]; + + for (x = 0; x < s->planewidth[0]; x++) + dst[x] = src[x] * gain; + } + } else { + float min = 255, max = 0, ceil = 255, floor = 0; + + for (y = 0; y < s->planeheight[0]; y++) { + const uint8_t *src = in->data[0] + y * in->linesize[0]; + + for (x = 0; x < s->planewidth[0]; x++) { + min = FFMIN(min, src[x]); + max = FFMAX(max, src[x]); + } + } + if (max > min) { + floor = min; + ceil = max; + } + + gain = 1 / (ceil - floor); + for (y = 0; y < s->planeheight[0]; y++) { + const uint8_t *src = in->data[0] + y * in->linesize[0]; + float *dst = s->in_data + y * s->planewidth[0]; + + for (x = 0; x < s->planewidth[0]; x++) + dst[x] = (src[x] - floor) * gain; + } + } + + msr_kernel(s->out_data, s->in_data, s->gauss, + s->planewidth[0], s->planeheight[0]); + simplest_color_balance(s, s->out_data, s->in_data, + s->planewidth[0], s->planeheight[0]); + + for (y = 0; y < s->planeheight[0]; y++) { + uint8_t *dst = out->data[0] + y * out->linesize[0]; + float *src = s->out_data + y * s->planewidth[0]; + + for (x = 0; x < s->planewidth[0]; x++) + dst[x] = src[x] * range; + } +} + +static void msrcp_yuv(RetinexContext *s, AVFrame *in, AVFrame *out) +{ + float chroma_protect_mul1 = s->chroma_protect - 1; + float chroma_protect_mul2 = 1 / log(s->chroma_protect); + float crange = s->full ? 255 : 224; + float yrange = s->full ? 255 : 219; + float floor = s->full ? 0 : 16; + float gain = 1. / yrange; + int u, v, x, y; + float yoffset = floor + 0.5; + + if (s->full) { + for (y = 0; y < s->planeheight[0]; y++) { + float *dst = s->in_data + y * s->planewidth[0]; + const uint8_t *src = in->data[0] + y * in->linesize[0]; + + for (x = 0; x < s->planewidth[0]; x++) + dst[x] = src[x] * gain; + } + } else { + float min = 255, max = 0, ceil = 255, floor = 0; + + for (y = 0; y < s->planeheight[0]; y++) { + const uint8_t *src = in->data[0] + y * in->linesize[0]; + + for (x = 0; x < s->planewidth[0]; x++) { + min = FFMIN(min, src[x]); + max = FFMAX(max, src[x]); + } + } + if (max > min) { + floor = min; + ceil = max; + } + + gain = 1 / (ceil - floor); + for (y = 0; y < s->planeheight[0]; y++) { + const uint8_t *src = in->data[0] + y * in->linesize[0]; + float *dst = s->in_data + y * s->planewidth[0]; + + for (x = 0; x < s->planewidth[0]; x++) + dst[x] = (src[x] - floor) * gain; + } + } + + msr_kernel(s->out_data, s->in_data, s->gauss, + s->planewidth[0], s->planeheight[0]); + simplest_color_balance(s, s->out_data, s->in_data, + s->planewidth[0], s->planeheight[0]); + + for (y = 0; y < s->planeheight[0]; y++) { + const uint8_t *usrc = in->data[1] + y * in->linesize[1]; + const uint8_t *vsrc = in->data[2] + y * in->linesize[2]; + uint8_t *ydst = out->data[0] + y * out->linesize[0]; + uint8_t *udst = out->data[1] + y * out->linesize[1]; + uint8_t *vdst = out->data[2] + y * out->linesize[2]; + const float *in_data = s->in_data + y * s->planewidth[0]; + const float *out_data = s->out_data + y * s->planewidth[0]; + + for (x = 0; x < s->planewidth[0]; x++) { + u = usrc[x] - 128; + v = vsrc[x] - 128; + if (s->chroma_protect > 1) + gain = in_data[x] <= 0 ? 1 : log(out_data[x] / in_data[x] * chroma_protect_mul1 + 1) * chroma_protect_mul2; + else + gain = in_data[x] <= 0 ? 1 : out_data[x] / in_data[x]; + gain = FFMIN(crange / 2 / FFMAX(FFABS(u), FFABS(v)), gain); + ydst[x] = out_data[x] * yrange + yoffset; + udst[x] = u * gain + 128; + vdst[x] = v * gain + 128; + } + } +} + +static int config_input(AVFilterLink *inlink) +{ + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + AVFilterContext *ctx = inlink->dst; + RetinexContext *s = ctx->priv; + + s->nb_planes = desc->nb_components; + + s->planeheight[1] = s->planeheight[2] = FF_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); + s->planeheight[0] = s->planeheight[3] = inlink->h; + s->planewidth[1] = s->planewidth[2] = FF_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); + s->planewidth[0] = s->planewidth[3] = inlink->w; + + s->in_data = av_mallocz_array(inlink->h, inlink->w * sizeof(*s->in_data)); + s->out_data = av_mallocz_array(inlink->h, inlink->w * sizeof(*s->out_data)); + s->gauss = av_mallocz_array(inlink->h, inlink->w * sizeof(*s->gauss)); + + switch (inlink->format) { + case AV_PIX_FMT_GRAY8: + s->retinex = msrcp_gray; + break; + case AV_PIX_FMT_YUVJ444P: + case AV_PIX_FMT_YUV444P: + s->retinex = msrcp_yuv; + break; + } + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + RetinexContext *s = ctx->priv; + AVFrame *out; + + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + + s->retinex(s, in, out); + + av_frame_free(&in); + return ff_filter_frame(outlink, out); +} + +static void uninit(AVFilterContext *ctx) +{ + RetinexContext *s = ctx->priv; + + av_freep(&s->in_data); + av_freep(&s->out_data); + av_freep(&s->gauss); +} + +static const AVFilterPad inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, + { NULL } +}; + +AVFilter ff_vf_retinex = { + .name = "retinex", + .description = NULL_IF_CONFIG_SMALL("Compress video dynamic range."), + .priv_size = sizeof(RetinexContext), + .priv_class = &retinex_class, + .query_formats = query_formats, + .uninit = uninit, + .inputs = inputs, + .outputs = outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, +}; -- 1.7.11.2 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel