FFmpeg eq filter complex: Contrast

I am playing with the eq filter in FFmpeg:

https://ffmpeg.org/ffmpeg-filters.html#eq

Using a command like:

ffmpeg -y -loop 1 -i input.jpg -filter_complex "[0:v]eq=1:0:1:1:1:1:1:1[outv]" -map [outv] -c:v libx264 -t 3 -pix_fmt yuv420p out.mp4 # does nothing

The documentation suggests that the first component of the filter is contrast:

Set the contrast expression. The value must be a float value in range -2.0 to 2.0. The default value is "0".

However, I found that for no change in contrast to happen, the value should be "1". Shouldn't this be the default?

Anyway, I am confused if this first value is even contrast. It doesn't behave as I would expect contrast to. I am comparing it to e.g. css -webkit-filter: contrast(x). In CSS, contrast(0) makes the entire image grey. However, in FFmpeg, the image appears to be part yellow and part grey (presumably dependent on my image:

enter image description here

CSS contrast(-1) is invalid. However, in FFmpeg, contrast -1 is almost an inverted contrast. I understand the two things were implemented completely separately, but I would have expected an approximate relation. Am I misunderstanding the eq filters contrast value?


Solution 1:

You need to specify the options in the filter by name, so your original filter settings would need to be changed to...

-filter_complex "[0:v]eq=contrast=1:brightness=0:saturation=1:gamma=1:
gamma_r=1:gamma_g=1:gamma_b=1:gamma_weight=1[outv]"

...if you wanted set all of those parameters. Otherwise, the filter is going to ignore your settings and apply the default values, or it could possibly misinterpret one intended option's value for another (it shouldn't, but stranger things have happened with FFmpeg's filters).

As far as the selected values vs. end results go, the code slhck pointed out shows that the value one sets per option are run through a series of internal calculations, then the results are used to evaluate and make pixel-level adjustments. It appears that the "base" calculation for contrast is...

(param->contrast * 256 * 16)

...so the default value of 0 would result in 0, a specified value of 1 would result in 4096, a value of -0.00275 would result in -11.264, etc, and these base values are used in further calculations down the line. In other words, it would be best to consider the filter's handling of these parameters as unique, so spend some time playing around with them to see how they work. To get a true idea of the effects, you can tweak and survey the output of the eq settings using FFplay, e.g.:

ffplay -i input.jpg -vf "eq=contrast=1.5:brightness=-0.05:saturation=0.75"

As far as your original script goes, since you were using only one input (your jpeg), one filter (eq), and all options except contrast were carrying default values, you can reduce the script down to the following to get your 3-second MP4, providing that eq=contrast=1 produces desirable results:

ffmpeg -y -loop 1 -i input.jpg -vf "eq=contrast=1" -c:v libx264 \
-pix_fmt yuv420p -t 3 out.mp4

full disclosure: post edited 2016/06/19 for greater clarification and extended info

Solution 2:

To answer your original question about contrast in FFmpeg vs. CSS, the code fragment below seems to suggest that contrast in FFmpeg only applies to luminance/luma (brightness), while saturation only applies to chrominance/chroma (colors).

static void set_contrast(EQContext *eq)
{
    eq->contrast = av_clipf(av_expr_eval(eq->contrast_pexpr, eq->var_values, eq), -1000.0, 1000.0);
    eq->param[0].contrast = eq->contrast;
    eq->param[0].lut_clean = 0;
    check_values(&eq->param[0], eq);
}

// ...

static void set_saturation(EQContext *eq)
{
    int i;

    eq->saturation = av_clipf(av_expr_eval(eq->saturation_pexpr, eq->var_values, eq), 0.0, 3.0);

    for (i = 1; i < 3; i++) {
        eq->param[i].contrast = eq->saturation;
        eq->param[i].lut_clean = 0;
        check_values(&eq->param[i], eq);
    }
}

Notice how in set_contrast, only param[0] (denoting the first color component, which in YUV is Y, the luma) is changed, while in set_saturation only param[1] and param[2] (denoting yellow and magenta, the chroma) are changed. This should account for the fact that you are seeing yellow and magenta, two chroma color components, when you decrease contrast to 0 in FFmpeg. When you set saturation to 0 also, I'm seeing a plain gray image similar to the one produced by CSS.

The relationship between CSS's contrast and saturate versus FFmpeg's contrast and saturation can be established as:

filter: contrast(c) saturate(s);

is equivalent to

eq=contrast=c:saturation=c*s

Obligatory screenshots showing c = 0.6 and s = 1.3:

CSS filter FFmpeg eq