Sunday, August 19, 2012

Skin detection in digital images

Face detection has been quite "hot" topic lately. Perhaps one reason is that
modern computers are now powerful enough to process sequential video frames
in real time.

There are many techniques for face detection, one approach is to first classify the pixels to be "skin" or "not skin" pixels. When the "skin" pixels have been found, the noise is removed and pixels are combined to create larger groups. If we are lucky one or more of this kind of pixel groups are part of a face. The pixel group(s) can then be inputted to a face detection algorithm, which makes the final detection for eyes, nose and mouth etc.

Pixel based operations needed for skin color detection are simple, and that is what makes the skin color techniques so attractive.

The skin color seems to be quite easy to spot, "just choose pixels which loosely matches skin color". But actually the problem is more difficult than it first seems. The skin color varies from dark to light and is heavily influenced by lightning.

The lightning conditions can make person's skin color completely different. The lightning color can be yellow, blue or white, and the image can be taken with or without a flash. There can be distracting highlights on skin of the face or the background can contain similar colors as skin.

As many other algorithms, also my algorithm expects images to be shot in normal daylight, without excessive higlights or shadows.

To actually detect a face, there must be some assumptions about the image. The face must be in the image and there should be as little as possible other exposed skin in the image. Suitable images are, for example, images that are coming from the laptop webcams.

To maximize the detection of skin colored pixels, many have proposed to use several detection mechanisms and then combining the results. I am here doing the same thing. In the following, I am presenting three methods for detecting pixels with skin color; using RGB-space, YCrCb-space and HSI-space. 

Note: I am not attaching the full source code here. If there is a huge demand for that, I can do it later on... 

Example images

Here you can see some of the test images downloaded from The USC-SIPI Image Database (http://sipi.usc.edu/database/) and from Kodak (http://r0k.us/graphics/kodak/).

I have ran the skin detection algorithm described below, and the pixels that the algorithm "guesses" to be skin are highlighted with red color.








Skin color cluster in RGB color space

Skin color cluster in RGB color space (R,G,B) is classified as skin if [1]:
R > 95 and G > 40 and B > 20 and
max{R,G,B}-min{R,G,B} > 15 and 
|R-G| > 15 and R > G and R > B

This can be written to a Java function:
// We are assuming here that most pixels in the random image
// are non-skin pixels. So let's try to get out of the function
// as fast as possible to avoid slowest operations, min and max.
public boolean isSkinRGB(int r, int g, int b) {
    // first easiest comparisons
    if ( (r<95) | (g<40) | (b<20) | (r<g) | (r<b) ) {
        return false; // no match, stop function here
    }
    int d = r-g;
    if ( -15<d && d<15) {
        return false; // no match, stop function here
    }
    // we have left most time consuming operation last
    // hopefully most of the time we are not reaching this point
    int max = max(r,g,b);
    int min = min(r,g,b);
    if ((max-min)<15) {
        // this is the worst case
        return false; // no match, stop function 
    }
    // all comparisons passed
    return true;
}

Skin color cluster in YCrCb color space 

First we need to convert RGB color values to YCrCb color values.

To convert RGB color values to YCrCb color values we can use following formula [5]:
Y  = (0.257*R) + (0.504*G) + (0.098*B) + 16 
Cr = (0.439*R) - (0.368*G) - (0.071*B) + 128
Cb = -(0.148*R)- (0.291*G) + (0.439*B) + 128

Using these values we can classify (Y,Cr,Cb) as skin if [3]:
Cr <= 1.5862 × Cb + 20 AND
Cr <= 0.3448 × Cb + 76.2069 AND
Cr <= -4.5652 × Cb + 234.5652 AND
Cr >= -1.15 × Cb + 301.75 AND
Cr >= -2.2857 × Cb + 432.85 AND

This can be written to a Java function:
class YCrCb {
    double Y = 0.0;
    double Cr = 0.0;
    double Cb = 0.0;
    
    public static YCrCb RGB2YCbCr(double R, double G, double B) {
        YCrCb result = new YCrCb();
        result.Y  = (0.257*R) + (0.504*G) + (0.098*B) + 16; 
        result.Cr = (0.439*R) - (0.368*G) - (0.071*B) + 128;
        result.Cb = -(0.148*R)- (0.291*G) + (0.439*B) + 128;
        return result;
    }
}

// Like in RGB case, let's get out of the function as
// fast as possible to avoid unnecessary multiplications. 
public static boolean isSkinYCbCr(double r, double g, double b) {
    YCrCb result = YCrCb.RGB2YCbCr(r, g, b);
    double cr = result.Cr;
    double cb = result.Cb;
    if (cr >= ((1.5862*cb) + 20)) {
        return false;
    }
    if (cr <= ((0.3448*cb) + 76.2069)) {
        return false;
    }
    if (cr <= ((-4.5652*cb) + 234.5652)) {
        return false;
    }
    if (cr >= ((-1.15*cb) + 301.75)) {
        return false;
    }
    if (cr >= ((-2.2857*cb) + 432.85)) {
        return false;
    }
    // all comparisons passed
    return true;
}

Skin color cluster in HSI color space 

For calculating HSI color values, we can use following method [4]:

First we need to normalize (R,G,B):
r = R / R + G + B
g = G / R + G + B
b = B / R + G + B

Then we can use the values:
h = cos^-1((R-G)+(R-B)/2*sqrt((R-G)^2+(R-B)(G-B)))
s = 1-3*min(r,g,b) : 0 <= s <= 1
i = (R + G + B) / (3*255) : 0 <= i <= 1
h = 360 - h, if (B>G) 

This can be written in Java like this:
class HSI {
    double h = 0.0;
    double s = 0.0;
    double i = 0.0;

    public HSI RGB2HSI(int R, int G, int B) {
        HSI result = new HSI();
        result.i = (R+G+B)/3.0;    // we have calculated I!
        if (R==G&&G==B) {
            return result;    // return result with h=0.0 and s=0.0
        }
        double r = R/i;            // normalize R
        double g = G/i;            // normalize G
        double b = B/i;            // normalize B
        double w = 0.5*(R-G+R-B) / Math.sqrt((R-G)*(R-G)+(R-B)*(G-B));
        if (w>1) W = 1.0;       // clip input for acos to -1 <= w <= 1
        if (w<-1) W = -1.0;     // clip input for acos to -1 <= w <= 1
        result.h = Math.acos(w);   // the value is 0 <= h <= Math.PI
        if (B>G) {
            result.h = 2*Math.PI - result.h;
        }
        // finally the last component s
        result.s = 1-3*min(r,g,b);
    }
}
The skin color cluster in HSI space is simply:
H < 25 
H > 230

Thus, we can write skin color detection in HSI-space in Java like this:

public boolean isSkinHSI(double r, double g, double b) {
    HSI hsi = RGB2HSI(r,g,b);
    if (hsi.h<25||hsi.h>230) {
        return true;
    )
    return false;
}
All in one

We can now use all these methods combined to make as good as possible guess about pixels being part of skin or not. It is enough to load an image and make the test for each pixel.

The Java-function to be used to mark skin pixels with color red (0x00FF0000) with any sized BufferedImage could look like this:
public static BufferedImage detect(BufferedImage in) {
    BufferedImage out = new BufferedImage(in.getWidth(),in.getHeight(),BufferedImage.TYPE_INT_RGB);
    int red = 0x00FF0000;
    int[] pixels = new int[in.getWidth()*in.getHeight()];
    in.getRGB(0,0, in.getWidth(), in.getHeight(), pixels, 0, in.getWidth());
    for (int i=0;i<pixels.length;i++) {
        int c = pixels[i];
        int r = (c&0x00FF0000)>>16;
        int g = (c&0x0000FF00)>>8;
        int b = (c&0x000000FF)>>0;
        int a = 0;
        if (isSkinYCbCr(r,g,b)) {
            a+=1;
        }
        if (isSkinRGB(r, g, b)) {
            a+=1;
        }
        if (isSkinHSI(r, g, b)) {
            a+=1;
        }
        if (a==3) { // if all the methods respond "true", mark the pixel
            pixels[i]= red;
        }
    }
    out.setRGB(0,0, in.getWidth(), in.getHeight(), pixels, 0, in.getWidth());
    return out;
}    

Face detection

This algorithm can be used to detect face, or at least give quite good guess where the face could be, when attached to the image capturing software like in my post: Capturing webcam image with Java Media Framework.

References and further reading:
[1]
Jure Kovaˇc, Peter Peer, and Franc Solina
Human Skin Colour Clustering for Face Detection
http://www.lrv.fri.uni-lj.si/~peterp/publications/eurocon03.pdf

[2]
Vladimir Vezhnevets, Vasili Sazonov, Alla Andreeva 
A Survey on Pixel-Based Skin Color Detection Techniques
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.5.521&rep=rep1&type=pdf

[3]
Nusirwan Anwar bin Abdul Rahman, Kit Chong Wei and John See
RGB-H-CbCr Skin Colour Model for Human Face Detection
http://pesona.mmu.edu.my/~johnsee/research/papers/files/rgbhcbcr_m2usic06.pdf

[4]
Conversion from RGB to HSI
Ruye Wang
http://fourier.eng.hmc.edu/e161/lectures/color_processing/node3.html

[5] 
RGB to YCbCr 
http://www.fourcc.org/fccyvrgb.php

13 comments:

@dhanifitong said...

hi Jussi, your post was just amazing for me. the algorithms was awesome.
could you send me the java classes, then i could start to learning on it :)

