Created
December 13, 2012 15:27
-
-
Save zeeshanlakhani/4277123 to your computer and use it in GitHub Desktop.
face-detection ~ visual-mean (no cropping approach in this file, just detection)
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
import os, sys, glob | |
import cv | |
import math | |
import argparse | |
HAAR_CASCADE_FRONT = \ | |
"/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_alt2.xml" | |
HAAR_CASCADE_PROFILE = \ | |
"/usr/local/share/OpenCV/haarcascades/haarcascade_profileface.xml" | |
# 600 pixels is our current threshold for image height/width | |
SIZE_THRESHOLD = 600 | |
MIN_WINDOW_SIZE_PROFILE = 70 | |
MIN_WINDOW_SIZE_FRONT = 70 | |
IMAGE_SCALE_FACTOR = 1 | |
DEVIATIONS_TRESHOLD = 1 | |
class SmartCrop(object): | |
""" | |
Base class for SmartCrop (using face detection) for Image Cropping. | |
""" | |
def __init__(self, image, **kwargs): | |
self.image = image | |
self.cvImage = cv.LoadImage(image) | |
self.name = image.title().split('/')[-1] | |
self.faces = [] | |
if 'out_dir' in kwargs: | |
self.out_dir = kwargs['out_dir'] | |
self.get_point = self._detect_faces_get_point() | |
@property | |
def get_center(self): | |
point = self.get_point | |
# float the denominator in order to get float output | |
return (point[0]/float(self.cvImage.width), | |
point[1]/float(self.cvImage.height)) | |
@property | |
def draw_from_points(self): | |
""" | |
For human testing. | |
See image's faces (green rects) and derived center (yellow dot). | |
""" | |
if self.faces: | |
for (x, y, w, h) in self.faces: | |
cv.Rectangle(self.cvImage, (x,y), (x+w, y+h), cv.RGB(0, 255, 0)) | |
cv.Circle(self.cvImage, self.get_point, 8, cv.RGB(255,255,0), -1) | |
cv.SaveImage('{}/{}'.format(self.out_dir, self.name), self.cvImage) | |
def _detect_faces_get_point(self): | |
""" | |
Main method for detecting faces and getting the visual mean. | |
""" | |
center_points = [] | |
detected = [] | |
image_scale = IMAGE_SCALE_FACTOR | |
# These Window Sizes may be updated for "larger" than THRESHOLD images | |
window_size_profile = MIN_WINDOW_SIZE_PROFILE | |
window_size_front = MIN_WINDOW_SIZE_FRONT | |
storage = cv.CreateMemStorage(0) | |
cascade_profile = cv.Load(HAAR_CASCADE_PROFILE) | |
cascade_front = cv.Load(HAAR_CASCADE_FRONT) | |
# Convert to grayscale | |
grayscale = cv.CreateImage((self.cvImage.width, self.cvImage.height), | |
8, 1) | |
cv.CvtColor(self.cvImage, grayscale, cv.CV_BGR2GRAY) | |
# Downsample | |
if grayscale.width >= SIZE_THRESHOLD and \ | |
grayscale.height >= SIZE_THRESHOLD: | |
# Fixed Scale for "larger" than THRESHOLD images. | |
image_scale = 2.5 | |
# Smooth grayscale image and Resize/Downsample | |
cv.Smooth(grayscale, grayscale, cv.CV_GAUSSIAN, 3, 0) | |
runner_image = cv.CreateImage((cv.Round(grayscale.width/image_scale), | |
cv.Round(grayscale.height/image_scale)), 8 ,1) | |
cv.Resize(grayscale, runner_image, interpolation=cv.CV_INTER_LINEAR) | |
# Check runner_image image after smoothing. Just for testing | |
# cv.NamedWindow('w1', cv.CV_WINDOW_AUTOSIZE) | |
# cv.ShowImage('w1', runner_image) | |
# cv.WaitKey() | |
# Perform algorithm and combine front-facing and profile lists | |
detected_front = cv.HaarDetectObjects(runner_image, cascade_front, | |
storage, 1.2, 2, cv.CV_HAAR_DO_CANNY_PRUNING, (window_size_front, | |
window_size_front)) | |
detected_profile = cv.HaarDetectObjects(runner_image, cascade_profile, | |
storage, 1.2, 2, cv.CV_HAAR_DO_CANNY_PRUNING, (window_size_profile, | |
window_size_profile)) | |
detected = detected_front + detected_profile | |
if detected: | |
for (x, y, w, h), n in detected: | |
_x = int(x * image_scale) | |
_y = int(y * image_scale) | |
_w = int(w * image_scale) | |
_h = int(h * image_scale) | |
self.faces.append((_x,_y,_w,_h)) | |
for (x, y, w, h) in self.faces: | |
mid_center = (x + (w/2), y + (h/2)) | |
center_points.append(mid_center) | |
return self._get_visual_center(center_points) | |
return (self.cvImage.width/2, self.cvImage.height/2) | |
def _get_visual_center(self, point_list): | |
""" | |
Get the visual mean of all the mid-centers of our faces. | |
""" | |
avg_point = tuple([sum(point) / len(point) for point in zip(*point_list)]) | |
return self._remove_bad_faces(point_list, avg_point) | |
def _getdeviations(self, point, avg, stddevs): | |
""" | |
Gets number of standard deviations from the mean. | |
""" | |
sd_point = [0, 0] | |
for idx, sd in enumerate(stddevs): | |
if sd <= 0: | |
sd_point[idx] = 0 | |
else: | |
sd_point[idx] = math.fabs(point[idx] - avg[idx])/sd | |
return sd_point | |
def _remove_bad_faces(self, points, avg): | |
""" | |
Remove points that have both coords more than 1 | |
standard deviation from the mean. | |
Also removes a "bad" point from self.faces. | |
""" | |
new_points = [] | |
variances = map(lambda (x, y): ((x - avg[0])**2,(y - avg[1])**2), points) | |
stddevs = [math.sqrt(sum(point)/len(point)) for point in zip(*variances)] | |
for idx, point in enumerate(points): | |
check_point = self._getdeviations(point, avg, stddevs) | |
if check_point[0] > DEVIATIONS_TRESHOLD and \ | |
check_point[1] > DEVIATIONS_TRESHOLD: | |
self.faces[idx] = None | |
else: | |
new_points.append(point) | |
if new_points: | |
self.faces = filter(None, self.faces) | |
return tuple([sum(point) / len(point) for point in zip(*new_points)]) | |
else: | |
return avg | |
def runner(args): | |
file_input = args.file_input | |
file_output = args.file_output | |
images = [] | |
if os.path.isdir(file_input): | |
exts = ['jpg', 'jpeg', 'bmp', 'png', 'gif', 'Jpeg', 'Gif', 'Bmp', 'Png'] | |
exts.extend([ext.upper() for ext in exts]) | |
for ext in exts: | |
search_inside = os.path.join(file_input,'*.{}'.format(ext)) | |
images.extend(glob.glob(search_inside)) | |
else: | |
images.append(file_input) | |
if not os.path.exists(file_output): | |
os.makedirs(file_output) | |
for image in images: | |
new = SmartCrop(image, out_dir=file_output) | |
#print new.get_point | |
#print new.get_center | |
new.draw_from_points | |
def main(): | |
parser = argparse.ArgumentParser(description='Run SmartCrop program') | |
parser.add_argument('-i', dest='file_input', default='.', | |
help='A path to an image or directory for input') | |
parser.add_argument('-o', dest='file_output', default='.', | |
help='A path to an image or directory for output') | |
args = parser.parse_args() | |
runner(args) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment