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:
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: