Every inspection reaches the same request eventually: the client wants a folder of NG images with the red boxes and labels on them, openable on any Windows PC without your program. You save the frame and the file comes out clean. The cause is structural, not a missing setting: display overlays are drawn by the UI layer at paint time and never touch the pixel buffer, while every capture and save path reads only that buffer. This guide gives the fix in five steps, a worked example that sizes the disk cost of a real archive, the format decision for BMP, PNG and JPEG, and a test that proves the annotations are in the file before you ship.
Why the saved image comes out clean
An overlay is an object, not pixels. A rectangle or a label sits in a list inside the display control; every time the window repaints, the UI toolkit draws the image first, then the overlay objects on top. The frame the camera delivered is never modified. Every save path reads that untouched buffer — the SDK's image writer, a capture-to-file call, a save button — so the file is a faithful copy of the camera output and the overlays simply are not in it. No camera setting or SDK flag changes this.
The fix is to do the display's job yourself: copy the frame, draw the same geometry into the copy's pixels, save the copy. If you have fought the opposite problem, overlays that do alter the frame, the pages on text overlay on a live camera image and pattern matching results that will not draw cover that side.
What you need
- The last acquired frame as an image object in your process, at camera resolution.
- The detector output: rectangles and labels in camera pixel coordinates.
- A drawing toolkit. On Windows, System.Drawing does everything shown here; the calls used below — Bitmap, Graphics, Pen, DrawRectangle, DrawString, Save — are standard documented .NET APIs.
- A writable folder and a naming rule. If records come from a hardware trigger, the numbered-filename pattern keeps them sortable without a database.
- Disk space: about 15 MB per 5 MP frame as a 24-bit BMP — the worked example below sizes a full archive.
The five steps
- Work in camera pixels. The display may be zoomed, fitted or letterboxed; your detector's coordinates are in the frame's own pixel grid. Draw in that grid or the box lands next to the flaw.
- Copy the frame before you draw. The acquisition layer reuses and rewrites its buffer for every frame. Draw into that buffer and you corrupt images the pipeline is still processing and tear the frames behind them — the same failure described in torn frames in a camera callback. Clone into your own bitmap.
- Leave indexed formats. 8-bit mono frames usually arrive as an indexed palette bitmap, and Graphics.FromImage refuses indexed pixel formats. Build a 24-bit RGB bitmap and draw the frame into it first. The palette trap also bites thresholded images; save a thresholded binary image without opening black walks through it.
- Draw geometry, then text. A 3-pixel red pen is visible on any background; the label goes above the box, clamped so it cannot start above the frame edge.
- Save, then dispose. Write BMP, PNG or JPEG (decision below), close the bitmap so the file handle is released, and never save onto the path the bitmap was loaded from — that throws.
Here is the whole thing in C# with System.Drawing:
using System.Drawing;
using System.Drawing.Imaging;
// Your detector's output, camera pixel coordinates:
Rectangle flawBox = new Rectangle(800, 960, 240, 160);
int flawId = 47;
// Where the NG record goes:
string archivePath = "D:/flaws/NG-047.bmp";
// frameBmp: the acquired frame from your SDK as a Bitmap,
// 2448 x 2048, Format8bppIndexed. Graphics.FromImage rejects
// indexed formats, so composite into 24-bit RGB:
using (var composite = new Bitmap(frameBmp.Width, frameBmp.Height, PixelFormat.Format24bppRgb))
using (var g = Graphics.FromImage(composite))
{
g.DrawImage(frameBmp, new Rectangle(0, 0, frameBmp.Width, frameBmp.Height));
using (var pen = new Pen(Color.Red, 3f))
{
g.DrawRectangle(pen, flawBox);
}
g.DrawString($"NG {flawId}", SystemFonts.DefaultFont, Brushes.Red,
flawBox.X, Math.Max(0, flawBox.Y - 22));
composite.Save(archivePath, ImageFormat.Bmp);
}
flawBox, flawId, archivePath, composite and pen are the variables this example declares; Bitmap, Graphics, Pen, DrawRectangle, DrawString and Save are documented .NET calls.
Prove the overlay is in the file
Reopen the file and check two pixels plus the length:
using (var reopened = new Bitmap(archivePath))
{
Console.WriteLine($"{reopened.Width} x {reopened.Height}"); // 2448 x 2048
Color corner = reopened.GetPixel(800, 960); // on the box border
Console.WriteLine($"{corner.R} {corner.G} {corner.B}"); // 255 0 0
Color inside = reopened.GetPixel(880, 1040); // inside the box
Console.WriteLine($"{inside.R} {inside.G} {inside.B}"); // original pixel value
}
Console.WriteLine(new System.IO.FileInfo(archivePath).Length); // 15040566
Corner red, interior untouched, length matching the arithmetic below — the annotations are pixels now. The length also catches the silent failure: a 32-bit composite reads 20,054,070 instead of 15,040,566. If you save to a stream instead of a path, for a database blob or a network transfer, position the stream at offset zero first; the .NET Image.Save documentation requires the image at offset zero, and bytes already in front corrupt the file.
Worked example: one 3-pixel red box on a 5 MP mono frame
Inputs, and where each comes from:
- Frame 2448 × 2048 px, the standard 5 MP sensor format (5,013,504 px), 8-bit mono at acquisition.
- Composite: 24-bit RGB, 3 bytes per pixel — the format this guide composites into.
- Flaw box 240 × 160 px at (800, 960), pen 3 px wide, red RGB (255, 0, 0) — the NG box-and-label case from the introduction.
- BMP container: 24-bit rows plus a 54-byte header pair (14-byte file header, 40-byte info header).
The rules, in words: a BMP row is padded up to a multiple of 4 bytes, the file is the headers plus every padded row, and a box outline of thickness t inside a w × h box overwrites the ring left between the box and a copy of it shrunk by 2t on each side.
Arithmetic:
- Pixels: 2448 × 2048 = 5,013,504.
- Stride: 2448 × 3 = 7,344 B, already a multiple of 4, so no padding.
- Pixel data: 7,344 × 2048 = 15,040,512 B.
- File: 15,040,512 + 54 = 15,040,566 B ≈ 15.0 MB.
- Ring: 240 × 160 = 38,400; (240 − 6) × (160 − 6) = 234 × 154 = 36,036; ring = 38,400 − 36,036 = 2,364 px. That is 2,364 / 5,013,504 = 0.047% of the frame. The pen is stroked centred on the path, so the true count sits a few hundred pixels either side — the share stays 0.05%.
Result: the annotation itself costs 0.05% of the file. The disk cost is all camera pixels: 500 NG frames per shift × 15.0 MB = 7.52 GB per shift.
Decision: keep plain 24-bit BMP up to about 1,000 NG frames per day — 1,000 × 15.0 MB ≈ 15 GB per day, trivial for any line PC. Past that threshold, switch the archive to PNG or add a retention limit. The box costs nothing either way; the archive policy is the only real decision.
BMP, PNG or JPEG for the record
| Format | Bytes per pixel on disk | Lossless | 5 MP frame | Use it when |
|---|---|---|---|---|
| BMP, 24-bit | 3 | yes | 15.0 MB | The default NG record; every viewer opens it |
| BMP, 32-bit | 4 | yes | 20.1 MB | Accidental default only — 25% larger for nothing |
| PNG | 1-2 compressed | yes | 5-10 MB | Archives over roughly 1,000 frames per day |
| JPEG, quality 90 | 0.1-0.2 compressed | no | 1-2 MB | Space-tight and large defects only; it smooths fine edges |
The PNG and JPEG sizes are estimates from typical compression ratios on noisy 8-bit sensor frames, not format limits. For measurement evidence stay lossless: JPEG smoothing moves edges by a pixel or so, and that is your tolerance gone.
Where this code runs
The steps are identical wherever the inspection lives: an area scan camera feeding a PC, a smart camera running everything on-device, or a vision controller archiving several cameras at once. Only the drawing calls' names change with the SDK.
The MV-SC5060XM-08M-WBN is a 6 MP mono smart camera with an 8 mm M12-mount lens; the whole inspection, compositing included, runs on the device.
- Interface
- GigE
- Resolution
- 6-10 MP
- Sensor format
- 1/2.4"
- Shutter
- Rolling
For a PC-side cell, the LS-CMR-0053 supplies 12 MP frames over GigE with a C-mount lens.
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- 12 MP
And the MV-VC2140-128G40-1T vision controller runs acquisition and archive together: 4 built-in light channels, 2 USB 3 ports, an N97 processor, 8 GB memory, a 128 GB SSD plus 1 TB HDD.
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
Mistakes that show up a week later
- Screenshotting the display instead of compositing: you capture window chrome and scaled coordinates. Composite; it is deterministic.
- Drawing at display-zoom coordinates instead of camera pixels (step 1).
- Saving over the file the bitmap was loaded from — the save throws, and you find out at the NG station.
- Holding the bitmap open across the shift; dispose it, or the next save fails on a locked file.
- Archiving JPEG for anything you might measure later.
- Leaving the 32-bit default: a third more disk for every frame, all year.
When compositing is the wrong answer
Three cases argue against baking annotations in:
- The client re-annotates or measures. Save the clean frame plus a sidecar file listing each annotation as type, x, y, width, height, label — the original pixels stay evidence-grade and any tool can redraw the graphics.
- The frames are 16-bit. Compositing into an 8-bit copy flattens the depth; if the defect contrast lives in the top bits, keep the clean 16-bit frame and file an annotated 8-bit copy beside it. The buffer-format traps behind this are in why your 16-bit buffer has half the expected pixels.
- The output is a report. Draw the boxes onto the report page and keep the frames untouched; for dataset capture the same logic applies, as in save camera frames to disk for offline testing and enrollment.
Checks before you roll it out
- Composite into a clone, never the live buffer — pipeline integrity.
- Detector coordinates used as-is, in camera pixels — the box sits on the flaw.
- Format24bppRgb composite — 15.0 MB, not 20.1 MB, per 5 MP frame.
- BMP up to about 1,000 NG frames per day; PNG or a retention rule above it — the sizing from the worked example.
- Reopen test run once: corner 255 0 0, interior untouched, length = stride × height + 54.
- Numbered or timestamped filenames — records sort themselves.
Next step: pick the frame source in the camera finder, browse smart cameras for inspection on the device or vision controllers for multi-camera archives, or configure a camera and send us the geometry — we will check the archive sizing with you. For anything not covered here, the contact page reaches the applications team.
Questions
Why does the image I save come out without the overlays?
The save call reads the pixel buffer, and overlays never touch it: the display control draws them onto the screen at paint time from its own object list. No camera or SDK setting changes this. Copy the frame into a fresh bitmap, draw the rectangles and text at camera pixel coordinates, then save that bitmap — the file then carries the annotations as real pixels.
Can I draw overlays directly into the camera's live buffer?
No. The acquisition layer reuses and rewrites that buffer for every frame, so drawing into it corrupts images the pipeline is still processing and tears the frames behind it. Take a copy first — clone the frame or copy the bytes into your own bitmap — and draw into the copy. The live path stays clean and the saved file still gets its box.
BMP, PNG or JPEG for a flaw-image archive?
Below about 1,000 NG frames per day, 24-bit BMP is the simple answer: 15.0 MB per 5 MP frame, lossless, and every Windows viewer opens it. Past that, PNG keeps the same pixels losslessly and typically lands at 5-10 MB on noisy sensor frames. JPEG cuts the space again but smooths edges, which spoils fine-defect evidence — use it only when the defect is large.
How much disk does a year of NG records need?
At 500 NG frames per shift, three shifts a day and 250 production days you store 375,000 frames a year. At 15.0 MB per 24-bit BMP that is about 5.6 TB. A 90-day retention rule cuts the steady state to roughly 135,000 frames, about 2 TB; switching the archive to PNG halves it again.
Can I keep the annotations editable instead of baking them in?
Yes, and it is the better evidence format: save the clean frame plus a sidecar text file listing each annotation as type, x, y, width, height and label. Any viewer or report tool can redraw them later, the original pixels stay untouched, and you never re-run the inspection to re-annotate. Composite a BMP only for people who want one file they can double-click.


