System designHow-to

Record a 12-bit grayscale video file without dropping to 8 bits

How to record 12-bit or 14-bit grayscale camera frames to a 16-bit video file with FFV1, the disk rate to budget, and the test that proves no bit was lost.

LumaSpec Vision11 min read Share on LinkedIn

On this page 11 sections
  1. 01Why a normal video file throws your bits away
  2. 02The decision: a lossless 16-bit master and an 8-bit copy
  3. 03What you need
  4. 04Encode the 16-bit sequence into an FFV1 video
  5. 05Worked example: disk rate and file size at 4000 × 3000 and 30 fps
  6. 06Pipe frames straight from the grab loop
  7. 07Prove the file kept every bit
  8. 08Common mistakes
  9. 09When an 8-bit file is the right answer
  10. 10Recording checklist
  11. 11Questions

Your camera streams 10-bit, 12-bit or 14-bit grayscale, and the moment those frames pass through a normal video export they collapse to 8 bits. The AVI and MP4 writers built into grab tools, editing suites and SDK examples target consumer video, and consumer video is 8 bits per sample. This guide sets up a 12-bit grayscale video file that keeps every level: which codec and container to use, an FFmpeg command that turns the 16-bit TIFF or PNG sequence you already save into a video, the disk rate to budget, and the round-trip test that proves no bit was lost. It is written for test engineers recording short clips of measurements, defects and motion for algorithm work.

Why a normal video file throws your bits away

A video file has two layers, and the 8-bit ceiling lives in only one of them. The container (AVI, MP4, MKV) is the box. The codec is the compression format written inside the box, and the MPEG-4 family codecs that populate AVI and MP4 files store 8 bits per sample. A 12-bit frame pushed through such a codec arrives as 8 bits, whatever the file extension says.

The cost is set by the sensor's bit depth:

levels merged into one output step = 2 ^ bits dropped resolution loss in dB ≈ 6.02 × bits dropped

Going from 12 bits to 8 drops 4 bits: each surviving step then covers 16 sensor levels, a 24 dB loss of intensity resolution. From 14 bits it is 6 bits, 64 levels, 36 dB. From a full 16-bit word it is 256 levels, 48 dB. Any threshold, ratio or contrast number measured from that copy is 16× to 256× coarser than what the sensor delivered. If you are still weighing whether the deep stream is worth carrying, machine vision camera bit depth: choosing 8, 10 or 12 bits walks that trade, and ADC bit depth vs pixel format explains why a 10-bit sensor can still ship 8-bit images out of a default pixel format.

The decision: a lossless 16-bit master and an 8-bit copy

Record one file that cannot lose data and derive everything else from it. The master is FFV1 video, gray16 pixel format, in an MKV or MOV container: FFV1 is a lossless codec that stores 16-bit grayscale words directly, so decode returns the exact values you encoded. The review copy is an 8-bit MP4 exported from the master, which plays anywhere and is enough for eyes. The one thing not to do is archive in the review format, because MPEG-4 family output is exactly the path that forces the 8-bit conversion.

Recording path Depth kept Lossless Size Reads in
MP4 or AVI with MPEG-4 family codecs 8 bits No Small Everywhere
FFV1 in MKV or MOV 16 bits Yes Compressed, noise-dependent FFmpeg-based tools
Raw gray16, uncompressed 16 bits Yes Largest A few readers, layout-specific
16-bit TIFF or PNG sequence 16 bits Yes Largest Any image tool

Uncompressed 16-bit grayscale AVI does exist, but only a few reader libraries list it among the formats they accept, which is why FFV1 in MKV or MOV is the portable carrier. If the recording step runs on an embedded encoder box instead of a workstation, its hardware encoder takes different settings; selecting lossless encoding for grayscale video on such hardware walks that choice.

What you need

  • An area scan camera streaming a mono pixel format deeper than 8 bits. Browse the area scan cameras or the camera finder, and judge by the pixel format list, not the megapixel count.
  • Your frames at full depth: a 16-bit TIFF or PNG sequence from the grab software, or the 2-byte pixel buffer from the SDK. Saving 12-bit camera images without losing intensity data and how to save 16-bit grayscale camera images reliably cover the still-image half.
  • FFmpeg on the recording machine. Any recent build carries the FFV1 encoder and the gray16 formats.
  • The word layout of your deep data. Most SDKs deliver deep mono samples left-justified in 16-bit words — a 12-bit sensor's full-scale 4095 arrives as 4095 × 16 = 65440 — while some deliver two pixels packed into three bytes. The recorder must match the layout the SDK hands over.
  • Disk sized from the worked example below.

