CLICK HERE FOR BLOGGER TEMPLATES AND MYSPACE LAYOUTS »

Saturday, 24 March 2012

Face Recognition Part 1: Detecting skin colour

Introduction

Face recognition is a difficult computer vision problem that needs to be tackled in several steps. The first major issue is detecting where faces are within the picture presented to the software and the very first step of this is to detect areas of skin colour within the picture.

Creating a skin model

There are a large range of skin colours that can be present within a picture and lighting conditions can make a big difference to identifying them. To be able to identify all different skin colours we need to generate a skin model that ignores the luminance of the colour.

To do this we produce an image with a large number of patches of different skin colours. We then use the following algorithm to build a basic colour map for skin colour:

Create a black image 400x400 pixels
For each pixel in the skin colour image
{
Convert the colour of the pixel to YCbCr (http://en.wikipedia.org/wiki/YCbCr)
Draw a white pixel on the black image at (Cb/256 * 400, Cr/256 * 400)
}

From a skin colour image like this:
The algorithm produces a basic colour map like this:


To remove noise from the colour map a binary erosion is applied (see the erode function in this post on Haishi's blog http://haishibai.blogspot.co.uk/2009/09/image-processing-find-binary-image.html) which produces the following skin model (Shown against the full YCbCr model and used as a mask to clearly show the colours identified as skin colour):

Skin Model after binary erosion

YCbCr colour space with Y=1

YCbCr colour space with Y=0

Using the skin model as a mask for the YCbCr colour space with Y=1

Using the skin model as a mask for the YCbCr colour space with Y=0

As you can see when we ignore the luminance (Y) the skin colour model is quite small but allows us to identify a wide range of different skin colours no matter how light or dark they are.

Now that we have a skin model bitmap we can project the colour of any pixel from a picture into the CbCr space that the model covers and test for a white pixel being present. If a white pixel is present then we have identified a skin colour pixel.

C# Code for the skin model:

using System;
using System.Drawing;

namespace EigenFaces
{
public class SkinModel
{
private Bitmap skinMap;
private Bitmap skinColourSpace;

public SkinModel()
{
this.skinColourSpace = new Bitmap(400, 400);

using(Graphics g = Graphics.FromImage(this.skinColourSpace))
{
g.FillRectangle(
Brushes.Black,
new Rectangle(
0,
0,
this.skinColourSpace.Width,
this.skinColourSpace.Height));
}

for (int x = 0; x < this.skinMap.Width; x++ )
{
for (int y = 0; y < this.skinMap.Height; y++)
{
Color pixel = this.skinMap.GetPixel(x, y);
double Cb = this.GetCb(pixel);
double Cr = this.GetCr(pixel);

int colourSpaceX = (int)((Cb/256) * 400);
int colourSpaceY = (int)((Cr/256) * 400);
this.skinColourSpace.SetPixel(
colourSpaceX,
colourSpaceY,
Color.White);
}
}

this.skinColourSpace = Erode(
this.skinColourSpace,
new bool[3, 3]
{
{ false, true, false },
{ true, true, true },
{ false, true, false }
},
3,
3);
}

private static bool ColoursEqual(Color c1, Color c2)
{
return ((c1.A == c2.A) &&
(c1.R == c2.R) &&
(c1.G == c2.G) &&
(c1.B == c2.B));
}

private static Bitmap Erode(
Bitmap normalisedImage,
bool[,] structure,
int structureWidth,
int structureHeight)
{
Bitmap erodedImage = new Bitmap(
normalisedImage.Width,
normalisedImage.Height);

for (int i = 0; i < erodedImage.Width; i++)
{
for (int j = 0; j < erodedImage.Height; j++)
{
bool keep = true;
for (int m = 0; m < structureWidth; m++)
{
for (int n = 0; n < structureHeight; n++)
{
Color pixel = normalisedImage.GetPixel(
Math.Min(
Math.Max(
0,
i + m - structureWidth / 2),
erodedImage.Width - 1),
Math.Min(
Math.Max(
0,
j + n - structureHeight / 2),
erodedImage.Height - 1));
bool pixelSet = ColoursEqual(
pixel,
Color.White);

if ((pixelSet != structure[m, n]) &&
(structure[m, n] != false))
{
keep = false;
break;
}
}
}

erodedImage.SetPixel(
i,
j,
keep ? Color.White : Color.Black);
}
}

return erodedImage;
}

private double GetCr(Color pixel)
{
return 128 +
0.5f * (double)pixel.R -
0.418688f * (double)pixel.G -
0.081312f * (double)pixel.B;
}

private double GetCb(Color pixel)
{
return 128 -
0.168736f * (double)pixel.R -
0.331264f * (double)pixel.G +
0.5f * (double)pixel.B;
}

public bool IsSkin(Color pixel)
{
int Cb = (int)((this.GetCb(pixel)/256) * 400);
int Cr = (int)((this.GetCr(pixel)/256) * 400);

return (ColoursEqual(
this.skinColourSpace.GetPixel(Cb, Cr),
Color.White));
}
}
}

To be continued...