You resized a high-resolution camera frame for a detection demo and the preview came out greyscale — yet the boxes the detector draws are still red and green, and every pixel you probe reads the same value in R, G and B. That combination points at one stage: the one between the camera and your display code. This guide is for anyone wiring a resize-and-convert pipeline behind a colour area-scan camera, on a workstation or on a low-power edge host. You will run a one-minute buffer test that names the stage dropping the colour planes, place the YUV-to-RGB conversion where it costs the least bandwidth, and add a channel check that keeps the fault from returning after the next library update.
The symptom: colour overlays on a grey frame
The signature is specific. The frame updates at full rate, the detector's overlays render in colour because your code draws them after decoding, and a pixel probe returns identical values in all three channels. The identical value is luma. In the BT.601 convention most camera ISPs use, Y = 0.299R + 0.587G + 0.114B, so a saturated red card reads about 76 in every channel, a green card about 150 and a blue card about 29. If your flat values match those numbers, you are reading the luma plane. Nothing upstream is destroyed: the chroma sits in U and V planes your reader never touches, which makes this a colour loss inside the pipeline, not a camera fault.
Likely causes, in order of probability
Work down this list; the first cause covers most reports.
- The resize node passes YUV420 or NV12 through untouched. It resizes every plane but never converts to RGB, while the display code assumes three interleaved channels. This fits the classic report: the same script shows colour on a workstation and grey on the edge host, because a host build may convert implicitly at the display sink while an on-device build does not. Test: the buffer ratio in the next section.
- The conversion is linked to the wrong branch. A pipeline that feeds a detector at full resolution and a preview at reduced resolution has two outputs, and the convert stage ends up on the one you never show. Test: print the pixel format of the displayed stream, not of the converted one.
- Library and on-device package are from different generations. Resize-and-convert runs in the device's image-processing firmware on an edge host and inside the library on a PC, so mixed versions skip the conversion silently. Test: run the identical pipeline head-less on the workstation. Fix: update both packages from the same release and set the source colour output explicitly.
- The display path treats a three-plane frame as one channel. A single-channel view shows grey even after a correct conversion, so the fix lives in the reader, not the pipeline. The look-alikes section below separates it from the resize fault.
- The scene is genuinely grey. A monochrome sensor variant, bare metal under white light or an under-exposed frame gives R = G = B legitimately. The colour card settles this one in seconds.
Worked example: a 1024 × 768 NV12 frame read as BGR
Inputs. The preview target is 1024 × 768 px, the size a typical full-FOV detection demo resizes a 12 MP camera frame to. The buffer arrives as NV12: 8-bit luma at one byte per pixel, plus two chroma planes at quarter width and quarter height, interleaved, for 1.5 bytes per pixel of frame overall. The reader expects BGR888 at 3 bytes per pixel. Both are fixed-size 8-bit layouts, so every byte count below is exact, not an estimate.
The rule. A buffer carries width × height × its bytes per pixel. Compare the bytes that arrive with the bytes the reader asks for; the ratio names the fault.
Result. The delivered buffer is exactly half the size the reader wants. A display loop that walks it as BGR triplets still gets one value per pixel per channel — by reading each luma byte three times — so every pixel comes out R = G = B and the chroma bytes are never decoded.
Decision. At a ratio of 0.5, the resize emitted YUV420 without conversion: set the camera output format to RGB before the resize, or convert after it. At 1.0 with channels still identical, check the reader's channel type and stride instead. A reader that pulls 3 bytes per pixel from a 1.5-byte stream starves the frame queue as well: the preview greys out, then freezes, so the two symptoms travel together.
The fix: convert once, at the right stage
Both placements work; pick by bandwidth, not habit.
Convert before the resize. Set the camera node's colour output — the same setting block where you set resolution and frame rate — to RGB888 or BGR888. The resize then handles three interleaved planes and the display code needs nothing extra. One explicit setting, no downstream conversion, and every saved sample is colour-true.
GigE, USB3, 10GigE and CoaXPress
1,287 models · from $149.00Convert after the resize. Keep the camera in NV12, resize all planes and convert to RGB at the end. This halves what the pipeline moves before display: at 1920 × 1080 px and 30 fps, NV12 carries 1.5 B/px × 2,073,600 px × 30 frames = 93 MB/s, about 0.75 Gbps, while RGB888 doubles that to 186 MB/s, about 1.5 Gbps. On an edge host with one shared link, that gap decides whether detector and preview both fit — the same trade-off behind RGB camera frame rate limits: pixel format and exposure.
| Buffer format | Bytes per pixel | Layout | Colour after a plain resize |
|---|---|---|---|
| NV12 | 1.5 | luma plane, then interleaved quarter-size U and V | no — conversion needed |
| I420 (YU12) | 1.5 | luma, U and V as separate quarter-size planes | no — conversion needed |
| RGB888 / BGR888 | 3 | one byte per channel, interleaved | yes |
| GREY | 1 | luma only | single plane by design |
Prove the fix: a three-line channel check
Run it on the displayed stream, before any overlay code — overlay drawing is exactly why the boxes stayed coloured while the frame went grey.
import cv2
import numpy as np
frame = preview_stream.get() # illustrative input: your resized output, uint8 ndarray, shape (768, 1024, 3)
b, g, r = cv2.split(frame) # documented OpenCV call: three uint8 planes
spread = int(cv2.absdiff(r, b).mean()) # mean absolute R-B difference, range 0-255
print(spread) # 0 = identical planes, still YUV; above 10 on the colour card = fixed
On the red card: before the fix the spread is 0 and every value sits near 76. After a real conversion the red channel separates from blue at once — on a saturated card under normal exposure the spread lands above 50, and the exact value follows your exposure and white balance. Wire the same lines into the start-up script as assert spread > 10 and the fault fails loudly at commissioning instead of quietly in production.
When it is not the resize
Three look-alikes fail the ratio test and need different fixes.
- Single-channel view of a converted frame. The pipeline converts correctly, but the array you draw is typed as one channel or a display step converts to grey. Signature: ratio 1.0, spread 0. The reader-side fix is in Camera preview shows a gray image: fix the format mismatch.
- The camera is not sending colour at all. A mono sensor variant or a monochrome pixel-format default leaves even the raw, pre-resize stream grey. Start at GigE camera shows no color image: fix the pixel format default.
- Stride and buffer-lifetime faults. A view over an SDK buffer with the wrong row stride shows diagonal stripes rather than uniform grey, and a view over a recycled buffer tears. Both are buffer-handling faults, covered in Wrap an SDK frame buffer in an OpenCV cv::Mat without copying.
- Exotic packed formats. If the pipeline leans on packed 4:4:4 YUV, read YUV 4:4:4 packed pixel format greyed out: why and what to use before committing: some tools grey that option out deliberately.
For the neighbouring display and buffer faults, the systems topic page gathers the rest.
Keep it from coming back
- Set the colour output at the source. One explicit RGB or BGR setting in the camera node beats a per-platform default that changes between library releases.
- Assert the buffer length. A reader expecting width × height × 3 bytes should reject a buffer at half that length with a named error instead of showing grey.
- Keep the spread assert in the start-up script and aim the camera at the colour card once per station.
- Update library and on-device package together. Mixed generations are cause three above.
- Log the pixel format beside every saved sample, so a greyscale surprise traces to a format change rather than a process change.
If the fault is pushing you toward hardware that behaves identically on every host, run the pipeline on one of the shop's vision controllers, where camera ports, light channels and the pipeline settings sit in one enclosure.
The camera finder filters area scan cameras by interface, resolution and sensor format, and the configurator below starts at the camera step.
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
Questions
Does resizing an image itself remove colour?
No. Resize interpolation blends neighbouring pixels but writes the same planes back: an RGB frame stays RGB at any size. Colour disappears only when the resized buffer is stored or read in a single-plane YUV layout such as NV12, or when a grey scene makes R, G and B legitimately similar. A saturated colour card separates the two cases in seconds: identical values around 76 on red are luma being read three times, not a resize artefact.
Which camera output format avoids this fault entirely?
RGB888 or BGR888 straight from the camera node when the pipeline draws colour overlays or runs colour checks: every stage downstream then sees three interleaved planes and no conversion step exists to skip. Choose NV12 when the link or the embedded host is bandwidth-bound and convert once after the resize — at 1920 × 1080 and 30 fps that halves the stream from about 186 MB/s to about 93 MB/s. Keep one conversion, in one place, and log it beside the pipeline settings.
Why does the same script show colour on a workstation but grey on an edge host?
The two builds place the YUV-to-RGB conversion in different code: a host library often converts at the display sink, while an on-device pipeline expects the source or the resize node to emit RGB. A package built for one path silently skips the other, so the identical script forks into a colour preview and a grey one. Update the pipeline library and the device package from the same release, and set the source colour format explicitly instead of trusting a platform default.
Is identical R, G and B normal on a monochrome camera?
Yes. A mono sensor emits one luma plane, so a reader that fills three channels duplicates the same value and R = G = B is correct at every stage, resize included. The greyscale fault only exists where a colour sensor's chroma planes are present but never read. If the camera model ships in mono and colour variants, check the sensor marking on the unit before debugging the pipeline.