This tool is for fixing a very specific horizontal shaking issue that affects some digitized VHS videos.
Input example:
AD13-E.original.mp4
vhs-deshaker output:
AD13-E.deshaked.mp4
I do not know what causes this type of horizontal shaking. I first encountered it when digitizing some of our old VHS cassettes with family/childhood videos. Some cassettes were more affected than others, suggesting a mechanical/physical problem.
Unfortunately I was unable to fix our videos with traditional video deshaking software. So I decided to try and create my own algorithm for fixing this nasty issue.
My algorithm analyzes the pure black at the left and right borders of VHS video frames. Ideally the pure black area should always have the same width. In my VHS videos, it should have been 8px on both left and right side of the frames. But due to horizontal shaking and distortions the actual width differed greatly between and within frames. vhs-deshaker attempts to reverse these deviations by realigning the rows of the video frames such that all frames start at the same column.
To achieve that, vhs-deshaker first applies edge detection to the pure black areas at the left and right borders of VHS videos to determine the horizontal shift for each line:
These shifts, actually described as line_starts (from left side of image) and line_ends (from right side of image), are then denoised. Denoising means that very small consecutive segments of line_starts are removed:
The next step is to merge the information from the line_starts and the line_ends. For rows with both line_start and line_end information, the larger consecutive segment wins because it is considered more reliable:
There are still many rows for which no line_start data is available. These gaps are filled by linear interpolation (for inner gaps) and extrapolated via nearest-known-value replication (for outer gaps):
Finally, the line_starts are smoothed. This smoothes any remaining discontinuities in the line_starts and thus ensures that neighboring lines are always shifted by a very similar amount:
This data is then used to shift/realign all rows of the frame.
You can download the latest binary from the releases page. The accompanying opencv DLL files must be kept in the same folder as the executable.
It is recommended to also install the ffmpeg binaries on your system and edit your PATH environment variable such that they can be executed from the commandline. (ffmpeg is needed to add back the audio stream to the output files, see below.)
In general the command can be executed like this:
vhs-deshaker -i <input-file> -o <output-file> [OPTION...]
The following options are supported:
-i, --input arg Input video
-o, --output arg Output video
-f, --framerate arg Enforce this framerate for the output video
-c, --colrange arg Column range, -1 = use double the value
given by -w (default: -1)
-t, --target-line-start arg Target line start, -1 = use same value as
given by -w (default: -1)
-w, --pure-black-width arg Pure black area width (default: 8)
-p, --pure-black-threshold arg
Pure black threshold (default: 20)
-m, --min-line-start-segment-length arg
Min line start segment length (default: 15)
-k, --line-start-smoothing-kernel-size arg
Line start smoothing kernel size (default:
51)
-h, --help Print usage
The most important parameter of all is -w
/ --pure-black-width
. A wrong -w
value can increase shaking. To get good results you have to measure the width of
the pure black borders on the left and right side of your video. If your video is very shaky you will likely have to estimate/guess this width
and/or just try multiple values until you converge to the best setting.
The parameter -p
/ --pure-black-threshold
is also fairly important because it is used to determine if a border pixel belongs to the pure black area or to the actual
content of your video. Measure the brightness/intensity (grayscale value) of your video's border pixels and then add a small "margin of safety" to this number. This
will be the ideal value for -p
. The "margin of safety" should be picked a little larger if your video is very noisy.
Unfortunately vhs-deshaker can only process video streams. The audio will not be included in the output file. Therefore you have to add back the audio stream manually to the output file. Furthermore, you should know that vhs-shaker uses the lossless HuffYUV video codec to generate the output file. Therefore the output files will be huge and you should make sure your disk has enough free space. Also, the output files must have .avi format / extension because mp4 does not support the HuffYUV codec.
I recommend to use ffmpeg to add back the audio stream to the deshaked video file. For example:
# Remove horizontal shaking
vhs-deshaker.exe -i input.avi -o deshaked.avi
# Add back the audio stream and recode the video stream from HuffYUV to H.264
ffmpeg -vn -i input.avi -an -i deshaked.avi -c:a copy -pix_fmt yuv420p final.mp4
The videos generated by vhs-deshaker are using the HuffYUV codec with full colorspace (YUV 4:4:4). By default ffmpeg would thus generate a H.264 encoded video with
the same YUV 4:4:4 colorspace. I recommend to overwrite this with the -pix_fmt yuv420p
option for better filesize and - most importantly - device compatibility,
since many devices do not support H.264 with YUV 4:4:4.
For details on how to use ffmpeg to mux audio/video streams you can read this StackOverflow post: https://stackoverflow.com/a/12943003/623685
Raw video output to stdout can be enabled by specifying stdout
as output file. You can use this to pipe the raw video data directly to ffmpeg.
Example:
vhs-deshaker -i input.avi -o stdout | ffmpeg -f rawvideo -c:v rawvideo -s 720x564 -pix_fmt bgr24 -r 50 -i pipe: -pix_fmt yuv420p deshaked.mp4
Please note you have to:
- specify the correct video resolution in the call to ffmpeg (e.g.
-s 720x564
). - specify the framerate in the call to ffmpeg (e.g.
-r 50
).
Advantages of piping to ffmpeg:
- You do not have to keep around an intermediate video file that is extremely large due to the lossless HuffYUV codec.
- You can deshake and merge the audio stream from the original input file in a single step (not shown in the above example).
See BUILD.md.
You can also run vhs-deshaker via docker. I provide a docker image at rsnitsch/vhs-deshaker
.
Example command:
docker run -it --rm -v "$(pwd):/videos" --user $(id -u):$(id -g) rsnitsch/vhs-deshaker:latest -i <input-file> -o <output-file> [OPTION...]
If the input file cannot be opened, double-check that your input file is actually visible within docker. The option -v "$(pwd):/videos"
should map the current working directory (on your host system) inside the docker container at /videos
(which is the working directory
of vhs-deshaker inside the docker container).
You can use the following command to list the files visible inside the vhs-deshaker docker container at /videos
:
docker run --entrypoint ls -it --rm -v "$(pwd):/videos" --user $(id -u):$(id -g) rsnitsch/vhs-deshaker:latest -Al
This executes ls -Al
inside the docker container which lists the files in /videos
. You should see the same contents as in your
current working directory on your host system. If not, then you have to find out why and fix this first.
Online threads discussing the issue: