How can I change the color of the hair such that it has the same texture as the original hair?

12 views (last 30 days)
Charlie Marzan
Charlie Marzan on 30 Jul 2017
Edited: DGM on 4 May 2022
I'm having a hard time of maintaining the texture of the original image. I have changed the color but the texture of the hair is absent.
This is my code:
% Extract the individual red, green, and blue color channels.
redChannel = rgbColorImage{ii}(:, :, 1);
greenChannel = rgbColorImage{ii}(:, :, 2);
blueChannel = rgbColorImage{ii}(:, :, 3);
% Specify the color we want to make this area.
desiredColor = [146, 40, 146]; % Purple
% Make the red channel that color
redChannel(maskedImage) = desiredColor(1);
greenChannel(maskedImage) = desiredColor(2);
blueChannel(maskedImage) = desiredColor(3);
% Recombine separate color channels into a single, true color RGB image.
rgbColorImage{ii} = cat(3, redChannel, greenChannel, blueChannel);
Output:
  5 Comments
yasar ismet Yilmaz
yasar ismet Yilmaz on 8 Sep 2021
@Image Analyst, Can you send the codes to my e-mail address as a whole? I would like to look at the photo by applying it. I just started working in matlab. I can't quite fit the skeleton of the code.

Sign in to comment.

Answers (2)

Image Analyst
Image Analyst on 30 Jul 2017
The problem is that you're doing it in the wrong color space. You should convert to hsv color space, then change only the hue channel, not the saturation or value channel. See attached demo.
  4 Comments
Image Analyst
Image Analyst on 31 Jul 2017
Because with dark hair, the saturation and value are so low, you might want to add a constant to the saturation and value images. See if that looks better.

Sign in to comment.


DGM
DGM on 3 May 2022
Edited: DGM on 4 May 2022
There are a number of things going wrong here. I kind of wish this were active.
Let's start with OP's example. Since the original images are unavailable, I'm going to use a standard test image for examples. I am going to ignore the task of creating the ROI mask, assuming some linear mask has already been created.
While simple color replacement is a common request and suggestion, it obviously cannot preserve any content in the ROI.
% replace ROI with solid color
inpict = imread('peppers.png');
mk = imread('redpepmask.png');
% create solid color field
cpict = repmat(permute([0.2 0.88 0.85],[1 3 2]),size(mk));
% combine images with simple linear composition
inpict = im2double(inpict); % both images need to be floating point of the same scale
mk = im2double(mk); % mask needs to be unit-scale floating point
outpict = cpict.*mk + inpict.*(1-mk);
imshow(outpict);
Obviously, that approach isn't fit for the task of changing the color of the object. Instead of replacing the color in whole, the original color could be adjusted. For the moment, let's focus on the example with the peppers instead of OP's original image. The selected pepper has relatively uniform color content. We could adjust hue and saturation to change the color of the pepper without changing its brightness. That might seem like it would preserve the shadows and highlight details like we want.
There are a couple of problems to point out about hue adjustment as discussed so far. For good reason, hue adjustment is generally not done via multiplication. H is a circular continuum. H = 10 is closer to H = 350 than it is to H = 30. Modular scaling of H causes color points in the vicinity of red to be moved away from their neighbors in a nonlinear manner. The discontinuous results should make it clear that this isn't working.
% bad hue adjustment in HSV
inpict = imread('peppers.png');
mk = imread('redpepmask.png');
% adjust by scaling H
Hfactor = 1.6;
hsvpict = rgb2hsv(inpict);
hsvpict(:,:,1) = mod(hsvpict(:,:,1)*Hfactor,1);
adjustedpict = hsv2rgb(hsvpict);
% combine images with simple linear composition
inpict = im2double(inpict); % both images need to be floating point of the same scale
mk = im2double(mk); % mask needs to be unit-scale floating point
outpict = adjustedpict.*mk + inpict.*(1-mk);
imshow(outpict);
Instead of modular scaling, use addition in order to rotate H. This preserves the continuity of the image, and local regions have the same amount of hue variation as they did before.
% basic non-MIMT hue adjustment in HSV
inpict = imread('peppers.png');
mk = imread('redpepmask.png');
% adjust hue by modular addition, not by scaling
Hoffset = 0.5;
hsvpict = rgb2hsv(inpict);
hsvpict(:,:,1) = mod(hsvpict(:,:,1)+Hoffset,1);
adjustedpict = hsv2rgb(hsvpict);
% combine images with simple linear composition
inpict = im2double(inpict); % both images need to be floating point of the same scale
mk = im2double(mk); % mask needs to be unit-scale floating point
outpict = adjustedpict.*mk + inpict.*(1-mk);
imshow(outpict);
That's better. It's not great, but it's continuous.
This is the point where I get fed up doing things the tedious way merely for sake of example. That whole mess of a hue rotation can be done more succinctly and safely using basic MIMT tools:
% same thing, but using MIMT
inpict = imread('peppers.png');
mk = imread('redpepmask.png');
% adjust hue in HSV using imtweak()
Hoffset = 0.5;
adjustedpict = imtweak(inpict,'hsv',[Hoffset 1 1]);
% combine images with simple linear composition
outpict = replacepixels(adjustedpict,inpict,mk);
imshow(outpict);
... and the result will be the same.
Tools aside, the result isn't great. The result has some tonal details, but it's obviously brighter than the original, and the highlights appear blown out. This is the second problem with thinking that we can just adjust H and get what we want. Generally, HSV and HSL yield poor results when trying to do large HS adjustments. I chose a 50% swing on purpose for emphasis. There are crude ways to compensate, but it's also possible to simply avoid the problem instead of trying to fix it.
That's another reason I introduced MIMT into the conversation. Base MATLAB and IPT don't have any color adjustment tools and while I suppose it's simple enough to do it in HSV, trying to do large adjustments in LAB is not as trivial as the prior example in HSV. Using MIMT imtweak(), it's no more complicated than the prior example.
% try to be less garbage
inpict = imread('peppers.png');
mk = imread('redpepmask.png');
% adjust hue in lchab using imtweak()
Hoffset = 0.5;
adjustedpict = imtweak(inpict,'lchab',[1 1 Hoffset]);
% combine images with linear composition in linear RGB
outpict = replacepixels(adjustedpict,inpict,mk,'linear');
imshow(outpict);
That's a lot better, but something still seems vaguely off. Besides being a teal pepper, it looks displaced from the scene. If you look closely, you might see some problems. Obviously, the edges of the mask aren't perfect, so there are some bits of red fringe in places; forgive my limited patience. Still, not all of those spots are a matter of poor selection. Some of the red colors around the stem aren't the pepper. They're the color of the pepper being reflected on the stem. Similarly, the red from the pepper is cast on the garlic and strongly on the yellow pepper. Likewise, the pepper itself reflects its surroundings. The orange and yellow peppers cast strongly on the subject pepper. Perhaps most notably, the light itself can be seen. Its once bluish-white cast has now become strongly yellow, in contrast to all the similar reflections on the other objects in the same scene.
The lesson here is that you shouldn't expect simple masked image adjustment to produce realistic results, as evidence of the object exists beyond the extent of the object itself, and the appearance of the object is influenced by its surroundings. Your brain is good at picking up on that, even if you can't quite put your finger on why it looks wrong.
So now that expectations are lowered a notch, let's try going back to the original image in question. The original task is more difficult in every way. Creating a mask for hair is a traditional exercise in frustration and disappointment, especially if you insist on using a binary mask. Not only can hair fan out in ways that make any masking attempt limited by the image resolution, hair itself is reflective and translucent. While short black hair is actually the easy case, consider how you might try to mask long blonde hair draped over a shoulder. Consider how you might mask teased hair on a backlit subject.
Now that expectations have been lowered another notch, let's assume that a sufficient mask has been obtained. How can we change the hair color? So far we've only been manipulating hue. That might have worked if the hair were similar in brightness to our target color. Trying to also effect huge brightness and saturation changes is difficult it we want the results to look plausible. Still, can we try an approach like before? After all, imtweak() can manipulate all three dimensions of the color model. Well obviously there isn't much saturation/chroma to work with, so we'll have to boost that a lot. We'll also have to raise the brightness a lot. Let's try doing that. Since I don't have a good hair picture, I'm going to just pick another standard test image of a black textured thing with subtle highlights.
% trying to colorize something black by naive adjustment
inpict = imread('tape.png');
mk = imread('tapemask.png');
% adjust hue in hsv using imtweak()
Hoffset = 0.2;
adjustedpict = imtweak(inpict,'hsv',[Hoffset 100 2]);
% combine images with linear composition in linear RGB
outpict = replacepixels(adjustedpict,inpict,mk,'linear');
imshow(outpict);
Of course that would be garbage. There's not much chroma information there but noise. Trying to boost it just makes more noise. Imagine if the source image were a JPG instead?
Instead of relying on adjusting the original color information, we can try any one of many methods in an attempt to colorize the image. Instead of trying to deal with the brightness change at the same time, do that first.
% trying to colorize something black by substitution
inpict = imread('tape.png');
mk = imread('tapemask.png');
% adjust levels and gamma
lvlpict = imadjust(inpict,[0 0.75],[0.15 1],1.2);
% directly replace H and S instead of adjusting original values
cpict = colorpict(size(inpict),[160 40 200],'uint8');
adjustedpict = imblend(255-lvlpict,cpict,1,'transfer lhsl>shsl'); % adjust cast saturation
adjustedpict = imblend(lvlpict,adjustedpict,1,'lightness'); % then colorize
% combine images with linear composition in linear RGB
outpict = replacepixels(adjustedpict,inpict,mk,'linear');
imshow(outpict);
That's ... better. Not great, but better. Again, all the things I mentioned start to show up again. The reflection in the table and the metallic pinstripe are wrong. It still looks like a colorized black object, since the influence of its surroundings has also been colorized. With a minute of extra manual brushwork and maybe another layer or two, you could get closer:
This brings us to the point that not everything has to be done in MATLAB. A lot of things are just simpler in an image manipulation environment like GIMP or Photoshop. Anything requiring brushwork really belongs in another environment. Bear in mind that's coming from the person who has been building a toolbox to do image manipulation tasks in MATLAB instead of in Photoshop/GIMP.
Still, even without touchup, the results are a lot better than where we started, so decide for yourself how much you really need to achieve.
The functions imtweak(), replacepixels(), colorpict(), and imblend() are part of MIMT, and are available on the File Exchange (see the link above).
For other examples of object color adjustment and colorization, see the following questions and answers:

Community Treasure Hunt

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

Start Hunting!

Translated by