How do I change the colour in a region of interest of an image, leaving parts of the image unchanged, using the image processing toolbox?
Show older comments
I am wondering if anyone can help me. I have developed some code which alters the amount of redness/greenness in photographs of faces, by adding or subtracting values of 'A' using the CIE LAB colour space. I now want to develop the code further, so that I can leave the eyes and lips unchanged, and just change the amount of redness/greeness in the skin of the face. Here is the code which allows me to alter the amount of redness/greeness in the image:
%load original photograph of face
rgbimage = imread('Original.jpg');
%Convert image from RGB to l*a*b
cform = makecform('srgb2lab');
labimage = applycform (rgbimage, cform);
%Convert lab image from unit8 to double
labdoubleimage = lab2double(labimage);
%ascertain original values of channels l, a and b/divide lab image into l,
%a and b channels
l_channel = labdoubleimage(:,:,1);
a_channel = labdoubleimage(:,:,2);
b_channel = labdoubleimage(:,:,3);
% tell matlab what you want the new value of channel a (redness) to be. A
%minus value makes image greener, a plus value makes image redder
a_channelnew = a_channel-14;
%tell matlab that the new lab image is made up of the two unaltered ‘l’and ‘b’ % channels, plus the altered ‘a’ (redness) channel
labimagenew = cat(3, l_channel, a_channelnew, b_channel);
%determine original values of 'l', ‘a’ and 'b' and check that new values of a are correct
OriginalLightnessmean = mean(mean(l_channel));
OriginalLightnessmean
Originalredmean = mean(mean(a_channel));
Originalredmean
Originalbluemean = mean(mean(b_channel));
Originalbluemean
Newredmean = mean(mean(a_channelnew));
Newredmean
%convert the image back into RGB format, then from double to uint 8
cform2 = makecform('lab2srgb');
rgbnewone = applycform(labimagenew, cform2);
one = im2uint8(rgbnewone);
At first I tried making a binary mask, then using roifilt2 to attempt to change the colour only in the black parts of the binary mask, however, roifilt2 only allows you to process 2D matrices. Does anyone know how I could adapt the above code to leave out certain parts of the image when I add or subtract values from the ‘A’ channel? Any pointers would be much appreciated.
Thank you!
Answers (1)
Sean de Wolski
on 29 Jun 2012
Edited: Sean de Wolski
on 29 Jun 2012
There are many ways to do this type of thing and I am not completely clear so I will just give a small example:
%Sample image and rectangular mask
I = imread('peppers.png');
M = false(size(I));
M(50:100,50:200,:) = true;
%Do some operation to change the color: here we are going to add 10 to the red channel, 40 to the green and -20 to the blue.
I2 = bsxfun(@plus,double(I),reshape([10,40,-20],1,1,3));
I(M) = uint8(I2(M)); %extract only the relevant parts
%Display
imshow(I)
This can obviously be built up and there is often no reason to perform the operation on the whole image. If you can provide code and images for us to run then we can likely be of more assistance.
2 Comments
Laura M
on 2 Jul 2012
You could do something like this to fix OP's example.
rgbimage = imread('peppers.png');
%display image and create a circular shaped binary mask
binarymask = logical(imread('redpepmask.png'));
%Convert image from RGB to l*a*b
cform = makecform('srgb2lab');
labimage = applycform (rgbimage, cform);
%Convert lab image from unit8 to double
labdoubleimage = lab2double(labimage);
%Do some operation to change the color
I2 = bsxfun(@plus,labdoubleimage,reshape([10,0,20],1,1,3));
%extract only the relevant parts
m = repmat(binarymask,[1 1 3]); % expand the mask
labdoubleimage(m) = I2(m);
%convert the image back into RGB format:
cform2 = makecform('lab2srgb');
rgbnewone = applycform(labdoubleimage, cform2);
imshow(rgbnewone);

