Returning an array of colors from a double image

95 views (last 30 days)
Mark
Mark on 6 Mar 2022
Commented: DGM on 23 Apr 2024 at 8:29
I am trying to write a function that takes a type double image as input and returns an array of the colors in that image. The returned colors are supposed to be in a matrix form. The colors in my existing image are red, green, blue, white, and yellow. I can't get my head around this. Any suggestions?

Answers (4)

Voss
Voss on 6 Mar 2022
% create an image with colors r,g,b,w,y
im = ones(2,4,3);
im(1,1,:) = [1 0 0];
im(1,3,:) = [0 1 0];
im(2,2,:) = [0 0 1];
im(2,4,:) = [1 1 0];
imshow(im);
% get the set of unique colors in the image:
colors = unique(reshape(im,[],3),'rows')
colors = 5×3
0 0 1 0 1 0 1 0 0 1 1 0 1 1 1

Image Analyst
Image Analyst on 6 Mar 2022
rgbImage = imread('peppers.png');
% Call the function:
colors = GetUniqueColors(rgbImage)
% Define the function:
function colors = GetUniqueColors(rgbImage)
[r, g, b] = imsplit(rgbImage);
colors = unique([r(:), g(:), b(:)], "rows")
end
  11 Comments
Image Analyst
Image Analyst on 9 Mar 2022
I think @DGM means to replace
colornames = {'w','r','g','b','y'};
by
colornames = {"white", "red", "gr","blue", "yellow"};

Sign in to comment.


DGM
DGM on 7 Mar 2022
Edited: DGM on 7 Mar 2022
You can leverage rgb2ind()'s minimum variance quantization to get a best-fit color table of specified length.
A = imread('https://www.mathworks.com/matlabcentral/answers/uploaded_files/917239/image.png');
[~,CT] = rgb2ind(A,6) % get a color table of at most 6 colors
CT = 6×3
0.0392 0.0392 0.0392 0.2039 0.1686 0.9569 0.9569 0.0549 0.0392 0.8471 0.9569 0.0588 0.9569 0.9569 0.9569 0.0863 0.9569 0.2235
Bear in mind that since these colors were originally close to the extremes of the data range, truncation means that the addition of zero-mean gaussian noise will indeed shift the mean colors of the image, even if the noise mean is zero. I should point out that it's pretty clear the blue, green and yellow patches weren't on their corners to begin with.
If you know that you only want primary + secondary + neutral colors, you can just round the result.
CTrounded = round(CT)
CTrounded = 6×3
0 0 0 0 0 1 1 0 0 1 1 0 1 1 1 0 1 0
Otherwise, you can try to renormalize the values to correct for the inward shift caused by the noise. This assumes that the colors in the image nominally spanned the data range before the noise was added.
CTnormalized = mat2gray(CT)
CTnormalized = 6×3
0 0 0 0.1795 0.1410 1.0000 1.0000 0.0171 0 0.8803 1.0000 0.0214 1.0000 1.0000 1.0000 0.0513 1.0000 0.2009

DGM
DGM on 7 Mar 2022
Edited: DGM on 7 Mar 2022
Oh okay I totally misunderstood the question. Round 2:
A = imread('patchchart.png');
patchmask = rgb2gray(A)>40;
patchmask = bwareaopen(patchmask,100); % remove positive specks
patchmask = ~bwareaopen(~patchmask,100); % remove negative specks
patchmask = imclearborder(patchmask); % get rid of outer white region
patchmask = imerode(patchmask,ones(10)); % erode to exclude edge effects
imshow(patchmask)
% segment the image
[L N] = bwlabel(patchmask);
% get average color in each mask region
patchcolors = zeros(N,3);
for p = 1:N % step through patches
patchmk = L==p;
Apatch = A(patchmk(:,:,[1 1 1]));
patchcolors(p,:) = mean(reshape(Apatch,[],3),1);
end
patchcolors = patchcolors./255; % normalize
% specify a correlated list of colors and color names
colornames = {'w','r','g','b','y'};
colorrefs = [1 1 1; 1 0 0; 0 1 0; 0 0 1; 1 1 0];
% find color distances in RGB
D = patchcolors - permute(colorrefs,[3 2 1]);
D = squeeze(sum(D.^2,2));
% find index of closest match for each patch
[~,idx] = min(D,[],2);
% look up color names
patchnames = reshape(colornames(idx),4,4)
patchnames = 4×4 cell array
{'b'} {'y'} {'w'} {'y'} {'y'} {'w'} {'w'} {'r'} {'w'} {'y'} {'r'} {'r'} {'g'} {'w'} {'w'} {'r'}
Alternatively, instead of doing the distance minimization the long way, you could just use rgb2ind() to do that work:
% find index of closest match for each patch
idx = rgb2ind(permute(patchcolors,[1 3 2]),colorrefs) + 1;
% look up color names
patchnames = reshape(colornames(idx),4,4)
patchnames = 4×4 cell array
{'b'} {'y'} {'w'} {'y'} {'y'} {'w'} {'w'} {'r'} {'w'} {'y'} {'r'} {'r'} {'g'} {'w'} {'w'} {'r'}
  15 Comments
