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:

  • X and Y: The horizontal and vertical frequency sample indices.
  • W and H: The width and height of the frequency domain plane.
  • weight_y: The expression calculating the multiplier (typically between 0.0 and 1.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 to 128 maps zero frequency to a neutral 8-bit gray, preventing clipping of negative spatial values caused by removing low frequencies. Setting it to 0 leaves 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.png

To 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 30 increases the cutoff threshold, removing more mid-range frequencies and preserving only extremely sharp details.
  • Decreasing 30 allows 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