Created
January 7, 2013 19:09
-
-
Save j0hn/4477557 to your computer and use it in GitHub Desktop.
Face classification using SimpleAI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# coding: utf-8 | |
import os | |
import math | |
import random | |
from PIL import Image | |
from simpleai.machine_learning import ClassificationProblem, is_attribute, NaiveBayes, precision, KNearestNeighbors | |
BASE_PATH = os.path.dirname(os.path.abspath(__file__)) | |
CORPUS_PATH = os.path.join(BASE_PATH, "corpus", "faces") | |
class FaceImage(object): | |
""" | |
Example of the face image corpus. | |
Attributes: | |
* userid: the user id of the person in the image | |
* pose: the head position of the person. | |
possible values: straight, left, right, up | |
* expression: the facial expression of the person. | |
possible values: neutral, happy, sad, angry. | |
* eyes: is the eye state of the person. | |
possible values: open, sunglasses. | |
* scale: the scale of the image. | |
possible values: 1, 2, and 4. | |
1 indicates a full-resolution image (128 columns by 120 rows); | |
2 indicates a half-resolution image (64 by 60); | |
4 indicates a quarter-resolution image (32 by 30). | |
""" | |
def __init__(self, filepath): | |
self.image = Image.open(filepath) | |
self.width, self.height = self.image.size | |
self.pixels = self.image.load() | |
# filename format: | |
# <userid> <pose> <expression> <eyes> <scale>.bmp | |
filename = filepath.rsplit("/", 1)[-1] | |
filename = filename[:-4] # Remove the .bmp part | |
fields = filename.split("_") | |
if len(fields) == 4: | |
fields.append("1") # Add scale to the ones without | |
self.userid, self.pose, self.expression, self.eyes, self.scale = fields | |
def __len__(self): | |
return self.width * self.height | |
def __iter__(self): | |
""" | |
Iterates over every pixel in the image and returns | |
a 3-uple with (x, y, intensity) of the pixel. | |
""" | |
for x in xrange(self.width): | |
for y in xrange(self.height): | |
red, green, blue = self.pixels[(x, y)] | |
assert red == green == blue | |
yield x, y, red | |
class FaceClassification(ClassificationProblem): | |
@is_attribute | |
def black_center_of_mass_x(self, image): | |
return self.black_center_of_mass(image)[0] | |
@is_attribute | |
def black_center_of_mass_y(self, image): | |
return self.black_center_of_mass(image)[1] | |
def black_center_of_mass(self, image): | |
mean = self.image_mean_level(image) | |
coords = self.center_of_mass(image, lambda x, y, color: color < mean) | |
return coords | |
return self.discretize(coords) | |
@is_attribute | |
def white_center_of_mass_x(self, image): | |
return self.white_center_of_mass(image)[0] | |
@is_attribute | |
def white_center_of_mass_y(self, image): | |
return self.white_center_of_mass(image)[1] | |
def white_center_of_mass(self, image): | |
mean = self.image_mean_level(image) | |
coords = self.center_of_mass(image, lambda x, y, color: color >= mean) | |
return coords | |
return self.discretize(coords) | |
def discretize(self, coords): | |
bot, top, N = 0.20, 0.8, 10 | |
x, y = coords | |
# x = min(x, top) | |
# x = max(x, bot) - bot | |
# y = min(y, top) | |
# y = max(y, bot) - bot | |
# print coords, x, y | |
return int(N * x), int(N * y) | |
def center_of_mass(self, image, selector): | |
sx = 0 | |
sy = 0 | |
n = 0 | |
for x, y, color in image: | |
if not selector(x, y, color): | |
continue | |
sx += x | |
sy += y | |
n += 1 | |
cx, cy = sx / n, sy / n | |
# cx = cx / image.width | |
# cy = cy / image.height | |
return cx, cy | |
def image_mean_level(self, image): | |
mean = getattr(image, "_mean_level", None) | |
if mean is None: | |
mean = sum(pixel[2] for pixel in image) / len(image) | |
image._mean_level = mean | |
return mean | |
def distance(self, image_a, image_b): | |
be = self.euclidean(self.black_center_of_mass(image_a), | |
self.black_center_of_mass(image_b)) | |
we = self.euclidean(self.white_center_of_mass(image_a), | |
self.white_center_of_mass(image_b)) | |
return be + we | |
def euclidean(self, coords_a, coords_b): | |
ax, ay = coords_a | |
bx, by = coords_b | |
return math.sqrt((ax - bx) ** 2 + (ay - by) ** 2) | |
def target(self, image): | |
return image.eyes | |
class OnlineFacesCorpus(object): | |
def __init__(self, corpus_folder, accept=None): | |
self.corpus_folder = corpus_folder | |
self.accept = accept | |
def __iter__(self): | |
i = 0 | |
for folder in os.listdir(self.corpus_folder): | |
folder_path = os.path.join(self.corpus_folder, folder) | |
if not os.path.isdir(folder_path): | |
continue | |
for filename in os.listdir(folder_path): | |
filepath = os.path.join(folder_path, filename) | |
if os.path.isdir(filepath): | |
continue | |
if filepath.endswith(".bmp"): | |
if self.accept(i): | |
yield FaceImage(filepath) | |
i += 1 | |
if i % 10 == 0: | |
print "read {} examples".format(i) | |
def main(): | |
testindexes = set(random.sample(xrange(1800), 3)) | |
test = OnlineFacesCorpus(CORPUS_PATH, accept=lambda i: i in testindexes) | |
corpus = OnlineFacesCorpus(CORPUS_PATH, accept=lambda i: i not in testindexes) | |
problem = FaceClassification() | |
# i = 0 | |
# for image in corpus: | |
# print [a(image) for a in problem.attributes] | |
# i += 1 | |
# if i == 10: | |
# break | |
# return | |
classifier = KNearestNeighbors(corpus, problem) | |
p = precision(classifier, problem.target, test) | |
print "Precision = {}".format(p) | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# coding: utf-8 | |
from faces import * | |
path = "/some/path/" | |
img = FaceImage(path) | |
prob = FaceClassification() | |
mean = prob.image_mean_level(img) | |
mean = mean * 2 | |
new = img.image.copy() | |
pix = new.load() | |
for x in xrange(img.width): | |
for y in xrange(img.height): | |
if pix[(x, y)][0] > mean: | |
pix[(x, y)] = (255, 255, 255) | |
else: | |
pix[(x, y)] = (0, 0, 0) | |
wx, wy = prob.white_center_of_mass(img) | |
bx, by = prob.black_center_of_mass(img) | |
pix[(wx, wy)] = (255, 0, 0) | |
pix[(bx, by)] = (0, 0, 255) | |
new.save("out.bmp", "BMP") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment