|
|
@@ -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 */ |