avfilter: Add colorconstancy filter
authorMina <minasamy_@hotmail.com>
Mon, 16 Jul 2018 08:20:02 +0000 (10:20 +0200)
committerThilo Borgmann <thilo.borgmann@mail.de>
Mon, 23 Jul 2018 20:34:52 +0000 (22:34 +0200)
Signed-off-by: Mina <minasamy_@hotmail.com>
Changelog
MAINTAINERS
doc/filters.texi
libavfilter/Makefile
libavfilter/allfilters.c
libavfilter/vf_colorconstancy.c [new file with mode: 0644]

index 72da5bf..807a05d 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -15,6 +15,7 @@ version <next>:
 - vc1 decoder is now bit-exact
 - ATRAC9 decoder
 - lensfun wrapper filter
+- colorconstancy filter
 
 
 version 4.0:
index 78f450d..234b655 100644 (file)
@@ -332,6 +332,7 @@ Filters:
   vf_bwdif                              Thomas Mundt (CC <thomas.mundt@hr.de>)
   vf_chromakey.c                        Timo Rothenpieler
   vf_colorchannelmixer.c                Paul B Mahol
+  vf_colorconstancy.c                   Mina Sami    (CC <minas.gorgy@gmail.com>)
   vf_colorbalance.c                     Paul B Mahol
   vf_colorkey.c                         Timo Rothenpieler
   vf_colorlevels.c                      Paul B Mahol
index e3005c3..0b0903e 100644 (file)
@@ -9940,6 +9940,47 @@ gradfun=radius=8
 
 @end itemize
 
