FFmpeg Fish-Eye Correction with Remap Filter
This article explains how to correct fish-eye lens distortion in
video files using FFmpeg's remap filter alongside custom
coordinate maps. You will learn the mechanics of the remap
filter, how to generate custom X and Y displacement maps using Python
and OpenCV, and how to execute the final FFmpeg command to render
undistorted video.
Understanding the
remap Filter
The FFmpeg remap filter transforms an image by mapping
source pixels to new destination coordinates. It requires three distinct
video or image inputs:
- Input 0: The source video to be corrected.
- Input 1: The X-map, an image defining the horizontal source coordinate for each destination pixel.
- Input 2: The Y-map, an image defining the vertical source coordinate for each destination pixel.
The dimensions of the X and Y maps dictate the resolution of the
output video. Pixel values in these maps store coordinate data,
typically represented as 16-bit unsigned integers
(gray16le) to support pixel dimensions up to
65535×65535.
Step 1: Generating Custom Coordinate Maps
To un-distort fish-eye footage, compute an inverse mapping grid that transforms rectilinear destination coordinates back into distorted source coordinates. A script using Python, NumPy, and OpenCV generates the necessary 16-bit PGM map files based on lens calibration parameters.
import cv2
import numpy as np
# Output dimensions
width = 1920
height = 1080
# Camera matrix (fx, fy: focal lengths; cx, cy: principal point)
K = np.array([[800.0, 0.0, width / 2.0],
[0.0, 800.0, height / 2.0],
[0.0, 0.0, 1.0]], dtype=np.float32)
# Fish-eye distortion coefficients (k1, k2, k3, k4)
D = np.array([-0.05, 0.01, -0.005, 0.0], dtype=np.float32)
# Compute identity rectification and new camera matrix
R = np.eye(3, dtype=np.float32)
new_K = cv2.fisheye.estimateNewCameraMatrixForUndistortRectify(K, D, (width, height), np.eye(3), balance=0.0)
# Generate undistortion maps
map_x, map_y = cv2.fisheye.initUndistortRectifyMap(K, D, R, new_K, (width, height), cv2.CV_32FC1)
# Convert float coordinates to 16-bit unsigned integers
map_x_16 = np.clip(np.round(map_x), 0, 65535).astype(np.uint16)
map_y_16 = np.clip(np.round(map_y), 0, 65535).astype(np.uint16)
# Save as 16-bit PGM files
cv2.imwrite("xmap.pgm", map_x_16)
cv2.imwrite("ymap.pgm", map_y_16)This script outputs xmap.pgm and ymap.pgm,
which serve as look-up tables containing the source pixel indices for
every pixel in the corrected frame.
Step 2: Applying the Maps in FFmpeg
Run FFmpeg with the source video and the two generated
.pgm map files. Use the -lavfi flag to feed
all three streams into the remap filter:
ffmpeg -i input.mp4 -i xmap.pgm -i ymap.pgm -lavfi "[0:v][1:v][2:v]remap" -c:v libx264 -crf 18 -c:a copy output.mp4Map Generation and Command Considerations
Interpolation Modes: By default, the
remapfilter uses bilinear interpolation. You can explicitly set nearest-neighbor, bilinear, or bicubic interpolation using theinterpoption:-lavfi "[0:v][1:v][2:v]remap=interp=bicubic"Pixel Formats: Ensure that map files generated manually or without OpenCV are encoded in
gray16leformat so FFmpeg reads coordinate data precisely without downsampling artifacts.