High-Pass Filter Expressions for FFmpeg fftfilt
The FFmpeg fftfilt filter operates in the frequency
domain by performing a 2D Fast Fourier Transform (FFT) on video frames
or images, modifying frequency coefficients via user-defined
expressions, and performing an inverse FFT back to the spatial domain.
This article explains how to write custom mathematical expressions for
fftfilt to build high-pass image filters that attenuate
low-frequency content and preserve fine details, edges, and
textures.
Understanding
fftfilt Variables and Options
When writing expressions for fftfilt, FFmpeg evaluates
your formula for each frequency sample in the transform grid. The
primary parameters and variables include:
XandY: The horizontal and vertical frequency sample indices.WandH: The width and height of the frequency domain plane.weight_y: The expression calculating the multiplier (typically between0.0and1.0) applied to the luma channel frequency coefficients.weight_u/weight_v: The expressions for chroma channels.dc_y: The direct current (DC) coefficient for luma, representing the average brightness. Setting this to128maps zero frequency to a neutral 8-bit gray, preventing clipping of negative spatial values caused by removing low frequencies. Setting it to0leaves only high-frequency fluctuations centered at zero.
Distance Calculation in Frequency Space
A high-pass filter attenuates frequencies close to the center and
passes frequencies farther away. In FFmpeg's frequency representation
for fftfilt, the low-frequency center is positioned at
(W/2, H/2).
The Euclidean distance \(D(X, Y)\)
from the center can be calculated using the built-in hypot
function:
hypot(X - W/2, Y - H/2)
Normalized relative to the frame dimensions, this distance is represented as:
hypot((X - W/2)/W, (Y - H/2)/H)
Custom High-Pass Filter Expressions
1. Gaussian High-Pass Filter
The Gaussian high-pass filter provides a smooth transition between rejected and passed frequencies, preventing ringing artifacts (Gibbs phenomenon) around sharp edges.
The mathematical formula is:
\[H(u, v) = 1 - e^{-\frac{D(u, v)^2}{2 D_0^2}}\]
In FFmpeg expression syntax:
ffmpeg -i input.png -vf "fftfilt=dc_y=128:weight_y='1-exp(-4*PI*(pow((X-W/2)/W,2)+pow((Y-H/2)/H,2)))'" output.pngTo control the cutoff radius explicitly, introduce a scaling factor \(S\):
ffmpeg -i input.png -vf "fftfilt=dc_y=128:weight_y='1-exp(-pow(hypot(X-W/2,Y-H/2)/30,2))'" output.png- Increasing
30increases the cutoff threshold, removing more mid-range frequencies and preserving only extremely sharp details. - Decreasing
30allows more mid-range textures to pass through.
2. Ideal (Sharp Cutoff) High-Pass Filter
An ideal high-pass filter cuts off all frequencies within a radius \(R\) completely and passes everything outside that radius without modification.
Using FFmpeg's gte (greater-than-or-equal) conditional
function:
ffmpeg -i input.png -vf "fftfilt=dc_y=128:weight_y='gte(hypot(X-W/2,Y-H/2),25)'" output.png- Frequencies where the distance from the center is \(\ge 25\) receive a multiplier of
1.0. - Frequencies inside the radius receive
0.0.
Note: Ideal filters introduce visible ripple or ringing artifacts around contrast boundaries.
3. Butterworth High-Pass Filter
A Butterworth filter offers a balance between the sharpness of an ideal filter and the smoothness of a Gaussian filter, controlled by the order \(n\).
The formula for an order-1 Butterworth high-pass filter is:
\[H(u, v) = \frac{1}{1 + \left(\frac{D_0}{D(u, v)}\right)^{2n}}\]
In FFmpeg expression syntax with cutoff radius \(D_0 = 20\) and order \(n = 1\):
ffmpeg -i input.png -vf "fftfilt=dc_y=128:weight_y='1/(1+pow(20/max(hypot(X-W/2,Y-H/2),0.001),2))'" output.png- The
max(..., 0.001)prevents division by zero at the exact center coordinates(W/2, H/2).
Processing Color Channels
By default, modifying only weight_y leaves chroma
channels intact, which can cause color artifacts. To apply the same
high-pass filtering across all planes, pass identical expressions to
weight_u and weight_v, and set their DC
components to 128:
ffmpeg -i input.png -vf "fftfilt=dc_y=128:dc_u=128:dc_v=128:weight_y='1-exp(-pow(hypot(X-W/2,Y-H/2)/35,2))':weight_u='1-exp(-pow(hypot(X-W/2,Y-H/2)/35,2))':weight_v='1-exp(-pow(hypot(X-W/2,Y-H/2)/35,2))'" output.png