+@section greyedge
+A color constancy variation filter which estimates scene illumination via grey edge algorithm
+and corrects the scene colors accordingly.
+
+See: @url{https://staff.science.uva.nl/th.gevers/pub/GeversTIP07.pdf}
+
+The filter accepts the following options:
+
+@table @option
+@item difford
+The order of differentiation to be applied on the scene. Must be chosen in the range
+[0,2] and default value is 1.
+
+@item minknorm
+The Minkowski parameter to be used for calculating the Minkowski distance. Must
+be chosen in the range [0,65535] and default value is 1. Set to 0 for getting
+max value instead of calculating Minkowski distance.
+
+@item sigma
+The standard deviation of Gaussian blur to be applied on the scene. Must be
+chosen in the range [0,1024.0] and default value = 1. Sigma can't be set to 0
+if @var{difford} is greater than 0.
+@end table
+
+@subsection Examples
+@itemize
+
+@item
+Grey Edge:
+@example
+greyedge=difford=1:minknorm=5:sigma=2
+@end example
+
+@item
+Max Edge:
+@example
+greyedge=difford=1:minknorm=0:sigma=2
+@end example
+
+@end itemize
+
 @anchor{haldclut}
 @section haldclut
 
index 5d4549e..86b04e3 100644 (file)
@@ -228,6 +228,7 @@ OBJS-$(CONFIG_FSPP_FILTER)                   += vf_fspp.o
 OBJS-$(CONFIG_GBLUR_FILTER)                  += vf_gblur.o
 OBJS-$(CONFIG_GEQ_FILTER)                    += vf_geq.o
 OBJS-$(CONFIG_GRADFUN_FILTER)                += vf_gradfun.o
+OBJS-$(CONFIG_GREYEDGE_FILTER)               += vf_colorconstancy.o
 OBJS-$(CONFIG_HALDCLUT_FILTER)               += vf_lut3d.o framesync.o
 OBJS-$(CONFIG_HFLIP_FILTER)                  += vf_hflip.o
 OBJS-$(CONFIG_HISTEQ_FILTER)                 += vf_histeq.o
index 521bc53..2d19929 100644 (file)
@@ -217,6 +217,7 @@ extern AVFilter ff_vf_fspp;
 extern AVFilter ff_vf_gblur;
 extern AVFilter ff_vf_geq;
 extern AVFilter ff_vf_gradfun;
+extern AVFilter ff_vf_greyedge;
 extern AVFilter ff_vf_haldclut;
 extern AVFilter ff_vf_hflip;
 extern AVFilter ff_vf_histeq;
diff --git a/libavfilter/vf_colorconstancy.c b/libavfilter/vf_colorconstancy.c
new file mode 100644 (file)
index 0000000..7194688
--- /dev/null
@@ -0,0 +1,758 @@
+/*
+ * Copyright (c) 2018 Mina Sami
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it 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.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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
+ * Color Constancy filter
+ *
+ * @see http://colorconstancy.com/
+ *
+ * @cite
+ * J. van de Weijer, Th. Gevers, A. Gijsenij "Edge-Based Color Constancy".
+ */
+
+#include "libavutil/bprint.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+
+#include "avfilter.h"
+#include "formats.h"
+#include "internal.h"
+#include "video.h"
+
+#include <math.h>
+
+#define GREY_EDGE "greyedge"
+
+#define NUM_PLANES    3
+#define MAX_DIFF_ORD  2
+#define MAX_META_DATA 4
+#define MAX_DATA      4
+
+#define INDEX_TEMP 0
+#define INDEX_DX   1
+#define INDEX_DY   2
+#define INDEX_DXY  3
+#define INDEX_NORM INDEX_DX
+#define INDEX_SRC  0
+#define INDEX_DST  1
+#define INDEX_ORD  2
+#define INDEX_DIR  3
+#define DIR_X 0
+#define DIR_Y 1
+
+/**
+ * Used for passing data between threads.
+ */
+typedef struct ThreadData {
+    AVFrame *in, *out;
+    int meta_data[MAX_META_DATA];
+    double  *data[MAX_DATA][NUM_PLANES];
+} ThreadData;
+
+/**
+ * Common struct for all algorithms contexts.
+ */
+typedef struct ColorConstancyContext {
+    const AVClass *class;
+
+    int difford;
+    int minknorm; /**< @minknorm = 0 : getMax instead */
+    double sigma;
+
+    int nb_threads;
+    int planeheight[4];
+    int planewidth[4];
+
+    int filtersize;
+    double *gauss[MAX_DIFF_ORD+1];
+
+    double white[NUM_PLANES];
+} ColorConstancyContext;
+
+#define OFFSET(x) offsetof(ColorConstancyContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+#define GINDX(s, i) ( (i) - ((s) >> 2) )
+
+/**
+ * Sets gauss filters used for calculating gauss derivatives. Filter size
+ * depends on sigma which is a user option hence we calculate these
+ * filters each time. Also each higher order depends on lower ones. Sigma
+ * can be zero only at difford = 0, then we only convert data to double
+ * instead.
+ *
+ * @param ctx the filter context.
+ *
+ * @return 0 in case of success, a negative value corresponding to an
+ * AVERROR code in case of failure.
+ */
+static int set_gauss(AVFilterContext *ctx)
+{
+    ColorConstancyContext *s = ctx->priv;
+    int filtersize = s->filtersize;
+    int difford    = s->difford;
+    double sigma   = s->sigma;
+    double sum1, sum2;
+    int i;
+
+    for (i = 0; i <= difford; ++i) {
+        s->gauss[i] = av_mallocz_array(filtersize, sizeof(*s->gauss[i]));
+        if (!s->gauss[i]) {
+            for (; i >= 0; --i) {
+                av_freep(&s->gauss[i]);
+            }
+            av_log(ctx, AV_LOG_ERROR, "Out of memory while allocating gauss buffers.\n");
+            return AVERROR(ENOMEM);
+        }
+    }
+
+    // Order 0
+    av_log(ctx, AV_LOG_TRACE, "Setting 0-d gauss with filtersize = %d.\n", filtersize);
+    sum1 = 0.0;
+    if (!sigma) {
+        s->gauss[0][0] = 1; // Copying data to double instead of convolution
+    } else {
+        for (i = 0; i < filtersize; ++i) {
+            s->gauss[0][i] = exp(- pow(GINDX(filtersize, i), 2.) / (2 * sigma * sigma)) / ( sqrt(2 * M_PI) * sigma );
+            sum1 += s->gauss[0][i];
+        }
+        for (i = 0; i < filtersize; ++i) {
+            s->gauss[0][i] /= sum1;
+        }
+    }
+    // Order 1
+    if (difford > 0) {
+        av_log(ctx, AV_LOG_TRACE, "Setting 1-d gauss with filtersize = %d.\n", filtersize);
+        sum1 = 0.0;
+        for (i = 0; i < filtersize; ++i) {
+            s->gauss[1][i] = - (GINDX(filtersize, i) / pow(sigma, 2)) * s->gauss[0][i];
+            sum1 += s->gauss[1][i] *GINDX(filtersize, i);
+        }
+
+        for (i = 0; i < filtersize; ++i) {
+            s->gauss[1][i] /= sum1;
+        }
+
+        // Order 2
+        if (difford > 1) {
+            av_log(ctx, AV_LOG_TRACE, "Setting 2-d gauss with filtersize = %d.\n", filtersize);
+            sum1 = 0.0;
+            for (i = 0; i < filtersize; ++i) {
+                s->gauss[2][i] = ( pow(GINDX(filtersize, i), 2) / pow(sigma, 4) - 1/pow(sigma, 2) )
+                                 * s->gauss[0][i];
+                sum1 += s->gauss[2][i];
+            }
+
+            sum2 = 0.0;
+            for (i = 0; i < filtersize; ++i) {
+                s->gauss[2][i] -= sum1 / (filtersize);
+                sum2 += (0.5 * GINDX(filtersize, i) * GINDX(filtersize, i) * s->gauss[2][i]);
+            }
+            for (i = 0; i < filtersize ; ++i) {
+                s->gauss[2][i] /= sum2;
+            }
+        }
+    }
+    return 0;
+}
+
+/**
+ * Frees up buffers used by grey edge for storing derivatives final
+ * and intermidiate results. Number of buffers and number of planes
+ * for last buffer are given so it can be safely called at allocation
+ * failure instances.
+ *
+ * @param td holds the buffers.
+ * @param nb_buff number of buffers to be freed.
+ * @param nb_planes number of planes for last buffer to be freed.
+ */
+static void cleanup_derivative_buffers(ThreadData *td, int nb_buff, int nb_planes)
+{
+    int b, p;
+
+    for (b = 0; b < nb_buff; ++b) {
+        for (p = 0; p < NUM_PLANES; ++p) {
+            av_freep(&td->data[b][p]);
+        }
+    }
+    // Final buffer may not be fully allocated at fail cases
+    for (p = 0; p < nb_planes; ++p) {
+        av_freep(&td->data[b][p]);
+    }
+}
+
+/**
+ * Allocates buffers used by grey edge for storing derivatives final
+ * and intermidiate results.
+ *
+ * @param ctx the filter context.
+ * @param td holds the buffers.
+ *
+ * @return 0 in case of success, a negative value corresponding to an
+ * AVERROR code in case of failure.
+ */
+static int setup_derivative_buffers(AVFilterContext* ctx, ThreadData *td)
+{
+    ColorConstancyContext *s = ctx->priv;
+    int nb_buff = s->difford + 1;
+    int b, p;
+
+    av_log(ctx, AV_LOG_TRACE, "Allocating %d buffer(s) for grey edge.\n", nb_buff);
+    for (b = 0; b <= nb_buff; ++b) { // We need difford + 1 buffers
+        for (p = 0; p < NUM_PLANES; ++p) {
+            td->data[b][p] = av_mallocz_array(s->planeheight[p] * s->planewidth[p], sizeof(*td->data[b][p]));
+            if (!td->data[b][p]) {
+                cleanup_derivative_buffers(td, b + 1, p);
+                av_log(ctx, AV_LOG_ERROR, "Out of memory while allocating derivatives buffers.\n");
+                return AVERROR(ENOMEM);
+            }
+        }
+    }
+    return 0;
+}
+
+#define CLAMP(x, mx) av_clip((x), 0, (mx-1))
+#define INDX2D(r, c, w) ( (r) * (w) + (c) )
+#define GAUSS(s, sr, sc, sls, sh, sw, g) ( (s)[ INDX2D(CLAMP((sr), (sh)), CLAMP((sc), (sw)), (sls)) ] * (g) )
+
+/**
+ * Slice calculation of gaussian derivatives. Applies 1-D gaussian derivative filter
+ * either horizontally or vertically according to meta data given in thread data.
+ * When convoluting horizontally source is always the in frame withing thread data
+ * while when convoluting vertically source is a buffer.
+ *
+ * @param ctx the filter context.
+ * @param arg data to be passed between threads.
+ * @param jobnr current job nubmer.
+ * @param nb_jobs total number of jobs.
+ *
+ * @return 0.
+ */
+static int slice_get_derivative(AVFilterContext* ctx, void* arg, int jobnr, int nb_jobs)
+{
+    ColorConstancyContext *s = ctx->priv;
+    ThreadData *td = arg;
+    AVFrame *in = td->in;
+    const int ord = td->meta_data[INDEX_ORD];
+    const int dir = td->meta_data[INDEX_DIR];
+    const int src_index  = td->meta_data[INDEX_SRC];
+    const int dst_index  = td->meta_data[INDEX_DST];
+    const int filtersize = s->filtersize;
+    const double *gauss  = s->gauss[ord];
+    int plane;
+
+    for (plane = 0; plane < NUM_PLANES; ++plane) {
+        const int height      = s->planeheight[plane];
+        const int width       = s->planewidth[plane];
+        const int in_linesize = in->linesize[plane];
+        double *dst = td->data[dst_index][plane];
+        int slice_start, slice_end;
+        int r, c, g;
+
+        if (dir == DIR_X) {
+            /** Applying gauss horizontally along each row */
+            const uint8_t *src = in->data[plane];
+            slice_start = (height * jobnr      ) / nb_jobs;
+            slice_end   = (height * (jobnr + 1)) / nb_jobs;
+
+            for (r = slice_start; r < slice_end; ++r) {
+                for (c = 0; c < width; ++c) {
+                    dst[INDX2D(r, c, width)] = 0;
+                    for (g = 0; g < filtersize; ++g) {
+                        dst[INDX2D(r, c, width)] += GAUSS(src, r,                        c + GINDX(filtersize, g),
+                                                          in_linesize, height, width, gauss[GINDX(filtersize, g)]);
+                    }
+                }
+            }
+        } else {
+            /** Applying gauss vertically along each column */
+            const double *src = td->data[src_index][plane];
+            slice_start = (width * jobnr      ) / nb_jobs;
+            slice_end   = (width * (jobnr + 1)) / nb_jobs;
+
+            for (c = slice_start; c < slice_end; ++c) {
+                for (r = 0; r < height; ++r) {
+                    dst[INDX2D(r, c, width)] = 0;
+                    for (g = 0; g < filtersize; ++g) {
+                        dst[INDX2D(r, c, width)] += GAUSS(src, r + GINDX(filtersize, g), c,
+                                                          width, height, width, gauss[GINDX(filtersize, g)]);
+                    }
+                }
+            }
+        }
+
+    }
+    return 0;
+}
+
+/**
+ * Slice Frobius normalization of gaussian derivatives. Only called for difford values of
+ * 1 or 2.
+ *
+ * @param ctx the filter context.
+ * @param arg data to be passed between threads.
+ * @param jobnr current job nubmer.
+ * @param nb_jobs total number of jobs.
+ *
+ * @return 0.
+ */
+static int slice_normalize(AVFilterContext* ctx, void* arg, int jobnr, int nb_jobs)
+{
+    ColorConstancyContext *s = ctx->priv;
+    ThreadData *td = arg;
+    const int difford = s->difford;
+    int plane;
+
+    for (plane = 0; plane < NUM_PLANES; ++plane) {
+        const int height = s->planeheight[plane];
+        const int width  = s->planewidth[plane];
+        const int64_t numpixels = width * (int64_t)height;
+        const int slice_start   = (numpixels * jobnr    ) / nb_jobs;
+        const int slice_end     = (numpixels * (jobnr+1)) / nb_jobs;
+        const double *dx = td->data[INDEX_DX][plane];
+        const double *dy = td->data[INDEX_DY][plane];
+        double *norm = td->data[INDEX_NORM][plane];
+        int i;
+
+        if (difford == 1) {
+            for (i = slice_start; i < slice_end; ++i) {
+                norm[i] = sqrt( pow(dx[i], 2) + pow(dy[i], 2));
+            }
+        } else {
+            const double *dxy = td->data[INDEX_DXY][plane];
+            for (i = slice_start; i < slice_end; ++i) {
+                norm[i] = sqrt( pow(dx[i], 2) + 4 * pow(dxy[i], 2) + pow(dy[i], 2) );
+            }
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Utility function for setting up differentiation data/metadata.
+ *
+ * @param ctx the filter context.
+ * @param td to be used for passing data between threads.
+ * @param ord ord of differentiation.
+ * @param dir direction of differentiation.
+ * @param src index of source used for differentiation.
+ * @param dst index destination used for saving differentiation result.
+ * @param dim maximum dimension in current direction.
+ * @param nb_threads number of threads to use.
+ */
+static void av_always_inline
+get_deriv(AVFilterContext *ctx, ThreadData *td, int ord, int dir,
+          int src, int dst, int dim, int nb_threads) {
+    td->meta_data[INDEX_ORD] = ord;
+    td->meta_data[INDEX_DIR] = dir;
+    td->meta_data[INDEX_SRC] = src;
+    td->meta_data[INDEX_DST] = dst;
+    ctx->internal->execute(ctx, slice_get_derivative, td, NULL, FFMIN(dim, nb_threads));
+}
+
+/**
+ * Main control function for calculating gaussian derivatives.
+ *
+ * @param ctx the filter context.
+ * @param td holds the buffers used for storing results.
+ *
+ * @return 0 in case of success, a negative value corresponding to an
+ * AVERROR code in case of failure.
+ */
+static int get_derivative(AVFilterContext *ctx, ThreadData *td)
+{
+    ColorConstancyContext *s = ctx->priv;
+    int nb_threads = s->nb_threads;
+    int height = s->planeheight[1];
+    int width  = s->planewidth[1];
+
+    switch(s->difford) {
+    case 0:
+        if (!s->sigma) { // Only copy once
+            get_deriv(ctx, td, 0, DIR_X, 0         , INDEX_NORM, height, nb_threads);
+        } else {
+            get_deriv(ctx, td, 0, DIR_X, 0,          INDEX_TEMP, height, nb_threads);
+            get_deriv(ctx, td, 0, DIR_Y, INDEX_TEMP, INDEX_NORM, width , nb_threads);
+            // save to INDEX_NORM because this will not be normalied and
+            // end gry edge filter expects result to be found in INDEX_NORM
+        }
+        return 0;
+
+    case 1:
+        get_deriv(ctx, td, 1, DIR_X, 0,          INDEX_TEMP, height, nb_threads);
+        get_deriv(ctx, td, 0, DIR_Y, INDEX_TEMP, INDEX_DX,   width , nb_threads);
+
+        get_deriv(ctx, td, 0, DIR_X, 0,          INDEX_TEMP, height, nb_threads);
+        get_deriv(ctx, td, 1, DIR_Y, INDEX_TEMP, INDEX_DY,   width , nb_threads);
+        return 0;
+
+    case 2:
+        get_deriv(ctx, td, 2, DIR_X, 0,          INDEX_TEMP, height, nb_threads);
+        get_deriv(ctx, td, 0, DIR_Y, INDEX_TEMP, INDEX_DX,   width , nb_threads);
+
+        get_deriv(ctx, td, 0, DIR_X, 0,          INDEX_TEMP, height, nb_threads);
+        get_deriv(ctx, td, 2, DIR_Y, INDEX_TEMP, INDEX_DY,   width , nb_threads);
+
+        get_deriv(ctx, td, 1, DIR_X, 0,          INDEX_TEMP, height, nb_threads);
+        get_deriv(ctx, td, 1, DIR_Y, INDEX_TEMP, INDEX_DXY,  width , nb_threads);
+        return 0;
+
+    default:
+        av_log(ctx, AV_LOG_ERROR, "Unsupported difford value: %d.\n", s->difford);
+        return AVERROR(EINVAL);
+    }
+
+}
+
+/**
+ * Slice function for grey edge algorithm that does partial summing/maximizing
+ * of gaussian derivatives.
+ *
+ * @param ctx the filter context.
+ * @param arg data to be passed between threads.
+ * @param jobnr current job nubmer.
+ * @param nb_jobs total number of jobs.
+ *
+ * @return 0.
+ */
+static int filter_slice_grey_edge(AVFilterContext* ctx, void* arg, int jobnr, int nb_jobs)
+{
+    ColorConstancyContext *s = ctx->priv;
+    ThreadData *td = arg;
+    AVFrame *in    = td->in;
+    int minknorm   = s->minknorm;
+    const uint8_t thresh = 255;
+    int plane;
+
+    for (plane = 0; plane < NUM_PLANES; ++plane) {
+        const int height        = s->planeheight[plane];
+        const int width         = s->planewidth[plane];
+        const int in_linesize   = in->linesize[plane];
+        const int slice_start   = (height * jobnr) / nb_jobs;
+        const int slice_end     = (height * (jobnr+1)) / nb_jobs;
+        const uint8_t *img_data = in->data[plane];
+        const double *src       = td->data[INDEX_NORM][plane];
+        double *dst             = td->data[INDEX_DST][plane];
+        int r, c;
+
+        dst[jobnr] = 0;
+        if (!minknorm) {
+            for (r = slice_start; r < slice_end; ++r) {
+                for (c = 0; c < width; ++c) {
+                    dst[jobnr] = FFMAX( dst[jobnr], fabs(src[INDX2D(r, c, width)])
+                                        * (img_data[INDX2D(r, c, in_linesize)] < thresh) );
+                }
+            }
+        } else {
+            for (r = slice_start; r < slice_end; ++r) {
+                for (c = 0; c < width; ++c) {
+                    dst[jobnr] += ( pow( fabs(src[INDX2D(r, c, width)] / 255.), minknorm)
+                                    * (img_data[INDX2D(r, c, in_linesize)] < thresh) );
+                }
+            }
+        }
+    }
+    return 0;
+}
+
+/**
+ * Main control function for grey edge algorithm.
+ *
+ * @param ctx the filter context.
+ * @param in frame to perfrom grey edge on.
+ *
+ * @return 0 in case of success, a negative value corresponding to an
+ * AVERROR code in case of failure.
+ */
+static int filter_grey_edge(AVFilterContext *ctx, AVFrame *in)
+{
+    ColorConstancyContext *s = ctx->priv;
+    ThreadData td;
+    int minknorm  = s->minknorm;
+    int difford   = s->difford;
+    double *white = s->white;
+    int nb_jobs   = FFMIN3(s->planeheight[1], s->planewidth[1], s->nb_threads);
+    int plane, job, ret;
+
+    td.in = in;
+    ret = setup_derivative_buffers(ctx, &td);
+    if (ret) {
+        return ret;
+    }
+    get_derivative(ctx, &td);
+    if (difford > 0) {
+        ctx->internal->execute(ctx, slice_normalize, &td, NULL, nb_jobs);
+    }
+
+    ctx->internal->execute(ctx, filter_slice_grey_edge, &td, NULL, nb_jobs);
+    if (!minknorm) {
+        for (plane = 0; plane < NUM_PLANES; ++plane) {
+            white[plane] = 0; // All values are absolute
+            for (job = 0; job < nb_jobs; ++job) {
+                white[plane] = FFMAX(white[plane] , td.data[INDEX_DST][plane][job]);
+            }
+        }
+    } else {
+        for (plane = 0; plane < NUM_PLANES; ++plane) {
+            white[plane] = 0;
+            for (job = 0; job < nb_jobs; ++job) {
+                white[plane] += td.data[INDEX_DST][plane][job];
+            }
+            white[plane] = pow(white[plane], 1./minknorm);
+        }
+    }
+
+    cleanup_derivative_buffers(&td, difford + 1, NUM_PLANES);
+    return 0;
+}
+
+/**
+ * Normalizes estimated illumination since only illumination vector
+ * direction is required for color constancy.
+ *
+ * @param light the estimated illumination to be normalized in place
+ */
+static void normalize_light(double *light)
+{
+    double abs_val = pow( pow(light[0], 2.0) + pow(light[1], 2.0) + pow(light[2], 2.0), 0.5);
+    int plane;
+
+    // TODO: check if setting to 1.0 when estimated = 0.0 is the best thing to do
+
+    if (!abs_val) {
+        for (plane = 0; plane < NUM_PLANES; ++plane) {
+            light[plane] = 1.0;
+        }
+    } else {
+        for (plane = 0; plane < NUM_PLANES; ++plane) {
+            light[plane] = (light[plane] / abs_val);
+            if (!light[plane]) { // to avoid division by zero when correcting
+                light[plane] = 1.0;
+            }
+        }
+    }
+}
+
+/**
+ * Redirects to corresponding algorithm estimation function and performs normalization
+ * after estimation.
+ *
+ * @param ctx the filter context.
+ * @param in frame to perfrom estimation on.
+ *
+ * @return 0 in case of success, a negative value corresponding to an
+ * AVERROR code in case of failure.
+ */
+static int illumination_estimation(AVFilterContext *ctx, AVFrame *in)
+{
+    ColorConstancyContext *s = ctx->priv;
+    int ret;
+
+    ret = filter_grey_edge(ctx, in);
+
+    av_log(ctx, AV_LOG_DEBUG, "Estimated illumination= %f %f %f\n",
+           s->white[0], s->white[1], s->white[2]);
+    normalize_light(s->white);
+    av_log(ctx, AV_LOG_DEBUG, "Estimated illumination after normalization= %f %f %f\n",
+           s->white[0], s->white[1], s->white[2]);
+
+    return ret;
+}
+
+/**
+ * Performs simple correction via diagonal transformation model.
+ *
+ * @param ctx the filter context.
+ * @param arg data to be passed between threads.
+ * @param jobnr current job nubmer.
+ * @param nb_jobs total number of jobs.
+ *
+ * @return 0.
+ */
+static int diagonal_transformation(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    ColorConstancyContext *s = ctx->priv;
+    ThreadData *td = arg;
+    AVFrame *in = td->in;
+    AVFrame *out = td->out;
+    double sqrt3 = pow(3.0, 0.5);
+    int plane;
+
+    for (plane = 0; plane < NUM_PLANES; ++plane) {
+        const int height = s->planeheight[plane];
+        const int width  = s->planewidth[plane];
+        const int64_t numpixels = width * (int64_t)height;
+        const int slice_start   = (numpixels * jobnr) / nb_jobs;
+        const int slice_end     = (numpixels * (jobnr+1)) / nb_jobs;
+        const uint8_t *src = in->data[plane];
+        uint8_t *dst       = out->data[plane];
+        double temp;
+        unsigned i;
+
+        for (i = slice_start; i < slice_end; ++i) {
+            temp = src[i] / (s->white[plane] * sqrt3);
+            dst[i] = av_clip_uint8((int)(temp + 0.5));
+        }
+    }
+    return 0;
+}
+
+/**
+ * Main control function for correcting scene illumination based on
+ * estimated illumination.
+ *
+ * @param ctx the filter context.
+ * @param in holds frame to correct
+ * @param out holds corrected frame
+ */
+static void chromatic_adaptation(AVFilterContext *ctx, AVFrame *in, AVFrame *out)
+{
+    ColorConstancyContext *s = ctx->priv;
+    ThreadData td;
+    int nb_jobs = FFMIN3(s->planeheight[1], s->planewidth[1], s->nb_threads);
+
+    td.in  = in;
+    td.out = out;
+    ctx->internal->execute(ctx, diagonal_transformation, &td, NULL, nb_jobs);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    static const enum AVPixelFormat pix_fmts[] = {
+        // TODO: support more formats
+        // FIXME: error when saving to .jpg
+        AV_PIX_FMT_GBRP,
+        AV_PIX_FMT_NONE
+    };
+
+    return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
+}
+
+static int config_props(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    ColorConstancyContext *s = ctx->priv;
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
+    const double break_off_sigma = 3.0;
+    double sigma = s->sigma;
+    int ret;
+
+    if (!sigma && s->difford) {
+        av_log(ctx, AV_LOG_ERROR, "Sigma can't be set to 0 when difford > 0.\n");
+        return AVERROR(EINVAL);
+    }
+
+    s->filtersize = 2 * floor(break_off_sigma * s->sigma + 0.5) + 1;
+    if (ret=set_gauss(ctx)) {
+        return ret;
+    }
+
+    s->nb_threads = ff_filter_get_nb_threads(ctx);
+    s->planewidth[1]  = s->planewidth[2]  = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
+    s->planewidth[0]  = s->planewidth[3]  = inlink->w;
+    s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
+    s->planeheight[0] = s->planeheight[3] = inlink->h;
+
+    return 0;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AVFilterLink *outlink = ctx->outputs[0];
+    AVFrame *out;
+    int ret;
+
+    ret = illumination_estimation(ctx, in);
+    if (ret) {
+        return ret;
+    }
+
+    if (av_frame_is_writable(in)) {
+        out = in;
+    } else {
+        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+        if (!out) {
+            av_log(ctx, AV_LOG_ERROR, "Out of memory while allocating output video buffer.\n");
+            return AVERROR(ENOMEM);
+        }
+        av_frame_copy_props(out, in);
+    }
+    chromatic_adaptation(ctx, in, out);
+
+    return ff_filter_frame(outlink, out);
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    ColorConstancyContext *s = ctx->priv;
+    int difford = s->difford;
+    int i;
+
+    for (i = 0; i <= difford; ++i) {
+        av_freep(&s->gauss[i]);
+    }
+}
+
+static const AVFilterPad colorconstancy_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .config_props = config_props,
+        .filter_frame = filter_frame,
+    },
+    { NULL }
+};
+
+static const AVFilterPad colorconstancy_outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_VIDEO,
+    },
+    { NULL }
+};
+
+#if CONFIG_GREYEDGE_FILTER
+
+static const AVOption greyedge_options[] = {
+    { "difford",  "set differentiation order", OFFSET(difford),  AV_OPT_TYPE_INT,    {.i64=1},   0,   2,      FLAGS },
+    { "minknorm", "set Minkowski norm",        OFFSET(minknorm), AV_OPT_TYPE_INT,    {.i64=1},   0,   65535,  FLAGS },
+    { "sigma",    "set sigma",                 OFFSET(sigma),    AV_OPT_TYPE_DOUBLE, {.dbl=1},   0.0, 1024.0, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(greyedge);
+
+AVFilter ff_vf_greyedge = {
+    .name          = GREY_EDGE,
+    .description   = NULL_IF_CONFIG_SMALL("Estimates scene illumination by grey edge assumption."),
+    .priv_size     = sizeof(ColorConstancyContext),
+    .priv_class    = &greyedge_class,
+    .query_formats = query_formats,
+    .uninit        = uninit,
+    .inputs        = colorconstancy_inputs,
+    .outputs       = colorconstancy_outputs,
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
+};
+
+#endif /* CONFIG_GREY_EDGE_FILTER */