SOKIYAH, S.Pd said...

i really need the code..
plz send to my email jussi..

Dror said...

Seems like you have a bug in RGB2HSI method:
result.i = R+G+B/3.0; should be result.i = (R+G+B)/3.0;

Jussi said...

Thank you very much for the correction!

Jussi said...

Many have requested the source code. Unfortunately I do not have time for posting that right now... But I will take a look if I manage to create something in the near future.

Chabba said...

Hi admin :)
I really liked your post, is it possible to publish the full source code (or send it via email? )? I would really appreciate it :)
cheers

Anonymous said...

There are a lot of mismatch between what the author says he is about to do and what he does. To make this work, stick to the source code not the description. The last method I haven't been able to get working regardless.

Jussi said...

I am sorry if my textual description for the process is vague or conflicting to the source code.

Yes, I could post the full source as has been requested; I will do that when I have good time slot for that.

Naveen said...

hello sir,
i am doing a project in face detection and i am in desperate need of a skin detection algorithm. i want to use this algorithm to cut the facial part from the image instead of detecting it. ur code will be very help full. can u pl post the source code or pl send it through mail..pl help!!

Anonymous said...

hi your algorithm was excellent . am doing a project that should differentiate face from palm , can u mail me algorithm/source code that detects face from palm .

Jussi said...

Hi! The source code is not able to detect faces. It can only detect pixels from the image with color values that "could be" skin.

Krzysiek said...

Thanks for posting your code. I found big bug in yCrCb conversion, some conditions are denied. After that worked well in my case

Unknown said...

Hi admin,

I really appreciate this post and the references to reading material. I'm doing a study on skin tone detection optimization and would be really thankful if you could post the source code.

Thanks,
Vivek