Diagnosing Wheel Imbalance with a GoPro and FFmpeg | joshfrye.com
Diagnosing Wheel Imbalance with a GoPro and FFmpeg | joshfrye.com
There is nothing quite as annoying as a highway-speed vibration that you just can’t track down. You’ve had the wheels balanced, the alignment checked, and yet, at 70 MPH, the steering wheel still dances.
Recently, I decided to stop guessing and start measuring. Using a GoPro mounted under the car and some ffmpeg magic, I was able to visually isolate exactly which wheel was causing the problem.
Here is the nerd write-up on how to use frame differencing to diagnose mechanical vibrations.
The Setup
I mounted a GoPro to the frame of the car, pointed at the suspension assembly. The goal was to capture high-frame-rate footage (60fps or higher is best) of the wheel while driving at the speed where the vibration is most prominent.
Once I had the footage, the real work began.
Phase 1: Metadata and Overview
First, we need to understand what we’re working with. ffprobe is the go-to for extracting metadata.
ffprobe -v error -show_format -show_streams input_video.mov
I also like to generate a contact sheet of thumbnails to quickly find the sections of video where I was actually at highway speed.
mkdir -p thumbnails<br>ffmpeg -i input_video.mov -vf "fps=1/60" thumbnails/thumb_%03d.jpg
Phase 2: Visual Evidence
Before diving into advanced filters, a simple scaled-down GIF can help confirm you have the right angle.
ffmpeg -ss 00:05:30 -t 5 -i input_video.mov -vf "scale=480:-1" -r 15 -f gif observation.gif
Phase 3: Advanced Diagnosis (Vibration Isolation)
This is where it gets interesting. To prove that a movement is a mechanical vibration rather than just a road bump , we use Frame Differencing .
Road bumps are irregular and usually affect the whole frame. A mechanical imbalance is periodic and constant. By subtracting the previous frame from the current one, we can highlight only the pixels that are changing.
The Magic Command
ffmpeg -y -ss [start_time] -t 3 -i input_video.mov \<br>-vf "format=gray, tblend=all_mode=difference, scale=480:-1" \<br>-r 15 -f gif vibration_diff.gif
format=gray: Strips color to focus on intensity.
tblend=all_mode=difference: The core logic. It highlights changes between consecutive frames.
Interpreting the Results
In a difference map:
Road Bumps: Appear as sudden, temporary flashes of the whole assembly.
Mechanical Vibration: Appears as a constant “ghosting” or “fuzziness” around the edges of the wheel and suspension.
Driver Side (The Control)
For comparison, here is the driver side. The edges are much cleaner, and the “ghosting” is significantly reduced.
The difference map is mostly black until the end when you see the effect of hitting a bump.
Passenger Side (The Problem)
Before applying any noise reduction, a simple difference map shows the passenger side, but it is extremely noisy due to compression artifacts and camera sensor noise:
A simple frame difference without thresholding is flooded with low-level noise.
To clean this up and isolate the true mechanical vibration, we apply a threshold to the filter chain. By adding lutyuv=y='if(gt(val,20),val,0)' (or if(gt(val,20),255,0) for binary contrast), we drop any pixel intensity change below a value of 20 to zero (black).
Here is the much cleaner result highlighting only the persistent high-frequency oscillation around the tire and control arms:
The thresholded difference map cleanly highlights the mechanical oscillation.
The Final Result: Visual Telemetry
By overlaying the difference map back onto the original footage and colorizing it (magenta, in this case), we get what I call “visual telemetry.”
To make this effective, I used a threshold filter to strip away low-level road noise and only highlight the most intense mechanical vibrations.
The Visual Telemetry Command
This is the multi-step filter chain required to isolate the movement and paint it magenta:
ffmpeg -y -i observation.gif -i vibration_diff.gif -filter_complex \<br>"[1:v]format=gray,lutyuv=y='if(gt(val,20),255,0)',format=rgb24,lutrgb=g=0[magenta_mask]; \<br>[0:v][magenta_mask]blend=all_mode=lighten,split[s0][s1]; \<br>[s0]palettegen[p]; [s1][p]paletteuse" vibration_overlay.gif
How it works:
Thresholding: lutyuv=y='if(gt(val,20),255,0)' takes the grayscale difference map and turns any pixel with a value over 20 into pure white, and anything else into pure black. This removes the “noise” of small road bumps.
Colorizing: lutrgb=g=0 strips the green channel from the white mask, leaving us with solid magenta.
Blending: blend=all_mode=lighten overlays our magenta highlights onto the original footage, only keeping the magenta pixels if they are brighter than the background (which they are).
Notice how the magenta “glow” is concentrated on the tire tread and the suspension components. This is the smoking gun for a mechanical imbalance. Since we’ve thresholded the output, only the high-frequency oscillation from the imbalance is...