I made this because I was trying to get usable undistorted video from a wide-angle Amazon fisheye camera, there are plenty of other solutoins out there but figured to give it a shot at making my own and hey it worked out, and specifically for wide angle cameras so figured to put it out there if it helps anyone.
This was the camera I used, but as long as you perform the calibration well any other wide angled camera should also work. https://www.amazon.com/dp/B07ZS75KZR
This is the website I used for my checkerboard in case you want to make your own: https://calib.io/pages/camera-calibration-pattern-generator
- Lists available USB cameras
- Records raw fisheye test videos
- Calibrates a fisheye / wide-angle camera using a ChArUco board
- Undistorts recorded videos
- Re-encodes the result into a web-compatible H.264 MP4
There is also a test video included in test_videos if you just want to try the undistortion script without recording your own video.
This is mainly intended for Windows users.
Run the activation/setup script:
.\activate_venv.ps1That should create/activate the Python environment and install the needed packages.
You should also have ffmpeg installed and available in your PATH if you want the final undistorted video to be broadly accepted by websites and video players.
Use this first if you do not know your camera index:
python list-cameras.pypython record-video.pyBy default, this records from DEVICE_ID = 1 at 640x480 and saves the output into test_videos.
Print the included ChArUco board PDF, or generate your own from calib.io.
Then run:
python fisheye_calibration.pyOptional audio guide:
python fisheye_calibration.py --audioYou can also choose a camera index:
python fisheye_calibration.py --device 1And choose where to save the calibration file:
python fisheye_calibration.py --output fisheye_calib_data.npzThe script walks through six capture phases: center flat, center tilted, image edges, image corners, close range, and far/mixed range. It aims for 60 captures total, with 10 per phase.
python fisheye_undistort.py test_videos\your_video.mp4This will create an output file next to the input video, usually named something like:
your_video_undistorted_h264.mp4You can also choose the output path:
python fisheye_undistort.py test_videos\your_video.mp4 -o output\undistorted.mp4Use a specific calibration file:
python fisheye_undistort.py test_videos\your_video.mp4 -c fisheye_calib_data.npzDisable the preview window:
python fisheye_undistort.py test_videos\your_video.mp4 --no-previewThe undistortion script writes a temporary AVI first, then uses ffmpeg to re-encode the final video as H.264 with yuv420p pixel format and +faststart, which makes the MP4 much more compatible with websites and players.
Camera index:
DEVICE_ID = 1Board settings:
SQUARES_X = 5
SQUARES_Y = 7
SQUARE_LENGTH = 0.030
MARKER_LENGTH = 0.022These must match your printed ChArUco board. The values are the number of chessboard squares, not inner corners.
Capture settings:
TARGET_CAPTURES = 60
COOLDOWN_TIME = 2.0
MIN_CHARUCO_CORNERS = 15Use more captures if your undistortion looks bad. For fisheye lenses, edge and corner coverage matters a lot.
DEVICE_ID = 1
WIDTH = 640
HEIGHT = 480
FPS = 30Change these if your camera uses a different index or supports a different resolution/FPS.
--balance 0.00.0 gives a more cropped image with fewer black borders.
1.0 keeps more field of view but may show more black edges.
--scale 1.0Adjust this if you want to experiment with the final field of view.
Calibration quality depends heavily on the poses you capture. Try to follow the guide it tells you how to get very varied shots.
If the undistorted video looks worse, the calibration data is probably weak. Re-run calibration and make sure the board appears in many different parts of the image.
That is basically it. Fisheye camera gets less fishy.