These USB3 models cover 6 MP, 12 MP and 20 MP sensors, whose full-resolution frame rates set the disk arithmetic in the worked example:

Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
$560.00View part
Area scan camerasHikrobot MV-CS200-10UM 20 MP Area Scan Camera, USB3.0, IMX183, 2nd Gen., MonoMV-CS200-10UM
Sensor format
1"
Lens mount
C-mount
$759.00View part
Area scan camerasHikrobot MV-CT0MU0060/R/NN/NN 6 MP Area Scan Camera, 3rd Gen., USB3.0, Mono, BASEMV-CT0MU0060/R/NN/NN
Sensor format
1/1.8"
Lens mount
C-mount
$969.00View part

Encode the 16-bit sequence into an FFV1 video

  1. Read one source frame's actual values in your image tool. A 12-bit payload scaled into the full word peaks near 65440; unscaled 12-bit data peaks at or below 4095. Note which you have. Nothing downstream changes these values, so the recording is faithful either way.
  2. Set the input rate to the rate the sequence was saved at — not the camera rate if you skipped frames, and not your display refresh.
  3. Encode the sequence:
ffmpeg -framerate 30 -pattern_type glob -i 'frame_*.png' -c:v ffv1 -pix_fmt gray16le recording.mkv

The switches: -framerate 30 sets the input rate, the glob picks up the numbered files, -c:v ffv1 selects the lossless codec, and -pix_fmt gray16le stores each sample as a 16-bit word. The same command reads a 16-bit TIFF series — change the pattern to '*.tif'. On shells without glob support, use a printf-style pattern such as frame_%04d.png.

  1. Export the review copy from the master, never from the camera:
ffmpeg -i recording.mkv -c:v libx264 -pix_fmt yuv420p review.mp4

Reports, players and colleagues get this file; measurements stay in the MKV.

  1. Archive the pair, and keep the image sequence until the project closes. The video is the deliverable; the sequence is the only form every tool can reopen frame by frame.

Worked example: disk rate and file size at 4000 × 3000 and 30 fps

Take a 12 MP USB3 camera such as the LS-CMR-0166: 4000 × 3000 pixels at 30 fps. The recorder stores 2 bytes per pixel, because gray16 gives every sample a 16-bit word, and the clip runs 60 seconds. Those four inputs fix the file before compression:

bytes per frame = 4000 × 3000 px × 2 B = 24,000,000 B = 24 MB write rate = 24 MB × 30 fps = 720 MB/s 60 s clip = 720 MB/s × 60 s = 43,200 MB = 43.2 GB

The same clip at 8 bits is 12 MB per frame, 360 MB/s, 21.6 GB per minute — half the size for 16× coarser levels. FFV1's range coder stores the finished file below that raw rate by an amount that tracks scene noise, so size the disk for the uncompressed figure.

The decision this drives: give the recording disk headroom of 43.2 GB per recorded minute and a sustained write of 720 MB/s. NVMe-class SSDs sustain well over 1 GB/s and carry the stream; SATA SSDs top out near 500 MB/s and do not. Where the disk cannot keep up, halve the frame rate — 15 fps halves both numbers to 360 MB/s and 21.6 GB per minute — or record in takes. A disk that falls behind shows up as dropped frames, so compare the SDK's frame counter with the frame count in the closed file after the first take.

Pipe frames straight from the grab loop

Live capture does not need the intermediate sequence. Start FFmpeg with a raw video input — pixel format gray16le, the frame's width and height, the frame rate — and write each acquired frame's 2-byte buffer to FFmpeg's standard input from your acquisition loop; closing the pipe finalizes the file. The plumbing is the same as in recording video from a machine vision camera in Python; only the pixel format and the codec change. Two details decide success: write exactly width × height × 2 bytes per frame, and reconcile your frame counter with the frame count FFmpeg reports at close — a gap means the disk fell behind mid-take.

Prove the file kept every bit

  1. Check the file's pixel format: ffprobe recording.mkv must report gray16. If it reports 8-bit gray or a yuv format, a conversion crept into the chain; find it before trusting anything else.
  2. Round-trip a known pair. Encode two flat frames, one at gray level 1000 and one at 1001, then decode both from the MKV. An 8-bit path returns 4 and 4, because 1000 ÷ 256 = 3.9 and 1001 ÷ 256 = 3.9 both round to 4; the FFV1 file must return 1000 and 1001 — different, and exactly what went in.
  3. Decode the first real frame and compare it element for element with its source image. For FFV1 the arrays must be identical; a single differing pixel means a format conversion somewhere upstream.

