How to Separate the RGB channels from an image and get the average of each RGB channel separetly

48 views (last 30 days)
Sarah DS
Sarah DS on 9 Apr 2021
Commented: Image Analyst on 12 Apr 2021
How to Separate the RGB channels from an image and get the average of each RGB channel separately
Hello MATLAB community,
I have a set of 20 images, which I need to process simultaneously. All the images have a stain of red colourant in the middle, which I need to separate from the white background. I need to obtain the RGB value average of each channel (pixels) of the stained red area. I am new to the MATLAB world. Hence I am a bit unsure how to proceed. I leave an example of one of the images I have.
Thank you for your time. I appreciate all your help.

Accepted Answer

DGM on 10 Apr 2021
Edited: DGM on 10 Apr 2021
I'm feeling particularly lazy today, so I'm not going out of my way to make this solution avoid nonstandard tools. They could be replaced, but let's see what I came up with.
% read images
% use thresholding in chroma to isolate red areas
% get rid of fabric patterning from bleed
% pick only the central blob
% fill in any holes before erosion
% erode to get rid of any remaining light edges
% display masked region
% calculate mean color tuple
selectedpix=inpict(repmat(mask,[1 1 3]));
meancolor=mean(reshape(selectedpix,[numel(selectedpix)/3 3]))/255
This will isolate the central part of the stain
and the mean color tuple (normalized) is dumped to console:
meancolor =
0.3814 0.0423 0.0695
So at least for this image, it works. The thresholding and strel sizes may need to be adjusted. If you only ever need one size of strel, you can just save it and reuse it instead of regenerating it every time. I just did that in case they needed tweaking.
mono() is part of the MIMT, which can be downloaded here if desired.
In this example, I'm thresholding chroma in LCHab. You could do the same thing using rgb2lab() and then converting to polar coordinates, etc. The main point is to leverage the lightness/color separation of the color model to avoid needing to deal with the shadows.
EDIT: I got to thinking that depending on your goals, it may be beneficial to further process the ROI. Removing specular highlights from the wet gauze would help keep them from lightening/desaturating your result. Furthermore, the materials used in the gauze would tend to skew the results a bit. It may be more meaningful to use a median of the sample area.
Consider this example.
% remove specular highlights from the mask
maskedpict2=inpict.*uint8(mpc & mask);
% get mean, median, and mode colors (imstats is from MIMT)
selectedpix=inpict(repmat(mpc & mask,[1 1 3]));
[mn md mo]=imstats(im2double(reshape(selectedpix,[numel(selectedpix)/3 1 3])), ...
Using the mean, median, and mode colors returned for both cases, I threw together this sample comparison image:
Against the white background of this website, it's a bit hard to tell, but sampling the mean is most susceptible to the influence of the specular highlights. The mode color is least affected.
  1 Comment
Sarah DS
Sarah DS on 10 Apr 2021
Thank you for all your help! I will try to understand all the fuctions and comprehend exactly what is happening.

Sign in to comment.

More Answers (2)

Image Analyst
Image Analyst on 10 Apr 2021
Edited: Image Analyst on 10 Apr 2021
Have you tried the Color Thresholder on the Apps tab of the tool ribbon? It's pretty straightforward. Read in your image, choose RGb color space, adjust the thresholds, export the code, then apply the masks
% Get separate color channels
[r, g, b] = imsplit(rgbImage);
% Find all red pixels.
[mask, maskedRGB] = createMask(rgbImage); % Color thresholder will make this function for you when you export the function.
% Take largest blob only
mask = bwareafilt(mask, 1);
% Compute means within the masked blob.
meanR = mean(r(mask));
meanG = mean(g(mask));
meanB = mean(b(mask));
DGM on 11 Apr 2021
Your color levels are high and desaturated because you're not really isolating the ROI.
% Extract the background (white) region
Iwhite = rgb2gray(I);
Bn = imbinarize(Iwhite); % you're not using this for anything
% bear in mind that the grayscale image is uint8
% and this an equality test instead of gt/lt
% so this isn't going to select anything
% since the averaging is considering the inverse of this mask
% you're averaging the entire image, which is mostly white.
idx = Iwhite == 1;
% Calculate average RGB of the region
Rave = uint8(mean(Ir(~idx)));
Gave = uint8(mean(Ig(~idx)));
Bave = uint8(mean(Ib(~idx)));
% Set the region to average RGB
Ir(~idx) = Rave;
Ig(~idx) = Gave;
Ib(~idx) = Bave;
% concatenate after setting the region
Iout = cat(3,Ir,Ig,Ib);
Even if you used Bn for the mask, you'll still end up sampling a bunch of non-target areas.
Like I implied, selecting by intensity alone is going to make it hard to avoid shadows. This mask could still be cleaned up using morphological operations, but you'd ... have to actually use them.
If you're after the base dye color and want to ignore the lighter color of the weft, you may get better results calculating the mode color or expanding on the de-highlighting example I added yesterday.
If you decide to try the example (and have already downloaded MIMT), you'll need the attached copy of imstats(). The version on the FEX has a bug that will break it if you're running a newer Matlab version.

Sign in to comment.

Image Analyst
Image Analyst on 11 Apr 2021
Like DGM, I'm not even sure what the goals are. The use case/context was never given. What is the fabric? What is the dye and why would it end up on the fabric? Why are some of the vertical threads red, even those not in contact with the stain? Why do you need to know the color? Is it going to be perceived by humans? Or do you just want to know the color of the stain? How was it applied? If you have a beaker of stain/dye, why not just measure its color directly instead of off a fabric? If humans view it, how to they perceive it? For example do they just look at the stain as a whole with both specular reflections (that CGM mentioned) and red places all lumped together in an overall perception? Or do you think the people will tend to ignore the white specular highlights and just concentrate on the red parts exclusively? If I were developing a solution for a customer, these are the questions I would ask.
By the way, have you checked out the Color Threshold like I suggested? And I'll tell you another one that may be useful: colorcloud(). colorcloud() can give you the 3-D color gamut.
Above you can see the 3-D color gamut in RGB color space. You can see that you do not have two well-isolated clusters/balls of color. So where in that near-continuum of color would you think the mean color should be?
Anyway, since you didn't seem to run the Color Thresholder for some reason, here is my solution.
% Demo by Image Analyst, April, 2021.
clc; % Clear the command window.
close all; % Close all figures (except those of imtool.)
workspace; % Make sure the workspace panel is showing.
format long g;
format compact;
fontSize = 16;
fprintf('Beginning to run %s.m ...\n', mfilename);
% Read in image.
folder = [];
baseFileName = 'redspot.jpeg';
fullFileName = fullfile(folder, baseFileName);
% Check if file exists.
if ~exist(fullFileName, 'file')
% The file doesn't exist -- didn't find it there in that folder.
% Check the entire search path (other folders) for the file by stripping off the folder.
fullFileNameOnSearchPath = baseFileName; % No path this time.
if ~exist(fullFileNameOnSearchPath, 'file')
% Still didn't find it. Alert user.
errorMessage = sprintf('Error: %s does not exist in the search path folders.', fullFileName);
rgbImage = imread(fullFileName);
[rows, columns, numberOfColorChannels] = size(rgbImage)
% Display the test image full size.
subplot(2, 2, 1);
imshow(rgbImage, []);
axis('on', 'image');
caption = sprintf('Original Image : "%s"', baseFileName);
title(caption, 'FontSize', fontSize, 'Interpreter', 'None');
hp = impixelinfo(); % Set up status line to see values when you mouse over the image.
% Set up figure properties:
% Enlarge figure to full screen.
hFig1 = gcf;
hFig1.Units = 'Normalized';
hFig1.WindowState = 'maximized';
% Get rid of tool bar and pulldown menus that are along top of figure.
% set(gcf, 'Toolbar', 'none', 'Menu', 'none');
% Give a name to the title bar.
hFig1.Name = 'Demo by Image Analyst';
% Threshold image.
[mask, maskedRGBImage] = createMask(rgbImage);
% Display mask image.
subplot(2, 2, 2);
imshow(mask, []);
axis('on', 'image');
title('Initial Color Segmentation Mask', 'FontSize', fontSize, 'Interpreter', 'None');
hold on;
hp = impixelinfo(); % Set up status line to see values when you mouse over the image.
% Take the largest blob.
mask = bwareafilt(mask, 1);
% Fill holes.
mask = imfill(mask, 'holes');
% Display mask image again.
subplot(2, 2, 3);
imshow(mask, []);
axis('on', 'image');
title('Final Mask', 'FontSize', fontSize, 'Interpreter', 'None');
hold on;
hp = impixelinfo(); % Set up status line to see values when you mouse over the image.
% Compute means within the masked blob.
[r, g, b] = imsplit(rgbImage);
meanR = mean(r(mask));
meanG = mean(g(mask));
meanB = mean(b(mask));
% Display the image masked with the final mask.
% Mask the image using bsxfun() function to multiply the mask by each channel individually. Works for gray scale as well as RGB Color images.
maskedRGBImage = bsxfun(@times, maskedRGBImage, cast(mask, 'like', maskedRGBImage));
subplot(2, 2, 4);
imshow(maskedRGBImage, []);
axis('on', 'image');
caption = sprintf('Mean R = %.1f. Mean G = %.1f. Mean B = %.1f.', meanR, meanG, meanB)
title(caption, 'FontSize', fontSize, 'Interpreter', 'None');
hold on;
hp = impixelinfo(); % Set up status line to see values when you mouse over the image.
fprintf('Done running %s.m\n', mfilename);
function [BW,maskedRGBImage] = createMask(RGB)
%createMask Threshold RGB image using auto-generated code from colorThresholder app.
% [BW,MASKEDRGBIMAGE] = createMask(RGB) thresholds image RGB using
% auto-generated code from the colorThresholder app. The colorspace and
% range for each channel of the colorspace were set within the app. The
% segmentation mask is returned in BW, and a composite of the mask and
% original RGB images is returned in maskedRGBImage.
% Auto-generated by colorThresholder app on 10-Apr-2021
% Convert RGB image to chosen color space
I = rgb2hsv(RGB);
% Define thresholds for channel 1 based on histogram settings
channel1Min = 0.920;
channel1Max = 0.201;
% Define thresholds for channel 2 based on histogram settings
channel2Min = 0.200;
channel2Max = 1.000;
% Define thresholds for channel 3 based on histogram settings
channel3Min = 0.000;
channel3Max = 0.595;
% Create mask based on chosen histogram thresholds
sliderBW = ( (I(:,:,1) >= channel1Min) | (I(:,:,1) <= channel1Max) ) & ...
(I(:,:,2) >= channel2Min ) & (I(:,:,2) <= channel2Max) & ...
(I(:,:,3) >= channel3Min ) & (I(:,:,3) <= channel3Max);
BW = sliderBW;
% Initialize output masked image based on input image.
maskedRGBImage = RGB;
% Set background pixels where BW is false to zero.
maskedRGBImage(repmat(~BW,[1 1 3])) = 0;
You can see that my mean RGB are 2 or 3 gray levels different than DGM's. It all comes down to how you define your segmentation.
Image Analyst
Image Analyst on 12 Apr 2021
You can get a light meter here:
We use the HD450.
Don't use a cell phone to capture photos for scientific use. They have all kinds of operations going on behind the scenes to change the color to give you a more vivid photo. You just have no idea how it modifies the images from one sample the the next. Like, if the spot is very large, will it saturate (the other meaning of saturating meaning boost the saturation in HSV color space) the colors to make the red appear more vivid than a smaller spot, or no spot at all? Who knows. That's why you need to use a machine vision camera, or at the very least a "prosumer" camera like a good Nikon or Canon that you can put into manual mode. If it's in manual mode, with predetermined fixed exposure time and aperture and iso, then you'll know the camera is not changing anything.
I don't think obtaining either of the above two items has anything to do with COVID. They should still be available. If your project sponsor is not willing to outlay a thousand or two dollars for a good system, then warn them that they'll be stuck with what they got. Garbage in, garbage out. You could get by with just getting the stain size fairly accurately, but the colors won't be accurate without at least an embedded color checker chart in every scene. The camera adjusts the settings according to what colors are present in the scene. So ask yourself this: if there is more red in one scene because the spot is larger than in another scene, how will the camera change it's settings? If you can't answer this quantitatively (which you can't) then that means the camera is most likely automatically changing its settings in a way that you know nothing about and have little control over. That's not a good thing obviously.
You need to take a photo and see that no part of the fabric or stain is saturated (this time meaning "clipping") on the bright or dark end. Make adjustments (if you can) to try to see that the histogram fits within the 0-255 limits for all pixels.
Attached is my color correction and calibration tutorial from my course on "The measurement of color and appearance." Please look it over.

Sign in to comment.

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!