Every so often a camera module that captures JPEG frames starts returning images with a strong, even pink or magenta cast. The file opens, the header bytes look right, and the temptation is to order a replacement. In most cases the sensor is fine: the capture is corrupt in its colour path because the decoder reads chroma from the wrong bytes, most often a byte-order or declared-format mismatch between module and host. Three tests separate a format fault from a transport fault and from a genuinely dead module, ordered so the cheapest test comes first. Each test ends in a decision, and the worked example shows the size arithmetic you can reuse on your own captures.
What the pink cast tells you
A JPEG frame carries one luma plane for brightness and two chroma planes for colour, following the Y′UV family of colour models. When every pixel takes its chroma from the wrong bytes, the whole frame shifts by the same amount, and a uniform pink cast is the visible result. Uniformity is itself diagnostic: a torn read produces banding, duplicated rows or garbage at the bottom of the frame, not one colour applied evenly everywhere.
Two checks split the cases before you touch any code. Structure first: a complete capture starts FF D8 and ends FF D9, so intact markers tell you the two ends of the file arrived. Size second: the worked example below turns it into a number with a threshold. The table lists the signatures and the first move for each.
| Signature | Likely fault | First move |
|---|---|---|
| Uniform pink, markers intact, size in the normal band | Colour format or byte order | Swap the declared order |
| Uniform pink, size 2× or more above the band | Transport: bus or buffer | Rewire, re-read, compare bytes |
| Torn bands, half frames, varying size | Bus timing or short reads | Lower the clock, shorten the wiring |
| Pink only under IR illumination | IR-cut filter missing or stuck | Optics, not decode |
| Pink in raw frames too, not only JPEG | Module hardware or encode path | Swap the module |
Cause 1: the declared colour format is wrong
For packed 4:2:2 frames there are two documented byte orders. YUY2 stores luma first in each four-byte group: Y0, U, Y1, V. UYVY stores chroma first: U, Y0, Y, V. The documented 8-bit YUV format reference defines UYVY as YUY2 with the chroma and luma bytes flipped. Declare YUY2 while the data arrives as UYVY and every chroma byte is read from a luma position: one wrong mapping, applied identically to every pixel, which is exactly what a uniform cast looks like.
The mismatch can sit at either end of the pipeline. At the encode input, the onboard JPEG engine is configured for one input order while the sensor interface delivers another. At the decode end, the buffer that holds the frame declares a pixel format, and a wrong declaration misreads the planes — the same failure family as image bytes that need decoding before processing and the doubled colour planes from a mismatched Mat type.
Run the test on a saturated red target: decode one capture with each order declared, and the wrong order throws red hard toward green or blue while the right order looks correct. Then fix it at the source. Set the format once where the frame is produced and pass it with every buffer, so no stage downstream guesses.
Cause 2: bytes lost or duplicated on the way to the host
When the payload itself is damaged, size moves and structure tears. These modules are read over SPI from small host boards, and field reports on this hardware class trace corrupt captures to poor physical connections: loose jumpers, long shared ribbons and marginal signal levels. The same failure family shows on other interfaces — a parallel camera bus with no pixel clock loses image data in a fixed order, and a module that drops off the bus entirely is a connection problem with its own diagnostic sequence.
Buffers fail the same way. A read loop that stops early truncates the frame; a loop that keeps reading after the frame ends pulls stale bus content into the payload, inflating the file while the markers stay intact.
The test is a duplicate read: capture the same scene twice and byte-compare the files. A format fault reproduces bit for bit; a marginal connection rarely does. Then reseat and shorten the wiring, confirm a common ground, and lower the bus clock until two consecutive captures match byte for byte before raising it again.
Worked example: a 480 kB capture from a 5 MP module
Inputs. A 5 MP module read over SPI produces a suspect capture of 480 kB, with FF D8 at byte 0 and FF D9 at the end. Clean captures at this resolution land between 130 and 180 kB, and the fault of interest balloons individual captures to 480 kB or more on roughly one capture in five — both figures from field reports on this module class. The sensor resolution is nominal: 5,000,000 pixels.
Rule in words. For a fixed scene and fixed settings, JPEG size moves with scene detail by tens of percent, not by multiples: the clean band spans 130 to 180 kB, a ratio of 180 / 130 = 1.4. Any capture more than twice the top of the band is carrying extra or garbled bytes inside the payload, markers or no markers.
Result. The suspect capture compressed to about a third of what its mid-band neighbours achieved while staying structurally complete. An encoder does not fall from 67:1 to 21:1 on the same scene, so the extra bytes are not image data.
Decision. At 2.7×, above the 2× line, treat this capture as a transport fault and work Cause 2 first: rewire, drop the clock, re-read and compare. A capture inside the band with intact markers takes Cause 1 first: swap the declared byte order before touching hardware. Re-measure the clean band whenever the requested output size changes, because the band moves with it.
Cause 3: the onboard JPEG engine
These modules encode JPEG on the sensor board and expose output size and quality as settings. Settings are a weak suspect when the cast ignores them: changing the JPEG output size left the images identical, which is expected, because the size setting touches neither the input byte order nor the bus. Stop tuning it and use it as a probe instead. If pink appears at one quality level or one size only, pin the working combination in your init sequence and treat the rest as unusable.
The probe that settles the encode path is raw output. Capture one uncompressed frame from the same module and decode it with the declared format from Cause 1. A clean raw frame acquits the sensor, the lens and the wiring; the corruption then lives in the encode path alone, and bypassing that engine becomes the fix rather than a workaround.
When the module itself is at fault
Two swap tests end the debugging. Move the same module to a second host with known-good wiring: pink that follows the module travels with the hardware. Fit a second identical module to the original wiring: pink that reappears acquits the wiring for good. When pink follows the module and raw frames are wrong too, replace it — the same decision path as replacing a board-level camera when its sensor goes end of line.
The permanent fix removes the onboard encoder from the chain: a module that streams the sensor's frames straight to the host leaves every colour decision to code you control, and the board-level cameras category is built for exactly that. The LS-BCM-0020 captures 6.3 MP over GigE, the LS-BCM-0003 is a 3.1 MP USB3 model with a global shutter, and the MV-CB016-10GM-M-W pairs a 1.6 MP global-shutter sensor with an M12 mount on GigE.
- Sensor format
- 1/1.8"
- Lens mount
- CS-mount
- Sensor format
- 1/1.8"
- Lens mount
- C-mount
- Sensor format
- 1/2.9"
- Lens mount
- M12
For the full choice — sensor, mount, interface, shutter — the board-level camera selection guide walks the decisions in order.
Pink frames that are not this fault
Three signatures look similar and lead elsewhere. A camera that turns pink only at night, under infrared illumination, has an IR-cut filter that is missing or stuck: the colour is real and the fix is optical. A stream that decodes with complaints about extraneous bytes before the marker is carrying garbage ahead of the frame, a different failure covered step by step in fixing MJPEG streams with extraneous bytes before the marker. A frame that comes out gray rather than pink has a luma-side or format mismatch of its own — the gray-image format mismatch fix walks that case.
Keep these checks in the capture path
- Declare the pixel format once, at the source, and pass it with every buffer. A decode stage that guesses the order is what produces the uniform cast.
- Check FF D8 at byte 0 and FF D9 in the last two bytes before decode. The check costs microseconds and gates out empty or wildly truncated reads.
- Log capture size per resolution and flag anything above twice the measured band. Transport faults announce themselves as size long before they announce themselves as colour.
- Keep a raw-capture mode callable at any time. One uncompressed frame settles sensor-versus-encoder in minutes.
- Byte-compare two consecutive reads on any new or long bus run. Identical files confirm a stable link; drifting files point at wiring.
If the tests end at the module, the camera finder shortlists raw-streaming replacements and the cameras topic page collects the other camera diagnostics.
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.
The decisions in one line each: markers intact and size in band — swap the declared byte order. Size 2× or more above the band — rewire, drop the clock, re-read and compare. Pink in raw frames too — replace the module and stream raw to the host.
Questions
Why does a corrupt JPEG look pink instead of gray?
A JPEG stores one luma plane and two chroma planes. Neutral chroma (128, 128) decodes to gray, so a chroma path that writes zeros or luma bytes into the chroma planes lands far from neutral. In the standard colour transform, red alone carries a gain of 1.402 on the Cr offset, so the error shows as a strong red-magenta cast that covers the frame evenly.
The file starts with FF D8 and ends with FF D9 — is the capture complete?
Those markers prove the first and last bytes arrived; they say nothing about the bytes between them. A capture can carry dropped or duplicated data in the middle and still start and end correctly. Pair the marker check with the size test: a capture more than about twice your normal file size for the same scene carries extra or garbled payload even though the markers are intact.
Does changing the JPEG output size fix pink captures?
No. Changing the requested size alters the encoder's resolution and quality settings, but the parts that corrupt the colour path — the declared input byte order, the wiring, the host read loop — stay the same, so the capture comes out pink again at the new size. Size changes still help as a diagnostic: if the image is identical at several sizes, stop tuning the size and test the fixed parts of the path.
When should I stop debugging and replace the module?
After three results: raw (non-JPEG) frames from the same module decode with the same pink cast, a second identical module shows the fault on the same wiring, and captures stay far outside the normal size band after the bus has been rewired and re-read. At that point the encode path or the sensor is at fault; move to a module that streams raw frames to the host.
Can a board camera send frames without an onboard JPEG engine?
Yes — most board-level cameras stream raw or uncompressed frames over USB3 or GigE, and the host decides the pixel format. Moving format handling into your own code removes the encoder-decoder guessing game that produces pink captures. If your host can keep up with the data rate, that is the cleanest permanent fix for a module whose JPEG engine keeps producing corrupt colour.


