Commit 6cd0955d authored by Guillaume HEUSCH's avatar Guillaume HEUSCH
Browse files

[implementation] removed unecessary script files, moved implementation into __init__.py

parent fc0d47a5
from . import script
#!/usr/bin/env python
# encoding: utf-8
# Guillaume HEUSCH <guillaume.heusch@idiap.ch>
# Wed 29 Jul 09:27:26 CEST 2015
def get_config():
"""Returns a string containing the configuration information.
import sys
import numpy
class SkinColorFilter():
"""
This class implements a number of functions to perform skin color filtering.
It is based on the work published in "Adaptive skin segmentation via feature-based face detection",
M.J. Taylor and T. Morris, Proc SPIE Photonics Europe, 2014 [taylor-spie-2014]_
**Attributes:**
``mean`` : (numpy array 2x1)
the mean skin color
``covariance`` : (numpy array 2x2)
the covariance matrix of the skin color
``covariance_inverse`` : (numpy array 2x2)
the inverse covariance matrix of the skin color
``circular_mask`` : (numpy logical array)
mask of the size of the image, defining a circular region in the center
``luma_mask`` : (numpy logical array)
mask of the size of the image, defining valid luma values
"""
import bob.extension
return bob.extension.get_config(__name__)
def __init__(self):
self.mean = numpy.array([0.0, 0.0])
self.covariance = numpy.zeros((2, 2), 'float64')
self.covariance_inverse = numpy.zeros((2, 2), 'float64')
def __generate_circular_mask(self, image, radius_ratio=0.4):
"""__generate_circular_mask(image, [radius_ratio]
This function will generate a circular mask to be applied to the image.
The mask will be true for the pixels contained in a circle centered in the image center,
and with radius equals to radius_ratio * the image's height.
**Parameters:**
``image`` : (numpy array)
The face image.
``radius_ratio`` (Optional[float]):
the ratio of the image's height to define the radius of the circular region.
Defaults to 0.4.
"""
x_center = image.shape[1] / 2
y_center = image.shape[2] / 2
# arrays with the image coordinates
x = numpy.zeros((image.shape[1], image.shape[2]))
x[:] = range(0, x.shape[1])
y = numpy.zeros((image.shape[2], image.shape[1]))
y[:] = range(0, y.shape[1])
y = numpy.transpose(y)
# translate s.t. the center is the origin
x -= x_center
y -= y_center
# condition to be inside of a circle: x^2 + y^2 < r^2
radius = radius_ratio*image.shape[2]
self.circular_mask = (x**2 + y**2) < (radius**2)
def __remove_luma(self, image):
"""__remove_luma(image)
This function remove pixels with extreme luma values.
Some pixels are considered as non-skin if their intensity is either too high or too low.
The luma value for all pixels inside a provided circular mask is calculated. Pixels for which the
luma value deviates more than 1.5 * standard deviation are pruned.
**Parameters:**
``image`` : (numpy array)
The face image.
"""
# compute the mean and std of luma values on non-masked pixels only
luma = 0.299*image[0, self.circular_mask] + 0.587*image[1, self.circular_mask] + 0.114*image[2, self.circular_mask]
m = numpy.mean(luma)
s = numpy.std(luma)
# apply the filtering to the whole image to get the luma mask
luma = 0.299*image[0, :, :] + 0.587*image[1, :, :] + 0.114*image[2, :, :]
self.luma_mask = numpy.logical_and((luma > (m - 1.5*s)), (luma < (m + 1.5*s)))
def estimate_gaussian_parameters(self, image):
"""estimate_gaussian_parameters(image)
This function estimates the parameter of the skin color distribution.
The mean and covariance matrix of the skin pixels in the normalised rg colorspace are computed.
Note that only the pixels for which both the circular and the luma mask is 'True' are considered.
**Parameters:**
``image`` : (numpy array)
The face image.
"""
self.__generate_circular_mask(image)
self.__remove_luma(image)
mask = numpy.logical_and(self.luma_mask, self.circular_mask)
# get the mean
channel_sum = image[0].astype('float64') + image[1] + image[2]
nonzero_mask = numpy.logical_or(numpy.logical_or(image[0] > 0, image[1] > 0), image[2] > 0)
r = numpy.zeros((image.shape[1], image.shape[2]))
r[nonzero_mask] = image[0, nonzero_mask] / channel_sum[nonzero_mask]
g = numpy.zeros((image.shape[1], image.shape[2]))
g[nonzero_mask] = image[1, nonzero_mask] / channel_sum[nonzero_mask]
self.mean = numpy.array([numpy.mean(r[mask]), numpy.mean(g[mask])])
# get the covariance
r_minus_mean = r[mask] - self.mean[0]
g_minus_mean = g[mask] - self.mean[1]
samples = numpy.vstack((r_minus_mean, g_minus_mean))
samples = samples.T
cov = sum([numpy.outer(s,s) for s in samples])
self.covariance = cov / float(samples.shape[0] - 1)
# store the inverse covariance matrix (no need to recompute)
if numpy.linalg.det(self.covariance) != 0:
self.covariance_inverse = numpy.linalg.inv(self.covariance)
else:
self.covariance_inverse = numpy.zeros_like(self.covariance)
def get_skin_mask(self, image, threshold):
"""get_skin_mask(image, [threshold]) -> skin_mask
This function computes the probability of skin-color for each pixel in the image.
**Parameters:**
``image`` : (numpy array)
The face image.
``threshold`` : (Optional, float between 0 and 1)
the threshold on the skin color probability. Defaults to 0.5
**Returns:**
``skin_mask`` : (numpy logical array)
The mask where skin color pixels are labeled as True.
"""
skin_map = numpy.zeros((image.shape[1], image.shape[2]), 'float64')
# get the image in rg colorspace
channel_sum = image[0].astype('float64') + image[1] + image[2]
nonzero_mask = numpy.logical_or(numpy.logical_or(image[0] > 0, image[1] > 0), image[2] > 0)
r = numpy.zeros((image.shape[1], image.shape[2]), 'float64')
r[nonzero_mask] = image[0, nonzero_mask] / channel_sum[nonzero_mask]
g = numpy.zeros((image.shape[1], image.shape[2]), 'float64')
g[nonzero_mask] = image[1, nonzero_mask] / channel_sum[nonzero_mask]
# compute the skin probability map
r_minus_mean = r - self.mean[0]
g_minus_mean = g - self.mean[1]
v = numpy.dstack((r_minus_mean, g_minus_mean))
v = v.reshape((r.shape[0]*r.shape[1], 2))
probs = [numpy.dot(k, numpy.dot(self.covariance_inverse, k)) for k in v]
probs = numpy.array(probs).reshape(r.shape)
skin_map = numpy.exp(-0.5 * probs)
# gets sphinx autodoc done right - don't remove it
__all__ = [_ for _ in dir() if not _.startswith('_')]
return skin_map > threshold
#!/usr/bin/env python
# encoding: utf-8
# Guillaume HEUSCH <guillaume.heusch@idiap.ch>
# Tue 5 Apr 11:20:29 CEST 2016
"""Test Units
"""
import nose.tools
import numpy
import bob.ip.skincolorfilter.skin_color_filter as scf
skin_filter = scf.SkinColorFilter()
def test_circular_mask():
"""
Test the generation of the circular mask
"""
# limit case: the center of the image is located at (0,0)
# so it's considered as inside (x**2 + y**2) = 0 < 0.4
image = numpy.zeros((3, 1, 1))
skin_filter.generate_circular_mask(image)
assert numpy.all(skin_filter.circular_mask), "a 1x1 image should be True"
# easy case - the "cross" should be true
image = numpy.zeros((3, 3, 3))
skin_filter.generate_circular_mask(image)
assert skin_filter.circular_mask[0, 1], "middle-top should be inside"
assert numpy.all(skin_filter.circular_mask[1, :]), "the whole middle line should be inside"
assert skin_filter.circular_mask[2, 1], "middle-bottom should be inside"
# more realistic case - radius will be 0.4*15=6 pixels
image = numpy.zeros((3, 15, 15))
skin_filter.generate_circular_mask(image)
print skin_filter.circular_mask
# left
assert not(skin_filter.circular_mask[7, 1]), "(7,1) should not be inside"
assert skin_filter.circular_mask[7, 2], "(7,2) should be inside"
# top
assert not(skin_filter.circular_mask[1, 7]), "(1,7) should not be inside"
assert skin_filter.circular_mask[2, 7], "(2,7) should be inside"
# right
assert not(skin_filter.circular_mask[7, 13]), "(7,13) should not be inside"
assert skin_filter.circular_mask[7, 12], "(7,12) should be inside"
# bottom
assert not(skin_filter.circular_mask[13, 7]), "(13, 7) should not be inside"
assert skin_filter.circular_mask[12, 7], "(12, 7) should be inside"
def test_luma_mask():
"""
Test the generation of the luma mask
"""
# generate a greyish image
image = numpy.ones((3, 11, 11))*(numpy.random.standard_normal((3,11,11)) + 128)
image[:, 0, :] = 0 # first line is black
image[:, -1, :] = 255 # last line is white
# the circular mask (to compute mean and std luma)
skin_filter.generate_circular_mask(image)
skin_filter.remove_luma(image)
# the first and last line should be all False - extreme values
assert not(numpy.all(skin_filter.luma_mask[:, 0]))
assert not(numpy.all(skin_filter.luma_mask[:, -1]))
# there should be at least one True everywhere else
assert numpy.any(skin_filter.luma_mask)
def test_estimate_parameters():
"""
Test the ML estimation of the Gaussian parameters
"""
# a red image
image = numpy.zeros((3, 11, 11))
image[0, :, :] = 255
skin_filter.get_gaussian_parameters(image)
assert (skin_filter.mean == [1.0, 0.0]).all(), "mean for a red image is not OK"
assert (skin_filter.covariance == [[0.0, 0.0], [0.0, 0.0]]).all(), "covariance for red image is not OK"
# a green image
image = numpy.zeros((3, 11, 11))
image[1, :, :] = 255
skin_filter.get_gaussian_parameters(image)
assert (skin_filter.mean == [0.0, 1.0]).all(), "mean for a green image is not OK"
assert (skin_filter.covariance == [[0.0, 0.0], [0.0, 0.0]]).all(), "covariance for green image is not OK"
import sys
import numpy
import bob.io.base
import bob.io.image
import bob.ip.facedetect
from ..skin_color_filter import SkinColorFilter
def show_image(image, title=''):
"""
This function just shows an image with a title (if provided)
:param image: the image to show
:param title: the title of the plot
"""
from matplotlib import pyplot
# color image
if len(image.shape) == 3:
im = image.transpose(1,2,0)
pyplot.figure()
pyplot.imshow(im)
pyplot.title(title)
pyplot.show()
# grayscale
else:
pyplot.figure()
pyplot.imshow(image.astype(numpy.uint8), cmap='gray')
pyplot.title(title)
pyplot.show()
def main(user_input=None):
image = bob.io.base.load('./bob/ip/skincolorfilter/data/001.bmp')
show_image(image, 'original image')
detection = bob.ip.facedetect.detect_single_face(image)
if detection is not None:
bb, quality = detection
face = image[:, bb.top:bb.bottom, bb.left:bb.right]
show_image(face, 'cropped face')
skin_filter = SkinColorFilter()
skin_filter.get_gaussian_parameters(face)
skin_mask = skin_filter.get_skin_pixels(image, 0.5)
display_skin = numpy.copy(image)
display_skin[:, numpy.logical_not(skin_mask)] = 0
show_image(display_skin, 'skin pixels')
else:
print "No face detected in the image"
sys.exit()
#!/usr/bin/env python
# encoding: utf-8
# Guillaume HEUSCH <guillaume.heusch@idiap.ch>
# Wed 29 Jul 09:27:26 CEST 2015
import sys
import numpy
class SkinColorFilter():
"""
This class implements a number of functions to perform skin color filtering. It is based on the work published in
"Adaptive skin segmentation via feature-based face detection", M.J. Taylor and T. Morris, Proc SPIE Photonics Europe, 2014
"""
def __init__(self):
self.mean = numpy.array([0.0, 0.0])
self.covariance = numpy.zeros((2, 2), 'float64')
self.covariance_inverse = numpy.zeros((2, 2), 'float64')
def generate_circular_mask(self, image, radius_ratio=0.4):
"""
This function will generate a circular mask to be applied to the image.
The mask will be true for the pixels contained in a circle centered in the image center,
and with radius equals to radius_ratio * the image's height.
:param image: the (face) image to mask
"""
x_center = image.shape[1] / 2
y_center = image.shape[2] / 2
# arrays with the image coordinates
x = numpy.zeros((image.shape[1], image.shape[2]))
x[:] = range(0, x.shape[1])
y = numpy.zeros((image.shape[2], image.shape[1]))
y[:] = range(0, y.shape[1])
y = numpy.transpose(y)
# translate s.t. the center is the origin
x -= x_center
y -= y_center
# condition to be inside of a circle: x^2 + y^2 < r^2
radius = radius_ratio*image.shape[2]
self.circular_mask = (x**2 + y**2) < (radius**2)
def remove_luma(self, image):
"""
This function remove pixels that are considered as non-skin according to their luma values.
The luma value for all pixels inside a provided circular mask is calculated. Pixels for which the
luma value deviates more than 1.5 * standard deviation are pruned.
:param image: the (face) image to process
"""
# compute the mean and std of luma values on non-masked pixels only
luma = 0.299*image[0, self.circular_mask] + 0.587*image[1, self.circular_mask] + 0.114*image[2, self.circular_mask]
m = numpy.mean(luma)
s = numpy.std(luma)
# apply the filtering to the whole image to get the luma mask
luma = 0.299*image[0, :, :] + 0.587*image[1, :, :] + 0.114*image[2, :, :]
self.luma_mask = numpy.logical_and((luma > (m - 1.5*s)), (luma < (m + 1.5*s)))
def get_gaussian_parameters(self, image):
"""
This function computes the mean and covariance matrix of the skin pixels in the normalised rg colorspace.
Note that only the pixels for which both the circular and the luma mask is 'True' are considered.
:param image: the (face) image out of which skin color characteristics will be extracted.
"""
self.generate_circular_mask(image)
self.remove_luma(image)
mask = numpy.logical_and(self.luma_mask, self.circular_mask)
# get the mean
channel_sum = image[0].astype('float64') + image[1] + image[2]
nonzero_mask = numpy.logical_or(numpy.logical_or(image[0] > 0, image[1] > 0), image[2] > 0)
r = numpy.zeros((image.shape[1], image.shape[2]))
r[nonzero_mask] = image[0, nonzero_mask] / channel_sum[nonzero_mask]
g = numpy.zeros((image.shape[1], image.shape[2]))
g[nonzero_mask] = image[1, nonzero_mask] / channel_sum[nonzero_mask]
self.mean = numpy.array([numpy.mean(r[mask]), numpy.mean(g[mask])])
# get the covariance
r_minus_mean = r[mask] - self.mean[0]
g_minus_mean = g[mask] - self.mean[1]
samples = numpy.vstack((r_minus_mean, g_minus_mean))
samples = samples.T
cov = sum([numpy.outer(s,s) for s in samples])
self.covariance = cov / float(samples.shape[0] - 1)
# store the inverse covariance matrix (no need to recompute)
if numpy.linalg.det(self.covariance) != 0:
self.covariance_inverse = numpy.linalg.inv(self.covariance)
else:
self.covariance_inverse = numpy.zeros_like(self.covariance)
def get_skin_pixels(self, image, threshold):
"""
This function computes the probability of skin-color for each pixel in the image.
the distribution of skin color is considered to be gaussian in the rg colorspace.
:param image: the image to process
:param threshold: the threshold on the probability of a pixel to be of skin color.
"""
skin_map = numpy.zeros((image.shape[1], image.shape[2]), 'float64')
# get the image in rg colorspace
channel_sum = image[0].astype('float64') + image[1] + image[2]
nonzero_mask = numpy.logical_or(numpy.logical_or(image[0] > 0, image[1] > 0), image[2] > 0)
r = numpy.zeros((image.shape[1], image.shape[2]), 'float64')
r[nonzero_mask] = image[0, nonzero_mask] / channel_sum[nonzero_mask]
g = numpy.zeros((image.shape[1], image.shape[2]), 'float64')
g[nonzero_mask] = image[1, nonzero_mask] / channel_sum[nonzero_mask]
# compute the skin probability map
r_minus_mean = r - self.mean[0]
g_minus_mean = g - self.mean[1]
v = numpy.dstack((r_minus_mean, g_minus_mean))
v = v.reshape((r.shape[0]*r.shape[1], 2))
probs = [numpy.dot(k, numpy.dot(self.covariance_inverse, k)) for k in v]
probs = numpy.array(probs).reshape(r.shape)
skin_map = numpy.exp(-0.5 * probs)
return skin_map > threshold
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment