System designTroubleshooting

Green channel extraction error when the stored image is grayscale

Why green channel extraction throws an indexing error after rgb2gray makes the image grayscale, and how storing the color original fixes the callback.

LumaSpec Vision10 min read Share on LinkedIn

On this page 9 sections
  1. 01The symptom: an index error at the channel line
  2. 02Why the third dimension is gone after rgb2gray
  3. 03The fix: store the color original, convert for display only
  4. 04Worked example: a 4000 x 3000 color frame through the failing line
  5. 05Other sources of the same error, in order of probability
  6. 06Start the pipeline in color
  7. 07When the stored image really is grayscale
  8. 08Prevention checklist
  9. 09Questions

Your GUI loads an image, converts it, shows it on the first axes, and then the green channel button dies with Index exceeds matrix dimensions. at the extraction line. In nearly every case the channel code is correct: the load callback ran rgb2gray on the frame and stored the grayscale result with setappdata, so the array the second callback reads has two dimensions and plane 2 no longer exists. This guide traces the error to that line, gives corrected callbacks you can paste in, works the memory cost of keeping the color original on a real 12 megapixel frame, and closes with the checks that stop the same error returning from the camera side.

The symptom: an index error at the channel line

The traceback stops on one statement:

green = I3(:,:,2);   % Green channel

The interpreter reads I3(:,:,2) as every row, every column, plane 2 of the array. I3 was fetched with getappdata(0,'image'), so the message tells you the array behind that key has no plane 2. The statement never returns, the second axes stays blank, and the display calls after it never run. The syntax is correct; the stored data lost a dimension somewhere between the two buttons.

Why the third dimension is gone after rgb2gray

A color file reads in as three stacked planes, red at I(:,:,1), green at I(:,:,2), blue at I(:,:,3), with one sample per plane per pixel. rgb2gray collapses the three planes into a single luminance plane:

color image: I = rows x columns x 3 planes (ndims = 3) rgb2gray: I1 = rows x columns (ndims = 2) index rule: I3(:,:,2) is valid only when size(I3,3) >= 2

The load callback in the failing app does exactly that, then stores the collapsed array:

if ndims(I) == 3
    I1 = rgb2gray(I);        % rows x columns x 3  ->  rows x columns
else
    I1 = I;
end
I1 = imresize(I1, 0.5);
axes(handles.axes1);
image(I1);
setappdata(0,'image', I1);   % the gray copy is what survives

By the time the channel callback calls getappdata, it receives a rows x columns array: ndims returns 2, size returns two numbers, and asking for plane 2 of an array whose third dimension has size 1 raises the error.

The ten-second test is to run size(getappdata(0,'image')) at the command line after loading a color file. Two numbers instead of three confirm the diagnosis on the spot.

The fix: store the color original, convert for display only

One change removes the error: the appdata key must hold the untouched color image, and the grayscale copy becomes a display asset under its own key.

  1. Load the file and keep the color array returned by imread as your source of truth.
  2. Build the grayscale copy only for the first axes, resize that copy to fit, and store it under a second key.
  3. In the channel callback, read the color original and test ndims(I3) == 3 before indexing any plane.
  4. Assemble the green-only image with cat, or display the plane directly.

Corrected load callback:

% --- load button ---
[filen, pathn] = uigetfile('*.jpg;*.bmp', 'select an input image');
I  = imread([pathn, filen]);   % uint8, rows x columns x 3 when the file is color
if ndims(I) == 3
    I1 = rgb2gray(I);          % uint8, rows x columns display copy
else
    I1 = I;
end
I1 = imresize(I1, 0.5);        % half scale, fits the axes
axes(handles.axes1);
image(I1);
title(filen);
setappdata(0, 'image', I);    % color original, the channel source
setappdata(0, 'gray',  I1);    % display copy, half size

Corrected channel callback:

% --- green channel button ---
I3 = getappdata(0, 'image');
if ndims(I3) == 3
    green      = I3(:,:,2);                              % uint8, rows x columns
    a          = zeros(size(I3,1), size(I3,2), 'uint8'); % empty red and blue planes
    just_green = cat(3, a, green, a);                    % uint8, rows x columns x 3
    axes(handles.axes2);
    imshow(just_green);
    title('Green channel');
else
    axes(handles.axes2);
    imshow(I3);
    title('No color planes: stored image is grayscale');