kiana
kiana on 22 Apr 2024 at 19:16
Hello
thanks for the codes and explanation
I have a problem similar to this one
I tried this code for normal photos and it works but with photos with noise it does not work well
For Denoise I use
A = imerode(A,ones(3));
Ay = medfilt3(A,[101 1 1]); % use a long median filter
Ax = medfilt3(A,[1 101 1]); % both directions
A = max(Ax,Ay); % combine to keep patches from getting connected
A = imadjust(A,stretchlim(A,0.05));
patchmask = rgb2gray(A)<230;
patchmask = bwareaopen(patchmask,100); % remove positive specks
patchmask = ~bwareaopen(~patchmask,100); % remove negative specks
oamask = imdilate(patchmask,ones(10)); % dilate to merge patches
oamask = bwareafilt(oamask,1); % select largest blob
patchmask = patchmask & oamask; % exclude fiducials and any border noise
patchmask = imerode(patchmask,ones(10)); % erode to exclude edge effects
filter as you sent but it does not solve the problem
i upload the result and my photo
can you help me to solve this issue ?
DGM
DGM on 23 Apr 2024 at 8:29
That example was intended for a type of image without a black border. The prior versions may work, but for this example, I'm going to stop doing everything strictly with base/IPT tools.
A = imread('noise_1.png');
% A = amedfilt(A,5,1); % more robust against JPG-compressed impulse noise
A = fmedfiltforum(A,5); % fixed median noise reduction filter (get rid of SNP noise)
A = imerode(A,ones(5)); % this may no longer be strictly necessary, but it's safer
A = medfilt3(A,[11 11 1]); % this is not ideal, but it's succinct
A = imadjust(A,stretchlim(A,0.05));
patchmask = rgb2gray(A)>20; % threshold
patchmask = bwareaopen(patchmask,100); % remove positive specks
patchmask = ~bwareaopen(~patchmask,100); % remove negative specks
patchmask = imclearborder(patchmask); % get rid of outer white region
patchmask = imerode(patchmask,ones(10)); % erode to exclude edge effects
imshow(patchmask)
% segment the image
[L N] = bwlabel(patchmask);
% get average color in each mask region
patchcolors = zeros(N,3);
for p = 1:N % step through patches
patchmk = L==p;
Apatch = A(patchmk(:,:,[1 1 1]));
patchcolors(p,:) = mean(reshape(Apatch,[],3),1);
end
patchcolors = patchcolors./255;
% try to snap the centers to a grid
S = regionprops(patchmask,'centroid');
C = vertcat(S.Centroid);
climits = [min(C,[],1); max(C,[],1)];
C = round((C-climits(1,:))./range(climits,1)*3 + 1);
% reorder color samples
idx = sub2ind([4 4],C(:,2),C(:,1));
patchcolors(idx,:) = patchcolors;
% specify a correlated list of colors and color names
colornames = {'w','r','g','b','y'};
colorrefs = [1 1 1; 1 0 0; 0 1 0; 0 0 1; 1 1 0];
% find color distances in RGB
D = patchcolors - permute(colorrefs,[3 2 1]);
D = squeeze(sum(D.^2,2));
% find index of closest match for each patch
[~,idx] = min(D,[],2);
% look up color names
patchnames = reshape(colornames(idx),4,4)
patchnames = 4x4 cell array
{'b'} {'w'} {'r'} {'g'} {'w'} {'b'} {'r'} {'b'} {'b'} {'r'} {'b'} {'y'} {'g'} {'y'} {'g'} {'g'}
Doing an actual noise-removal median filter instead of trying to suppress everything with just simple median filters and morphological operations should be a lot more robust against impulse noise. The attached file fmedfiltforum() is a slower, version-dependent copy of MIMT fmedfilt(). It doesn't require MIMT, but it does require IPT and R2019b or newer. MIMT amedfilt() would potentially be quite a bit more robust (especially if the image is compressed) but it's slower, and probably overkill.
We have two types of images: ones with a contiguous white background, and ones with a black frame around the tiles. Consequently, I'm using two different ways to create the mask. Is it possible to write one function/script which can handle both types of images? What if we use saturation to find the foreground instead of looking at luma alone? Maybe, but relying on chroma/saturation will be problematic for two reasons. FIrst, the existence of white tiles means that you're going to have to extrapolate the location of tiles which the segmentation will miss. Second, the use of any JPG compression will probably ruin saturation data and risk creating extra problems.
Is there a better way? Probably. I'm just adapting ad-hoc scripts as people keep giving me slightly different versions. I don't have an understanding of the full scope of the problem requirements. If the images are grossly transformed or changed in other unexpected ways, the given examples will probably break.

Sign in to comment.

Categories

Find more on Images in Help Center and File Exchange

Community Treasure Hunt

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

Start Hunting!