Skip to content

Commit

Permalink
Add bob.ip.facedetect (#9)
Browse files Browse the repository at this point in the history
* Allow channels to be at front in generic detector

* Normalise to normalize in test

* Add thin wrapper around bob.ip.facedetect

Training is not supported at the moment and it is not a conda
dependency due to only existing on conda-forge at the moment.

* Expose bob at top level

* Slice off dead greyscale channel for channels_at_back=False

* Handle no results (returns None)

* Skip bob if bob missing

As will be on CI

* Actually ImportError rather than Menpo error

* Remove appveyor in favour of jenkins

* Update docs for bob [ci skip]
  • Loading branch information
Patrick Snape committed Jun 21, 2016
1 parent 72462fd commit 33939ee
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 18 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ integrate nicely with Menpo. At the moment the current libraries are wrapped:
Frontal face detection using the DPM Baseline model provided by
[Mathias et. al.](http://markusmathias.bitbucket.org/2014_eccv_face_detection/).
Training code is also wrapped, but requires explicit negative samples.
- **[bob.ip.facedetect](https://pythonhosted.org/bob.ip.facedetect/) (GPL v3)**
Frontal face detection based on the PhD thesis of Cosmin Atanasoaei of
EPFL based on an ensemble of weak LBP classifiers. **Not currently shipped
using conda and therefore must be installed independently.**

Important
---------
Expand All @@ -62,16 +66,13 @@ to satisfy the dependencies as specified in the meta.yaml BEFORE install.

#### Build Status

| CI Host | OS | Build Status |
|:--------:|:---------------------------------:|:-----------------------------------------------------:|
| Travis | Ubuntu 12.04 (x64) | [![Travis Build Status][travis_shield]][travis] |
| Jenkins | OSX 10.10 (x64) | [![Jenkins Build Status][jenkins_shield]][jenkins] |
| Appveyor | Windows Server 2012 R2 (x86, x64) | [![Appveyor Build Status][appveyor_shield]][appveyor] |
| CI Host | OS | Build Status |
|:--------:|:-----------------------------------------:|:-----------------------------------------------------:|
| Travis | Ubuntu 12.04 (x64) | [![Travis Build Status][travis_shield]][travis] |
| Jenkins | OSX 10.10 (x64) and Windows 10 (x86, x64) | [![Jenkins Build Status][jenkins_shield]][jenkins] |


[travis]: https://travis-ci.org/menpo/menpodetect
[travis_shield]: http://img.shields.io/travis/menpo/menpodetect.svg?style=flat
[appveyor]: https://ci.appveyor.com/project/jabooth/menpodetect
[appveyor_shield]: https://ci.appveyor.com/api/projects/status/github/menpo/menpodetect?svg=true
[jenkins]: http://jenkins.menpo.org/view/menpo/job/menpodetect
[jenkins_shield]: http://jenkins.menpo.org/buildStatus/icon?job=menpodetect
1 change: 1 addition & 0 deletions docs/source/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ core classes available for viewing online.
:maxdepth: 1

menpodetect/detect/index
menpodetect/bob/index
menpodetect/dlib/index
menpodetect/ffld2/index
menpodetect/opencv/index
Expand Down
12 changes: 12 additions & 0 deletions docs/source/api/menpodetect/bob/BobDetector.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.. _menpodetect-bob-BobDetector:

.. currentmodule:: menpodetect.bob

BobDetector
===========
.. autoclass:: BobDetector
:members:
:inherited-members:
:show-inheritance:

.. automethod:: __call__
24 changes: 24 additions & 0 deletions docs/source/api/menpodetect/bob/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.. _api-bob-index:

:mod:`menpodetect.bob`
======================
This module contains a wrapper of the detector provided by the Bob [1]_ [2]_ [3]_
project. The detector implements a boosted cascade of LBP features.

Detection
---------

.. toctree::
:maxdepth: 1

BobDetector
load_bob_frontal_face_detector


References
----------
.. [1] https://pythonhosted.org/bob.ip.facedetect/
.. [2] http://idiap.github.io/bob/
.. [3] Anjos, A., El-Shafey, L., Wallace, R., Günther, M., McCool, C. and Marcel, S.
Bob: a free signal processing and machine learning toolbox for researchers.
ACM-MM
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. _menpodetect-bob-load_bob_frontal_face_detector:

.. currentmodule:: menpodetect.bob

load_bob_frontal_face_detector
==============================
.. autofunction:: load_bob_frontal_face_detector
6 changes: 6 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ These projects are as follows:
OpenCV project. This is only available for Python 2.x due to limitations
of the OpenCV project. OpenCV implements a Viola-Jones detector
and provides models for both frontal and profile faces as well as eyes.
- `bob.ip.facedetect <https://pythonhosted.org/bob.ip.facedetect/>`_ -
Provides the detection capabilities of the Bob detector.
This detector is based on the PhD thesis of Cosmin Atanasoaei of
EPFL and is comprised of an ensemble of weak LBP classifiers.
**Not currently shipped using conda and therefore must be installed
independently.**

We would be very happy to see this collection expand, so pull requests
are very welcome!
Expand Down
1 change: 1 addition & 0 deletions menpodetect/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from menpodetect.bob import *
from menpodetect.dlib import *
from menpodetect.opencv import *
from menpodetect.pico import *
Expand Down
10 changes: 10 additions & 0 deletions menpodetect/bob/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from menpo.base import MenpoMissingDependencyError


try:
from .detect import load_bob_frontal_face_detector, BobDetector
except MenpoMissingDependencyError:
pass

# Remove from scope
del MenpoMissingDependencyError
39 changes: 39 additions & 0 deletions menpodetect/bob/conversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import bob.ip.facedetect
from menpo.shape import bounding_box


def bb_to_pointgraph(bb):
r"""
Convert a `bob.ip.facedetect.BoundingBox` to a
`menpo.shape.PointDirectedGraph`.
This enforces a particular point ordering.
Parameters
----------
bb : `bob.ip.facedetect.BoundingBox`
The bounding box to convert.
Returns
-------
bounding_box : `menpo.shape.PointDirectedGraph`
A menpo PointDirectedGraph giving the bounding box.
"""
return bounding_box(bb.topleft_f, bb.bottomright_f)


def pointgraph_to_bb(pg):
r"""
Convert a `menpo.shape.PointCloud` to a `bob.ip.facedetect.BoundingBox`.
Parameters
----------
pg : `menpo.shape.PointDirectedGraph`
The Menpo PointDirectedGraph to convert into a rect. No check is done
to see if the PointDirectedGraph actually is a rectangle.
Returns
-------
bounding_box : `bob.ip.facedetect.BoundingBox`
A bob BoundingBox.
"""
return bob.ip.facedetect.BoundingBox(pg.bounds()[0], pg.range())
138 changes: 138 additions & 0 deletions menpodetect/bob/detect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from __future__ import division
from functools import partial
from pathlib import Path

from menpo.base import MenpoMissingDependencyError

try:
import bob.ip.facedetect
from bob.ip.facedetect.detector.cascade import Cascade
except ImportError:
raise MenpoMissingDependencyError('bob.ip.facedetect')

from menpodetect.detect import detect
from menpodetect.compatibility import STRING_TYPES
from .conversion import bb_to_pointgraph


class _bob_detect(object):
r"""
A utility callable that allows the caching of a bob detector.
This callable is important for presenting the correct parameters to the
user. It also marshalls the return type of the detector back to
`menpo.shape.PointDirectedGraph`.
Parameters
----------
model : `Path` or `str` or `bob.ip.facedetect.detector.cascade.Cascade`
Either a path to a `bob.ip.facedetect.detector.cascade.Cascade` or a
`bob.ip.facedetect.detector.cascade.Cascade` itself.
Raises
------
ValueError
If a path was provided and it does not exist.
"""
def __init__(self, model):
if isinstance(model, STRING_TYPES) or isinstance(model, Path):
m_path = Path(model)
if not Path(m_path).exists():
raise ValueError('Model {} does not exist.'.format(m_path))
model = Cascade(cascade_file=str(m_path))
self._bob_model = model

def __call__(self, uint8_image, threshold=20, minimum_overlap=0.2):
r"""
Perform a detection using the cached bob detector.
Parameters
----------
uint8_image : `ndarray`
An RGB (3 Channels) or Greyscale (1 Channel) numpy array of uint8
with **channels at the front**.
threshold : `float`, optional
The threshold of the quality of detected faces.
Detections with a quality lower than this value will not be
considered. Higher thresholds will not detect all faces, while lower
thresholds will generate false detections.
``minimum_overlap`` : `float` ``[0, 1]``
Computes the best detection using the given minimum overlap.
Returns
------
bounding_boxes : `list` of `menpo.shape.PointDirectedGraph`
The detected objects.
"""
result = bob.ip.facedetect.detect_all_faces(
uint8_image, cascade=self._bob_model, threshold=threshold,
minimum_overlap=minimum_overlap)
if result is None:
bbs = []
else:
bbs, confidences = result
return [bb_to_pointgraph(b) for b in bbs]


class BobDetector(object):
r"""
A Bob cascade detector.
Wraps a bob cascade detector inside the menpodetect framework and provides
a clean interface to expose the bob arguments.
"""
def __init__(self, model):
self._detector = _bob_detect(model)

def __call__(self, image, greyscale=False, image_diagonal=None,
group_prefix='bob', threshold=20, minimum_overlap=0.2):
r"""
Perform a detection using the cached bob detector.
The detections will also be attached to the image as landmarks.
Parameters
----------
image : `menpo.image.Image`
A Menpo image to detect. The bounding boxes of the detected objects
will be attached to this image.
greyscale : `bool`, optional
Convert the image to greyscale or not.
image_diagonal : `int`, optional
The total size of the diagonal of the image that should be used for
detection. This is useful for scaling images up and down for
detection.
group_prefix : `str`, optional
The prefix string to be appended to each each landmark group that is
stored on the image. Each detection will be stored as group_prefix_#
where # is a count starting from 0.
threshold : `float`, optional
The threshold of the quality of detected faces.
Detections with a quality lower than this value will not be
considered. Higher thresholds will not detect all faces, while lower
thresholds will generate false detections.
``minimum_overlap`` : `float` ``[0, 1]``
Computes the best detection using the given minimum overlap.
Returns
------
bounding_boxes : `list` of `menpo.shape.PointDirectedGraph`
The detected objects.
"""
detect_partial = partial(self._detector, threshold=threshold,
minimum_overlap=minimum_overlap)
return detect(detect_partial, image, greyscale=greyscale,
image_diagonal=image_diagonal, group_prefix=group_prefix,
channels_at_back=False)


def load_bob_frontal_face_detector():
r"""
Load the bob frontal face detector.
Returns
-------
detector : `BobDetector`
The frontal face detector.
"""
return BobDetector(bob.ip.facedetect.default_cascade())
34 changes: 24 additions & 10 deletions menpodetect/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _greyscale(image):
return image


def menpo_image_to_uint8(image):
def menpo_image_to_uint8(image, channels_at_back=True):
r"""
Return the given image as a uint8 array. This is a copy of the image.
Expand All @@ -39,24 +39,33 @@ def menpo_image_to_uint8(image):
image : `menpo.image.Image`
The image to convert. If already uint8, only the channels will be
rolled to the last axis.
channels_at_back : `bool`, optional
If ``True``, the image channels are placed onto the last axis (the back)
as is common in many imaging packages. This is contrary to the Menpo
default where channels are the first axis (at the front).
Returns
-------
uint8_image : `ndarray`
`uint8` Numpy array, channels as the back (last) axis.
`uint8` Numpy array, channels as the back (last) axis if
``channels_at_back == True``.
"""
if image.pixels.dtype == np.uint8:
uint8_im = image.rolled_channels()
if channels_at_back:
uint8_im = image.pixels_with_channels_at_back(out_dtype=np.uint8)
# Handle the dead axis on greyscale images
if uint8_im.ndim == 3 and uint8_im.shape[-1] == 1:
uint8_im = uint8_im[..., 0]
else:
uint8_im = image.as_imageio(out_dtype=np.uint8)
# Handle the dead axis on greyscale images
if uint8_im.ndim == 3 and uint8_im.shape[-1] == 1:
uint8_im = uint8_im[..., 0]
from menpo.image.base import denormalize_pixels_range
uint8_im = denormalize_pixels_range(image.pixels, np.uint8)
# Handle the dead axis on greyscale images
if uint8_im.ndim == 3 and uint8_im.shape[0] == 1:
uint8_im = uint8_im[0]
return uint8_im


def detect(detector_callable, image, greyscale=True,
image_diagonal=None, group_prefix='object'):
image_diagonal=None, group_prefix='object', channels_at_back=True):
r"""
Apply the general detection framework.
Expand Down Expand Up @@ -85,6 +94,10 @@ def detect(detector_callable, image, greyscale=True,
The prefix string to be appended to each each landmark group that is
stored on the image. Each detection will be stored as group_prefix_#
where # is a count starting from 0.
channels_at_back : `bool`, optional
If ``True``, the image channels are placed onto the last axis (the back)
as is common in many imaging packages. This is contrary to the Menpo
default where channels are the first axis (at the front).
Returns
-------
Expand All @@ -100,7 +113,8 @@ def detect(detector_callable, image, greyscale=True,
scale_factor = image_diagonal / image.diagonal()
d_image = d_image.rescale(scale_factor)

pcs = detector_callable(menpo_image_to_uint8(d_image))
pcs = detector_callable(menpo_image_to_uint8(
d_image, channels_at_back=channels_at_back))

if image_diagonal is not None:
s = UniformScale(1 / scale_factor, n_dims=2)
Expand Down
Loading

0 comments on commit 33939ee

Please sign in to comment.