Commit 30f2c6df authored by Andreas Tille's avatar Andreas Tille

Imported Upstream version 0.0.+20150428

This diff is collapsed.
facedetect: a simple face detector for batch processing
`facedetect` is a simple face detector for batch processing. It answers the
basic question: "Is there a face in this image?" and gives back either an exit
code or the coordinates of each detected face in the standard output.
The aim is to provide a basic command-line interface that's consistent and easy
to use with software such as ImageMagick_, while progressively improving the
detection algorithm over time.
`facedetect` is currently used in fgallery_ to improve the thumbnail cutting
region, so that faces are always centered.
By default `facedetect` outputs the rectangles of all the detected faces::
./facedetect path/to/image.jpg
289 139 56 56
295 283 55 55
The output values are the X Y coordinates (from the top-left corner),
followed by width and height. For debugging, you can examine the face positions
directly overlaid on the source image using the ``-o`` flag::
./facedetect -o test.jpg path/to/image.jpg
To simply check if an image contains a face, use the ``-q`` switch and check
the exit status::
./facedetect -q path/to/image.jpg
echo $?
An exit status of 0 indicates the presence of at least one face. An exit status
of 2 means that no face could be detected (1 is reserved for failures).
The ``--center`` flag also exists for scripting convenience, and simply outputs
the X Y coordinates of face centers::
./facedetect --center path/to/image.jpg
317 167
322 310
The ``--biggest`` flag only outputs the biggest face in the image, while
``--best`` will attempt to select the face in focus and/or in the center of the
.. figure:: doc/biggest-best.jpg
:align: center
Comparison between ``--best`` (top) and ``--biggest`` (bottom). The actual
chosen face is highlighted in yellow.
Unless DOF or motion blur is used effectively by the photographer to separate
the subject, ``--biggest`` would in most cases select the same face as
``--best``, while being significantly faster to compute.
Sorting images with and without faces
The following example sorts pictures into two different "landscape"
and "portrait" directories using the exit code::
for file in path/to/pictures/*.jpg; do
name=$(basename "$file")
if facedetect -q "$file"; then
mv "$file" "path/to/portrait/$name"
mv "$file" "path/to/landscape/$name"
Blurring faces within an image
The following example uses the coordinates from `facedetect` to pixelate the
faces in all the source images using `mogrify` (from ImageMagick_)::
for file in path/to/pictures/*.jpg; do
name=$(basename "$file")
cp "$file" "$out"
facedetect "$file" | while read x y w h; do
mogrify -gravity NorthWest -region "${w}x${h}+${x}+${y}" \
-scale '10%' -scale '1000%' "$out"
Here ``mogrify`` is called for each output line of `facedetect` (which is
sub-optimal), modifying the file in-place.
The following software is currently required for `facedetect`:
- Python
- Python OpenCV (``python-opencv``)
- OpenCV data files (``opencv-data`` if available, or ``libopencv-dev``)
On Debian/Ubuntu, you can install all the required dependencies with::
sudo apt-get install python python-opencv libopencv-dev
and then install `facedetect` with::
sudo cp facedetect /usr/local/bin
Development status and ideas
Currently `facedetect` is not much beyond a simple wrapper over the Haar
Cascade classifier of OpenCV and the ``frontalface_alt2`` profile, which
provided the best results in terms of accuracy/detection rate for the general,
real life photos at my disposal.
In terms of speed, the LBP classifier was faster. But while the general theory
states that it should also be more accurate, the ``lbp_frontalface`` profile
didn't provide comparable results, suggesting that additional training is
necessary. If some training dataset is found though, creating an LBP profile
would probably be a better solution especially for the processing speed.
``haar_profileface`` had too many false positives in my tests to be usable.
Using it in combination with ``haar_eye`` (and other face parts) though, to
reduce the false positive rates and/or rank the regions, might be a very good
solution instead.
Both LBP and Haar don't play too well with rotated faces. This is particularly
evident with "artistic" portraits shot at an angle. Pre-rotating the image
using the information from a Hough transform might boost the detection rate in
many cases, and should be relatively straightforward to implement.
Authors and Copyright
`facedetect` can be found at
| `facedetect` is distributed under GPL2 (see COPYING) WITHOUT ANY WARRANTY.
| Copyright(c) 2013 by wave++ "Yuri D'Elia" <>.
facedetect's GIT repository is publicly accessible at::
or at `GitHub <>`_.
.. _ImageMagick:
.. _fgallery:
#!/usr/bin/env python
# facedetect: a simple face detector for batch processing
# Copyright(c) 2013 by wave++ "Yuri D'Elia" <>
# Distributed under GPL2 (see COPYING) WITHOUT ANY WARRANTY.
from __future__ import print_function
import argparse
import numpy as np
import cv2
import math
import sys
import os
# CV compatibility stubs
if 'IMREAD_GRAYSCALE' not in dir(cv2):
# Profiles
'HAAR_FRONTALFACE_ALT2': '/usr/share/opencv/haarcascades/haarcascade_frontalface_alt2.xml'
# Support functions
def error(msg):
sys.stderr.write("{}: error: {}\n".format(os.path.basename(sys.argv[0]), msg))
def fatal(msg):
def check_profiles():
for k, v in PROFILES.iteritems():
if not os.path.exists(v):
fatal("cannot load {} from {}".format(k, v))
def rank(im, rects):
scores = []
best = None
for i in range(len(rects)):
rect = rects[i]
b = min(rect[2], rect[3]) / 10.
rx = (rect[0] + b, rect[0] + rect[2] - b)
ry = (rect[1] + b, rect[1] + rect[3] - b)
roi = im[ry[0]:ry[1], rx[0]:rx[1]]
s = (rect[2] + rect[3]) / 2.
scale = 100. / max(rect[2], rect[3])
dsize = (int(rect[2] * scale), int(rect[3] * scale))
roi_n = cv2.resize(roi, dsize, interpolation=cv2.INTER_CUBIC)
roi_l = cv2.Laplacian(roi_n, cv2.CV_8U)
e = np.sum(roi_l) / (dsize[0] * dsize[1])
dx = im.shape[1] / 2 - rect[0] + rect[2] / 2
dy = im.shape[0] / 2 - rect[1] + rect[3] / 2
d = math.sqrt(dx ** 2 + dy ** 2) / (max(im.shape) / 2)
scores.append({'s': s, 'e': e, 'd': d})
sMax = max([x['s'] for x in scores])
eMax = max([x['e'] for x in scores])
for i in range(len(scores)):
s = scores[i]
sN = s['sN'] = s['s'] / sMax
eN = s['eN'] = s['e'] / eMax
f = s['f'] = eN * 0.7 + (1 - s['d']) * 0.1 + sN * 0.2
ranks = range(len(scores))
ranks = sorted(ranks, reverse=True, key=lambda x: scores[x]['f'])
for i in range(len(scores)):
scores[ranks[i]]['RANK'] = i
return scores, ranks[0]
def __main__():
ap = argparse.ArgumentParser(description='A simple face detector for batch processing')
ap.add_argument('--biggest', action="store_true",
help='Extract only the biggest face')
ap.add_argument('--best', action="store_true",
help='Extract only the best matching face')
ap.add_argument('-c', '--center', action="store_true",
help='Print only the center coordinates')
ap.add_argument('-q', '--query', action="store_true",
help='Query only (exit 0: face detected, 2: no detection)')
ap.add_argument('-o', '--output', help='Image output file')
ap.add_argument('-d', '--debug', action="store_true",
help='Add debugging metrics in the image output file')
ap.add_argument('file', help='Input image file')
args = ap.parse_args()
im = cv2.imread(args.file, cv2.IMREAD_GRAYSCALE)
if im is None:
fatal("cannot load input image {}".format(args.file))
im = cv2.equalizeHist(im)
side = math.sqrt(im.size)
minlen = int(side / 20)
maxlen = int(side / 2)
flags =
# optimize queries when possible
if args.biggest or args.query:
flags |=
# frontal faces
cc = cv2.CascadeClassifier(PROFILES['HAAR_FRONTALFACE_ALT2'])
features = cc.detectMultiScale(im, 1.1, 4, flags, (minlen, minlen), (maxlen, maxlen))
if args.query:
return 0 if len(features) else 2
# compute scores
scores = []
best = None
if len(features) and (args.debug or or args.biggest):
scores, best = rank(im, features)
# debug features
if args.output:
im = cv2.imread(args.file)
font =, 0.5, 0.5, 0, 1,
fontHeight ="", font)[0][1] + 5
for i in range(len(features)):
if best is not None and i != best and not args.debug:
rect = features[i]
fg = (0, 255, 255) if i == best else (255, 255, 255)
xy1 = (rect[0], rect[1])
xy2 = (rect[0] + rect[2], rect[1] + rect[3])
cv2.rectangle(im, xy1, xy2, (0, 0, 0), 4)
cv2.rectangle(im, xy1, xy2, fg, 2)
if args.debug:
lines = []
for k, v in scores[i].iteritems():
lines.append("{}: {}".format(k, v))
h = rect[1] + rect[3] + fontHeight
for line in lines:, line, (rect[0], h), font, fg)
h += fontHeight
cv2.imwrite(args.output, im)
# output
if ( or args.biggest) and best is not None:
features = [features[best]]
for rect in features:
x = int(rect[0] + rect[2] / 2)
y = int(rect[1] + rect[3] / 2)
print("{} {}".format(x, y))
for rect in features:
print("{} {} {} {}".format(*rect))
return 0
if __name__ == '__main__':
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