end

Three details in that code earn their place. The zero planes are created in the uint8 class of the green plane, so cat does not promote the whole image to double, where a 12 megapixel frame would occupy eight times the bytes and display range would need rescaling. The else branch shows the gray image with a message instead of crashing, so an operator sees the cause rather than a dead button. And the bare axes call from the original callback is gone: with no handle it creates a brand-new axes instead of drawing into handles.axes2, so the green image can land in the wrong window even after the indexing is fixed.

The test that proves the fix: after loading a color file, ndims(getappdata(0,'image')) returns 3, the extraction returns a rows x columns plane, and imshow(just_green) shows green wherever the part is green. The same discipline of checking a buffer's shape before processing is what Check multi channel image buffer size before processing covers on the acquisition side.

Worked example: a 4000 x 3000 color frame through the failing line

Inputs, each with its origin:

  • Frame size: 4000 x 3000 pixels, a 12 megapixel frame, since 4000 x 3000 = 12,000,000 pixels.
  • Storage class: uint8, one byte per sample, as imread returns for JPEG and BMP files.
  • Display copy: the half-size grayscale frame, 3000 x 0.5 = 1500 rows by 4000 x 0.5 = 2000 columns.
  • Decision budget: the RAM of the vision controller hosting the app.

Rule in words: an image occupies rows x columns x samples per pixel x bytes per sample.

bytes = rows x columns x samples_per_pixel x bytes_per_sample

Arithmetic, written out:

  • Color original: 3000 x 4000 x 3 = 36,000,000 samples, and at 1 byte each, 36,000,000 bytes = 34.3 MiB (36,000,000 / 1,048,576 = 34.3).
  • Grayscale copy at full size: 3000 x 4000 x 1 = 12,000,000 bytes = 11.4 MiB, which is all the failing app stored.
  • Display copy at half size: 1500 x 2000 x 1 = 3,000,000 bytes = 2.9 MiB.
  • The fix therefore holds 34.3 + 2.9 = 37.2 MiB in application data.

Decision: keep the color original. Up to roughly 100 megapixels per stored frame, the pair of copies stays in the tens of MiB, and the color planes are exactly what the channel tools need. Eight stations each holding a 37.2 MiB pair total 8 x 37.2 = 297.6 MiB, so memory is never the reason to strip color at this resolution. The MV-VC3301H-128G66 is a vision controller with 8 GB of RAM and four built-in light channels. Against 8 GB, which is 8192 MiB, one 37.2 MiB pair is 37.2 / 8192 = 0.45 percent.

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$2,419.00View partBuild a system with it

The same arithmetic catches allocation mistakes before they reach processing, which is the subject of Why your 16-bit image buffer has half the expected pixels.

Other sources of the same error, in order of probability

Likelihood Cause Fast test Fix
First The gray copy was stored in place of the color original ndims(getappdata(0,'image')) returns 2 Store the color original; convert only for display
Second The camera delivers a mono format, so even the original has two dimensions Inspect the format setting at connection; frames arrive rows x columns Select a color format before capture
Third The file on disk is grayscale, one sample per pixel ndims returns 2 immediately after imread Load a color file, or capture with a color camera
Fourth The half-size display copy was stored under the original key size returns half the rows and columns you shot Store the full array; resize only the display copy

The first row is the traceback case fixed above. The second is its camera-side twin and is covered in the next section. The third deserves ten seconds before any code changes: JPEG and BMP files that display with a tint can still be single-plane on disk, and no callback ordering will create planes the file never had. The fourth appears after someone shrinks images to fit the GUI and stores the shrunk array under the old key.

Start the pipeline in color

If the station must extract a green channel, capture has to deliver three planes. On GigE Vision and USB3 Vision cameras the setting is the GenICam PixelFormat node: choose a color mode there, and every frame arrives with red, green and blue planes already separated. The ordering rule is identical on a smart camera that runs inspection on the device, because its color-plane tools also need a color format in front of them.

Pick the capture end from area scan cameras with the color filter set, narrow it in the camera finder, and if the defect size is still open, let the camera resolution calculator settle how many pixels the green features need. For large fields of view the MV-CS200-10GC is a 20 MP color camera with a 1" sensor, a C-mount and a GigE interface.