Dark or flat-looking decoded frames are display scaling, not the file; why 12-bit camera images look black in a 16-bit preview gives the reason and the fix.

Common mistakes

  • Encoding 8-bit sources by accident. FFV1 preserves what it is given, so 8-bit PNGs produce a perfect 8-bit video. The bits have to exist before the encoder runs; the acquisition-side fall-back that quietly delivers Mono8 after a deeper format was requested is covered in camera image encoding falls back to mono8.
  • Word-layout mismatch. Decoding left-justified 16-bit words as packed 12-bit, or the reverse, turns a clean recording into noise or stripes. Set the layout once from the SDK's pixel format description and leave it alone.
  • Trusting a remux. Renaming or re-boxing an MP4 adds no bits; if the archive was written at 8 bits it stays there. Check the first take's pixel format before the campaign starts, not after it ends.
  • Reading FFV1 where the decoder is missing. Most editing suites do not decode FFV1, and a few readers stall on 16-bit video in general; that is what the TIFF or PNG master is for. When a reader insists on AVI and then refuses the file, the layout is the usual suspect — video capture backend fallback: diagnose AVI open errors walks those failures.
  • Judging by auto-normalized previews. A player that stretches every frame to full contrast erases your ability to compare brightness across frames; compare decoded numbers, not screenshots — why 10-bit PNG images look black in image viewers covers the display side.

When an 8-bit file is the right answer

Eyes-only recordings — a review clip, a report figure, training material — do not need the master's depth. Export the MP4 and share that. The display path is 8-bit anyway: test-bench software, HMIs and viewer windows draw 8-bit gray, and wiring deep data into them means choosing the window and scaling yourself; configuring test-bench software to display 8-bit grayscale images shows that side. The rule that separates the cases: if any number will ever be measured from the recording — intensity, ratio, threshold margin, defect contrast — the master stays 16-bit and lossless; if the recording exists only to be watched, the 8-bit MP4 is the deliverable and the disk bill halves.

Recording checklist

Before the take:

  • Requested and confirmed a mono pixel format deeper than 8 bits; one source frame read back, max value noted (near 4095 unscaled or 65440 scaled for a 12-bit sensor).
  • Word layout fixed: left-justified 16-bit words or packed 12-bit, matching the SDK.
  • Disk headroom at 43.2 GB per recorded minute for the 4000 × 3000, 30 fps case — scale by your own width × height × fps × 2 B.
  • True save rate noted for the input switch.

On the encode line:

  • -pix_fmt gray16le and -c:v ffv1 present, master in MKV or MOV.

After the take:

  • ffprobe reports gray16.
  • Flat pair at 1000 and 1001 decodes to two different values.
  • First real frame equals its source image element for element.
  • MP4 review copy exported from the master, never from the camera.

For a new recording station, size the camera side against the disk with the camera configurator; more recording and file-format guides sit on the systems topic page.

Questions

Can an AVI file hold 16-bit grayscale video?

Yes, when frames are written as uncompressed 16-bit grayscale rather than through a codec; a few reader libraries list exactly that format among the handful they accept, and codec-based AVI stays at 8 bits regardless. If a downstream tool insists on AVI, write raw gray16 frames into it and decode one frame back to confirm the arrays match before committing a campaign to it.

What frame rate goes on the input when my sequence skips frames?

Set the input rate to the rate at which frames were saved, not the camera rate. If you saved every 3rd frame from a 30 fps acquisition, the sequence runs at 10 fps, and -framerate 10 makes playback time match wall-clock time, so motion plays at true speed. A live pipe takes the camera rate directly.

Can 10-bit or 14-bit data use the same pipeline?

Yes. gray16 words hold any depth up to 16 bits: a 10-bit stream carries values 0 to 1023 and a 14-bit stream 0 to 16383, with no switch changing in the recording path. Expect decoded values to span the sensor's range, and expect slightly less compression as more of each word is used.

How much larger is a 16-bit recording than an 8-bit one?

Exactly twice before compression: 2 bytes per pixel instead of 1. At 4000 × 3000 pixels and 30 fps that is 720 MB/s against 360 MB/s, and 43.2 GB against 21.6 GB per recorded minute. FFV1 claws some of it back — more on clean static scenes, less on noisy ones — but size disks and recording sessions at the doubled figure.

Does the camera need a 16-bit output to record 16-bit video?

No. A camera streaming a 10-bit or 12-bit mono pixel format already carries more detail than any 8-bit file; the recorder stores each sample in a 16-bit word. What matters is that the format requested is the format delivered — a silent fall-back to an 8-bit format at acquisition loses the bits before any file is written.