Skip to content

Instantly share code, notes, and snippets.

@zeeshanlakhani
Created December 13, 2012 15:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeeshanlakhani/4277123 to your computer and use it in GitHub Desktop.
Save zeeshanlakhani/4277123 to your computer and use it in GitHub Desktop.
face-detection ~ visual-mean (no cropping approach in this file, just detection)
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