Area scan camerasHikrobot MV-CS200-10GC 20 MP Area Scan Camera, GigE, IMX183, Color, BasicMV-CS200-10GC
Sensor format
1"
Lens mount
C-mount
Interface
GigE
Resolution
16-25 MP
$739.00View partBuild a system with it

Compact color options for smaller fields of view:

Area scan camerasHikrobot MV-CU020-90GC 2.5 MP Area Scan Camera, GigE, GMAX4002, ColorMV-CU020-90GC
Sensor format
1/1.7"
Lens mount
C-mount
$309.00View part
Area scan camerasHikrobot MV-CH120-11UC 12 MP Area Scan Camera, USB3, IMX304, High-speed version, ColorMV-CH120-11UC
Sensor format
1.1"
Lens mount
C-mount
$2,339.00 · quotedView and quote

The MV-CU020-90GC is a 2.5 MP color camera with a 1/1.7" sensor, a global shutter and a GigE interface. The MV-CH120-11UC is a 12 MP color camera with a 1.1" sensor, a global shutter, a C-mount and a USB3 interface.

A color buffer that reaches your code without size information is the same family of mismatch; Color image buffer no size info error: match pixel format walks the receiving-end fix. When you are ready to size the whole station, step through the system configurator.

When the stored image really is grayscale

No ordering of callbacks creates a green plane that was never captured. If the source is a grayscale file or a mono stream, the choice is real: capture in color upstream, or accept the luminance plane as the signal. The trade in numbers: rgb2gray computes 0.2989 x red + 0.5870 x green + 0.1140 x blue per pixel, so 41.3 percent of the luminance comes from the other two planes (1 - 0.5870 = 0.4130). A green-stained defect on a neutral part keeps most of its contrast after the mix, but two defects with matching luminance become indistinguishable, and the green channel itself cannot be recovered, because the mix is not invertible.

Decision: when the inspection signal is a color difference, keep the color original end to end and use the gray copy only for display. Resize after extracting, never before: imresize applied to a rows x columns x 3 array returns a rows x columns x 3 array, so plane extraction survives the resize.

Prevention checklist

  • Test ndims(I3) == 3 at every point where a plane is indexed, not only where the file was read.
  • Keep the untouched original under one appdata key and every derived view under its own.
  • Name keys after content: one 'image' key written by two buttons is how this bug hides for weeks.
  • During commissioning, put size of the stored array in a debug title, three numbers for color and two for gray.
  • Fix the camera's color format once at connection time, not per frame.
  • Resize display copies; never resize the extraction source.

If the capture side needs a decision as well, the finder and configurator above are the next step, or send us the frame size and defect size through the contact page and we will size the pipeline with you.

Questions

Why does ndims return 2 for an image that looks colored on screen?

Because the file stores one luminance sample per pixel. JPEG and BMP files can display with a tint while holding a single plane, and a single-plane file reads in as rows x columns whatever it looks like. Run ndims on the array immediately after imread: 3 means real color data, 2 means one plane, and no callback ordering can add planes the file never contained.

How much extra memory does keeping the color original cost?

Three samples per pixel against one, so the color original costs 3 x rows x columns bytes: about 3 MB per megapixel against about 1 MB for the gray copy. At 20 megapixels that is 60,000,000 bytes = 57.2 MiB of color and 19.1 MiB of gray (60,000,000 / 1,048,576 = 57.2; 20,000,000 / 1,048,576 = 19.1). Together that is 76.3 MiB, under 1 percent of 8 GB of controller RAM.

Can I display the green plane without building an RGB array?

Yes. imshow(green) shows the plane as an intensity image, bright where green was strong, and that is usually the better view for measurement because the values are not re-tinted. Build the zero-green-zero stack with cat only when the operator needs to see green as green on screen.

Is the green channel recoverable after rgb2gray?

No. The conversion replaces each pixel's three samples with one weighted luminance value, and different colors share the same luminance, so the step cannot be undone. If any later stage needs a color plane, the color original must still exist at that stage: store it, pass it as an argument, or re-read the file.

How do I confirm the camera is sending color frames, not mono?

Open the camera's format control before capture. On GigE Vision and USB3 Vision devices the GenICam PixelFormat node lists the mono and color modes the sensor supports, and the selected mode decides whether frames carry three planes. Set it once at connection and log the choice at startup, so a later format change cannot masquerade as a GUI bug.