There are a couple key changes. This expands the mask so that it has the same size as the image.
m = repmat(binarymask,[1 1 3]);
... whereas the original code creates an array the same size, but the mask is present only in the first channel.
m = false(size(labdoubleimage));
m(binarymask) = true.
The other key change getting rid of the call to uint8() on this line. The LAB image is 'double', and the data in the chroma channels is outside the range of uint8(). Calling uint8() here doesn't do anything other than truncate data and add a bunch of rounding error. The truncated image will be unable to have any colors other than orange-red-magenta hues.
labdoubleimage(m) = uint8(I2(m));
There are some things to consider:
- An entire image copy is adjusted, which is more data than if only the ROI were adjusted
- Composition by logical indexing can only produce hard jagged edges on the inserted image.
- Direct additive adjustment of LAB values is not generally intuitive.
- Out-of-gamut points will be truncated in RGB, so their color will shift during conversion.
- In R2014b and later, rgb2lab()/lab2rgb() can be used instead of makecform()/applycform()/lab2double()
If you'd rather have an easier approach, MIMT tools can simplify a lot. Instead of trying to work in rectangular LAB, use an intuitve cylindrical model (LCHab). By default, imtweak() truncates in LCH to reduce hue/lightness distortion on conversion. Replacepixels() avoids all the class handling and array expansion that would otherwise be necessary for composing images using a mask. It can do logical or multiplicative composition in sRGB or linear RGB. The following example uses an antialiased mask:
A = imread('peppers.png');
mk = imread('redpepmask.png');
Aadj = imtweak(A,'lchab',[1.2 1 0.05]);
B = replacepixels(Aadj,A,mk);
imshow(B)

It's worth noting that whiie these tools were not available in 2012, they will work in period versions (tested in R2009b).
While the example with imtweak()/replacepixels() is certainly easier to read and write, it's not significantly faster or slower than the prior code, since it does more or less the same thing. The entire image copy is converted and adjusted and then converted back. If you really wanted to only adjust the ROI, you could ...
A = imread('peppers.png');
mk = imread('redpepmask.png');
% expand mask
mk3 = repmat(logical(mk),[1 1 3]);
% reshape masked image region to a Mx1x3 stripe
Amasked = reshape(A(mk3),[],1,3);
% adjust stripe
Aadj = imtweak(Amasked,'lchab',[1 0.75 -0.25]);
% assemble output image
B = A;
B(mk3) = Aadj;
imshow(B)
Of course, that would only let you use logical masks. If you wanted to use an antialiased/graduated mask, you'd have to start getting complicated again.
A = imread('peppers.png');
mk = imread('redpepmask.png');
% expand mask
mk3 = repmat(logical(mk),[1 1 3]);
% reshape nonzero mask region to a Mx1 stripe
mkmasked = im2double(mk(logical(mk)));
% reshape masked image region to a Mx1x3 stripe
Amasked = reshape(A(mk3),[],1,3);
% adjust stripe
Aadj = imtweak(Amasked,'lchab',[1 0.75 -0.25]);
% assemble output image
B = A;
B(mk3) = bsxfun(@times,double(Aadj),mkmasked) ...
+ bsxfun(@times,double(reshape(B(mk3),[],1,3)),(1-mkmasked));
imshow(B)
For the image/mask given, this is still significantly faster than full-frame adjustment and composition, but it should be quite apparent why it might be avoided.
EDIT: As of 1.49, MIMT includes roifilter(), which does allow ROI-based image filtering without all this hassle. Unlike IPT roifilt2(), MIMT roifilter() can process complete RGB image segments and handles soft masks. The two prior examples both simplify to this:
A = imread('peppers.png');
mk = imread('sources/standardmods/pep/redpepmask.png');
% create a function to operate on mask regions
F = @(x) imtweak(x,'lchab',[1 0.75 -0.25]);
% apply the filter to masked areas of A
B = roifilter(A,mk,F);
imshow(B)
Categories
Find more on ROI-Based Processing 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!