You threshold an image, save it, and the file opens as a black rectangle with faint dots where the objects should be. The threshold did its job: the pixels hold 0 and 1. Any grayscale viewer maps value 1 to 1/255 of full brightness, about 0.4%, which the eye reads as black. The fix is small: write 255 into the passing pixels, or multiply the 0/1 mask by 255, then save to a format that stores both values exactly. The same steps work for any library that thresholds into a 0/1 image, whether the code runs on the camera itself or on a PC beside the line.
Why a thresholded file opens black
An 8-bit grayscale file shows brightness in 256 steps: value 0 is black, value 255 is white, and value 128 is mid-gray. Many threshold functions default to writing 1 into every passing pixel. Value 1 sits at the bottom of that scale, so the saved file is technically correct and visually empty.
Some inspection packages hide the problem on screen by drawing a binary palette over the live image, so the preview shows white objects. The palette lives in the display, not in the file, which is why the saved copy disappoints; the live binary image preview for defect inspection guide covers that preview path. The same display-mapping trap is why 10-bit PNG files look black in ordinary viewers, as why 10-bit PNG images look black in image viewers explains. Change the pixel values, not the viewer.
Step 1: read the mask before you save
Confirm the diagnosis before changing anything. Read the minimum, maximum and unique values of the image your code is about to write. A mask in the problem state reports a minimum of 0, a maximum of 1 and exactly two unique values. If the maximum is already 255, the file itself is fine and the issue is the viewer or a palette question. Most libraries expose this as a statistics or histogram call, and it costs far less than hunting through saved files on disk.
Step 2: write 255 into the passing pixels
Two equivalent fixes exist, and each takes one line.
- Set the replace value at the threshold itself. Every threshold function carries a parameter that decides what value passing pixels receive, and the default is 1. Use 255 for an 8-bit mask and 65535 for a 16-bit one.
- Scale afterwards instead: multiply the 0/1 image by 255 and save the product.
gray : uint8 image, values 0-255 # camera frame in
T : int = 128 # threshold level
mask : uint8 = (gray >= T) # holds 0 or 1 <- opens black
out : uint8 = mask * 255 # holds 0 or 255 <- save this one
# test: unique(out) must be {0, 255}
The names here are generic and your library's parameter names differ, but the operation is one multiply or one replace-value setting. In IMAQ-style code, IMAQ Equalize stretches the 0/1 image to full scale and IMAQ Write File 2 then saves it, which does the same job.
Step 3: pick a format that stores both values exactly
PNG is the default: lossless, readable everywhere, and its compression eats the long runs of identical bytes that make up most of a mask. BMP stores raw bytes, which makes file size predictable at width × height × bytes per pixel. TIFF handles 16-bit copies and multi-page files. JPEG is the one format to keep away from masks.
The grayscale original you keep next to the mask has bit-depth pitfalls of its own; saving 12-bit camera images without losing intensity data covers that side.
| Format | What it stores | Size for one 4000 × 3000 mask | Verdict |
|---|---|---|---|
| PNG, 8-bit | 0/255 exactly, lossless compression | far below raw; tens to hundreds of kB is typical | default choice |
| BMP, 8-bit | 0/255 exactly, raw bytes | 12,000,000 bytes ≈ 11.4 MiB | fixed sizes, simple loaders |
| BMP, 1-bit packed | 0/1, eight pixels per byte | 1,500,000 bytes ≈ 1.4 MiB | bulk archives read by your own code |
| TIFF | 8- or 16-bit, optional compression | 12 MB and up, raw | keeping 16-bit frames |
| JPEG | approximate levels | small but wrong | never for masks |
Step 4: save, reopen and prove the file
Three reads on the reopened file settle it.
- Unique values must be exactly {0, 255}.
- The count of pixels at 255 must equal the count the threshold reported when the mask was made.
- One open in an ordinary viewer, not the inspection tool, shows white objects on black.
If a check fails, look at the scaling step before suspecting the writer. Pixel counts feed measurement later too: measure blob area in a binary image by pixel counting applies the same count per object.
Worked example: masks from a 4000 × 3000 camera
Price the storage for one 12 MP frame:
- Pixels per frame: 4,000 × 3,000 = 12,000,000
- 8-bit mask, raw: 12,000,000 × 1 byte = 12,000,000 bytes ≈ 11.4 MiB
- Saved as RGB by mistake: 12,000,000 × 3 = 36,000,000 bytes ≈ 34.3 MiB, three times the bytes for identical information
- Packed 1-bit: 12,000,000 ÷ 8 = 1,500,000 bytes ≈ 1.4 MiB
- Object coverage of 4%: 12,000,000 × 0.04 = 480,000 pixels must sit at 255, which is the count step 4 checks
The LS-CMR-0166 delivers exactly these frames: 4000 × 3000 pixels at 30 fps over USB3.
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
At 30 fps, raw 8-bit masks would push 30 × 12,000,000 = 360,000,000 bytes per second, about 360 MB/s, onto disk. A sparse mask under PNG compression typically lands one to two orders of magnitude below that, which is why the format choice decides whether continuous mask logging fits the line's storage at all.
Where the save code runs
The scaling and format logic is identical wherever the pixels live. An x86-platform smart camera such as the LS-SMC-0017 executes the same routine on the device and writes masks to its own storage or a network share, which suits single-station checks.
- Interface
- USB 2.0
- Resolution
- 6-10 MP
- Sensor format
- 1"
- Shutter
- Global
Multi-camera lines centralize instead: a vision controller such as the MV-VC3301H-128G66 pulls frames from several cameras, runs the inspection, writes every mask next to its frame, and its four built-in light channels drive the illumination as well.
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
If you are choosing that hardware now, smart cameras and vision sensors covers on-device inspection and vision controllers covers PC-side setups; both start from the camera finder.
When a black file is the correct file
Integer label maps for training and annotation keep small IDs on purpose: 0 for background, 1 for object, 2 for the next class, because the program reads class numbers straight from pixel values. Those files open black and are correct. The rule is simple: files for people get 0 and 255, files for programs keep the IDs the program expects. When both audiences need the same mask, save the ID map for the code and a scaled 0/255 copy for the report, and generate both from one threshold call so they cannot drift apart.
Checklist before you trust the files
At the threshold
- Replace value is 255 (8-bit) or 65535 (16-bit), not the default 1.
- The mask holds exactly two unique values before saving.
At the writer
- Format is PNG, BMP or TIFF; no JPEG anywhere in the mask path.
- The image is single-channel; RGB copies triple the bytes for nothing.
After the first save
- Reopened file shows unique values {0, 255}.
- Count at 255 equals the threshold-time count from step 4.
- Storage budget of bytes per frame × frame rate × retention fits the disk.
Grayscale copies alongside
- Native bit depth kept in a 16-bit container, per the 12-bit save guide linked above.
Next step
Size the station that produces the masks: area scan cameras lists the sensors, the camera configurator pairs a camera with lens and light, and the systems guides collect the pipeline around them. For a quote or a second opinion on a build, the contact page reaches the applications team.
Questions
What replace value should the threshold write?
Use 255 for an 8-bit mask and 65535 for a 16-bit one: the replace value should equal the maximum of the pixel format, so passing pixels display white and failing pixels stay at 0. If your pipeline keeps a 0/1 label image for further processing, leave that in memory and scale only the copy you write to disk.
Why does a saved mask sometimes come out three times larger than expected?
The writer defaulted to RGB. A grayscale mask saved as color writes the same plane three times: a 4000 × 3000 frame becomes 36,000,000 bytes instead of 12,000,000. Choose the grayscale or single-channel option in the writer's dialog, or convert the image to one channel before the write call, and the file drops back to one byte per pixel.
Why does the mask look white in the inspection software but black in a normal viewer?
The inspection software applies a binary palette at display time, mapping value 1 to white on screen. The saved file holds plain gray levels, and a viewer without that palette maps 1 to 0.4% brightness, which reads as black. Once the file stores 0 and 255, both programs show the same picture.
How much smaller is a PNG mask than the raw frame?
A 4000 × 3000 8-bit mask is 12,000,000 bytes raw, and packed 1-bit is 1,500,000. PNG rewrites runs of equal bytes losslessly, so a mask with mostly background typically lands in the low hundreds of kilobytes, tens of times below the raw size. The exact figure follows the object's area and edge length, so budget from your own first saved files.
Should masks stay as 1-bit files to save disk space?
Only if storage forces it and your own code reads the files. Packed 1-bit data is eight times smaller than raw 8-bit, but many loaders unpack it back to 0 and 1, which reintroduces the black-display problem, and some viewers refuse the format outright. An 8-bit 0/255 PNG usually compresses close to the same size and opens correctly everywhere.
Does anything change when the camera delivers 10-bit or 12-bit frames?
The mask logic does not change: the threshold still writes two values, and you still save 0 and 255 in an 8-bit file. What changes is the grayscale copy kept next to the mask. A 10-bit or 12-bit frame must go into a 16-bit container with its values left in place, which is a separate procedure from the mask save.


