Commit 2e466166 authored by Manuel Günther's avatar Manuel Günther Committed by Tiago de Freitas Pereira
Browse files

Implemented a better way to handle group multiple detections than a simple...

Implemented a better way to handle group multiple detections than a simple pruning (mostly copied from the 'color' branch)
parent 043a57de
......@@ -6,11 +6,11 @@ import bob.learn.boosting
from . import version
from .version import module as __version__
from ._library import FeatureExtractor, BoundingBox, prune_detections, overlapping_detections
from ._library import FeatureExtractor, BoundingBox, prune_detections, group_detections, overlapping_detections
from .detector import *
from .train import *
from .detect import default_cascade, best_detection, detect_single_face, detect_all_faces
from .detect import default_cascade, average_detections, best_detection, detect_single_face, detect_all_faces
def get_config():
......
......@@ -73,7 +73,80 @@ void bob::ip::facedetect::pruneDetections(const std::vector<boost::shared_ptr<Bo
// done.
}
void bob::ip::facedetect::bestOverlap(const std::vector<boost::shared_ptr<BoundingBox>>& boxes, const blitz::Array<double, 1>& weights, double threshold, std::vector<boost::shared_ptr<BoundingBox>>& overlapping_boxes, blitz::Array<double, 1>& overlapping_weights){
void bob::ip::facedetect::groupDetections(const std::vector<boost::shared_ptr<BoundingBox>>& boxes, const blitz::Array<double, 1>& weights, double overlap_threshold, double weight_threshold, unsigned box_count_threshold, std::vector<std::vector<boost::shared_ptr<BoundingBox>>>& grouped_boxes, std::vector<blitz::Array<double, 1>>& grouped_weights){
if (boxes.empty()){
bob::core::error << "Cannot find any box to compute overlaps" << std::endl;
return;
}
// sort boxes
std::vector<indexer> sorted(boxes.size());
for (int i = boxes.size(); i--;){
sorted[i] = std::make_pair(weights(i), i);
}
std::sort(sorted.begin(), sorted.end(), gt);
// compute all overlapping detections
// **this is O(n^2)!**
std::list<std::list<indexer> > collected;
std::list<indexer> best;
best.push_back(sorted.front());
collected.push_back(best);
std::vector<indexer>::const_iterator sit = sorted.begin();
std::list<std::list<indexer> >::iterator cit;
for (++sit; sit != sorted.end(); ++sit){
std::list<std::list<indexer> >::iterator best_cit = collected.end();
double best_overlap = overlap_threshold, current_overlap;
if (sit->first < weight_threshold)
// we have reached our weight limit; do not consider more bounding boxes
break;
// check if there is a good-enough overlap with one of the already collected bounding boxes
for (cit = collected.begin(); cit != collected.end(); ++cit){
current_overlap = boxes[sit->second]->similarity(*boxes[cit->front().second]);
if (current_overlap > best_overlap){
// get the bounding box with the highest overlap value
best_overlap = current_overlap;
best_cit = cit;
}
}
if (best_cit == collected.end()){
// no such overlap was found, add a new list of bounding boxes
std::list<indexer> novel;
novel.push_back(*sit);
collected.push_back(novel);
} else {
// add the bounding box to the list with the highest overlap
best_cit->push_back(*sit);
}
}
// now, convert lists to resulting grouped vectors of vectors of bounding boxes
grouped_boxes.reserve(collected.size());
grouped_weights.reserve(collected.size());
std::list<indexer>::const_iterator oit;
for (cit = collected.begin(); cit != collected.end(); ++cit){
if (cit->size() >= box_count_threshold){
blitz::Array<double,1> current_weights(cit->size());
std::vector<boost::shared_ptr<BoundingBox>> current_boxes(cit->size());
int o = 0;
for (oit = cit->begin(); oit != cit->end(); ++oit, ++o){
current_weights(o) = oit->first;
current_boxes[o] = boxes[oit->second];
}
grouped_boxes.push_back(current_boxes);
grouped_weights.push_back(current_weights);
}
}
// done.
}
void bob::ip::facedetect::bestOverlap(const std::vector<boost::shared_ptr<BoundingBox>>& boxes, const blitz::Array<double, 1>& weights, double overlap_threshold, std::vector<boost::shared_ptr<BoundingBox>>& overlapping_boxes, blitz::Array<double, 1>& overlapping_weights){
if (boxes.empty()){
bob::core::error << "Cannot find any box to compute overlaps" << std::endl;
return;
......@@ -99,7 +172,7 @@ void bob::ip::facedetect::bestOverlap(const std::vector<boost::shared_ptr<Boundi
std::list<std::list<indexer> >::iterator cit;
for (++sit; sit != sorted.end(); ++sit){
for (cit = collected.begin(); cit != collected.end(); ++cit){
if (boxes[sit->second]->similarity(*boxes[cit->front().second]) > threshold){
if (boxes[sit->second]->similarity(*boxes[cit->front().second]) > overlap_threshold){
cit->push_back(*sit);
break;
}
......@@ -116,7 +189,7 @@ void bob::ip::facedetect::bestOverlap(const std::vector<boost::shared_ptr<Boundi
for (cit = collected.begin(); cit != collected.end(); ++cit){
double current_total = 0.;
for (oit = cit->begin(); oit != cit->end(); ++oit){
current_total += oit->first;
current_total += std::max(oit->first, 0.);
}
if (current_total > best_total){
best_total = current_total;
......
......@@ -64,6 +64,7 @@ class BoundingBox{
double m_area;
};
void groupDetections(const std::vector<boost::shared_ptr<BoundingBox>>& detections, const blitz::Array<double, 1>& predictions, double overlap_threshold, double weight_threshold, unsigned box_count_threshold, std::vector<std::vector<boost::shared_ptr<BoundingBox>>>& grouped_boxes, std::vector<blitz::Array<double, 1>>& grouped_weights);
void pruneDetections(const std::vector<boost::shared_ptr<BoundingBox>>& detections, const blitz::Array<double, 1>& predictions, double threshold, std::vector<boost::shared_ptr<BoundingBox>>& pruned_boxes, blitz::Array<double, 1>& pruned_weights, const int number_of_detections);
void bestOverlap(const std::vector<boost::shared_ptr<BoundingBox>>& detections, const blitz::Array<double, 1>& predictions, double threshold, std::vector<boost::shared_ptr<BoundingBox>>& pruned_boxes, blitz::Array<double, 1>& pruned_weights);
......
......@@ -2,7 +2,7 @@ import pkg_resources
import math
from .detector import Sampler, Cascade
from ._library import BoundingBox, prune_detections, overlapping_detections
from ._library import BoundingBox, prune_detections, group_detections, overlapping_detections
import bob.io.base
import numpy
......@@ -12,8 +12,53 @@ def default_cascade():
return Cascade(bob.io.base.HDF5File(pkg_resources.resource_filename("bob.ip.facedetect", "MCT_cascade.hdf5")))
def best_detection(detections, predictions, minimum_overlap = 0.2):
"""best_detection(detections, predictions, [minimum_overlap]) -> bounding_box, prediction
def average_detections(detections, predictions, relative_prediction_threshold = 0.25):
"""average_detections(detections, predictions, [relative_prediction_threshold]) -> bounding_box, prediction
Computes the weighted average of the given detections, where the weights are computed based on the prediction values.
**Parameters:**
``detections`` : [:py:class:`BoundingBox`]
The overlapping bounding boxes.
``predictions`` : [float]
The predictions for the ``detections``.
``relative_prediction_threshold`` : float between 0 and 1
Limits the bounding boxes to those that have a prediction value higher then ``relative_prediction_threshold * max(predictions)``
**Returns:**
``bounding_box`` : :py:class:`BoundingBox`
The bounding box which has been merged from the detections
``prediction`` : float
The prediction value of the bounding box, which is a weighted sum of the predictions with minimum overlap
"""
# remove the predictions that are too low
prediction_threshold = relative_prediction_threshold * max(predictions)
detections, predictions = zip(*[[d,p] for d,p in zip(detections, predictions) if p >= prediction_threshold])
# turn remaining predictions into weights
s = sum(predictions)
weights = [p/s for p in predictions]
# compute weighted average of bounding boxes
top = sum(w * b.topleft_f[0] for w, b in zip(weights, detections))
left = sum(w * b.topleft_f[1] for w, b in zip(weights, detections))
bottom = sum(w * b.bottomright_f[0] for w, b in zip(weights, detections))
right = sum(w * b.bottomright_f[1] for w, b in zip(weights, detections))
# compute the average prediction value
value = sum(w*p for w,p in zip(weights, predictions))
# return the average bounding box
return BoundingBox((top, left), (bottom-top, right-left)), value
def best_detection(detections, predictions, minimum_overlap = 0.2, relative_prediction_threshold = 0.25):
"""best_detection(detections, predictions, [minimum_overlap], [relative_prediction_threshold]) -> bounding_box, prediction
Computes the best detection for the given detections and according predictions.
......@@ -31,6 +76,9 @@ def best_detection(detections, predictions, minimum_overlap = 0.2):
``minimum_overlap`` : float between 0 and 1
The minimum overlap (in terms of Jaccard :py:meth:`BoundingBox.similarity`) of bounding boxes with the best detection to be considered.
``relative_prediction_threshold`` : float between 0 and 1
Limits the bounding boxes to those that have a prediction value higher then ``relative_prediction_threshold * max(predictions)``
**Returns:**
``bounding_box`` : :py:class:`BoundingBox`
......@@ -49,23 +97,11 @@ def best_detection(detections, predictions, minimum_overlap = 0.2):
# keep only the bounding boxes with the highest overlap
detections, predictions = overlapping_detections(detections, numpy.array(predictions), minimum_overlap)
# compute the mean of the detected bounding boxes
s = sum(predictions)
weights = [p/s for p in predictions]
top = sum(w * b.topleft_f[0] for w, b in zip(weights, detections))
left = sum(w * b.topleft_f[1] for w, b in zip(weights, detections))
bottom = sum(w * b.bottomright_f[0] for w, b in zip(weights, detections))
right = sum(w * b.bottomright_f[1] for w, b in zip(weights, detections))
value = sum(w*p for w,p in zip(weights, predictions))
# as the detection value, we use the *BEST* value of all detections.
# value = predictions[0]
return BoundingBox((top, left), (bottom-top, right-left)), value
return average_detections(detections, predictions, relative_prediction_threshold)
def detect_single_face(image, cascade = None, sampler = None, minimum_overlap=0.2):
"""detect_single_face(image, [cascade], [sampler], [minimum_overlap]) -> bounding_box, quality
def detect_single_face(image, cascade = None, sampler = None, minimum_overlap=0.2, relative_prediction_threshold = 0.25):
"""detect_single_face(image, [cascade], [sampler], [minimum_overlap], [relative_prediction_threshold]) -> bounding_box, quality
Detects a single face in the given image, i.e., the one with the highest prediction value.
......@@ -85,6 +121,9 @@ def detect_single_face(image, cascade = None, sampler = None, minimum_overlap=0.
``minimum_overlap`` : float between 0 and 1
Computes the best detection using the given minimum overlap, see :py:func:`best_detection`
``relative_prediction_threshold`` : float between 0 and 1
Limits the bounding boxes to those that have a prediction value higher then ``relative_prediction_threshold * max(predictions)``
**Returns:**
``bounding_box`` : :py:class:`BoundingBox`
......@@ -102,7 +141,7 @@ def detect_single_face(image, cascade = None, sampler = None, minimum_overlap=0.
if sampler is None:
sampler = Sampler(patch_size = cascade.extractor.patch_size, distance=2, scale_factor=math.pow(2.,-1./16.), lowest_scale=0.125)
if len(image.shape)==3:
if image.ndim == 3:
image = bob.ip.color.rgb_to_gray(image)
detections = []
......@@ -116,15 +155,19 @@ def detect_single_face(image, cascade = None, sampler = None, minimum_overlap=0.
return None
# compute average over the best locations
bb, quality = best_detection(detections, predictions, minimum_overlap)
bb, quality = best_detection(detections, predictions, minimum_overlap, relative_prediction_threshold)
return bb, quality
def detect_all_faces(image, cascade = None, sampler = None, threshold = 0, minimum_overlap = 0.2):
"""detect_all_faces(image, [cascade], [sampler], [threshold], [minimum_overlap]) -> bounding_boxes, qualities
def detect_all_faces(image, cascade = None, sampler = None, threshold = 0, overlaps = 1, minimum_overlap = 0.2, relative_prediction_threshold = 0.25):
"""detect_all_faces(image, [cascade], [sampler], [threshold], [overlaps], [minimum_overlap], [relative_prediction_threshold]) -> bounding_boxes, qualities
Detects a single face in the given image, i.e., the one with the highest prediction value.
Detects all faces in the given image, whose prediction values are higher than the given threshold.
If the given ``minimum_overlap`` is lower than 1, overlapping bounding boxes are grouped, with the ``minimum_overlap`` being the minimum Jaccard similarity between two boxes to be considered to be overlapping.
Afterwards, all groups which have less than ``overlaps`` elements are discarded (this measure is similar to the Viola-Jones face detector).
Finally, :py:func:`average_detections` is used to compute the average bounding box for each of the groups, including averaging the detection value (which will, hence, usually decrease in value).
**Parameters:**
......@@ -144,8 +187,16 @@ def detect_all_faces(image, cascade = None, sampler = None, threshold = 0, minim
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.
``overlaps`` : int
The number of overlapping boxes that must exist for a bounding box to be considered.
Higher values will remove a lot of false-positives, but might increase the chance of a face to be missed.
The default value ``1`` will not limit the boxes.
``minimum_overlap`` : float between 0 and 1
Computes the best detection using the given minimum overlap, see :py:func:`best_detection`
Groups detections based on the given minimum bounding box overlap, see :py:func:`group_detections`.
``relative_prediction_threshold`` : float between 0 and 1
Limits the bounding boxes to those that have a prediction value higher then ``relative_prediction_threshold * max(predictions)``
**Returns:**
......@@ -163,7 +214,7 @@ def detect_all_faces(image, cascade = None, sampler = None, threshold = 0, minim
if sampler is None:
sampler = Sampler(patch_size = cascade.extractor.patch_size, distance=2, scale_factor=math.pow(2.,-1./16.), lowest_scale=0.125)
if len(image.shape)==3:
if image.ndim == 3:
image = bob.ip.color.rgb_to_gray(image)
detections = []
......@@ -174,9 +225,14 @@ def detect_all_faces(image, cascade = None, sampler = None, threshold = 0, minim
predictions.append(prediction)
if not detections:
# No face detected
return None
# prune overlapping detections
bbs, qualities = prune_detections(detections, predictions, minimum_overlap)
# group overlapping detections
if minimum_overlap < 1.:
bbs, qualities = group_detections(detections, predictions, minimum_overlap, threshold, overlaps)
# average them
bbs, qualities = zip(*[average_detections(b, q, relative_prediction_threshold) for b,q in zip(bbs, qualities)])
return bbs, qualities
......@@ -13,10 +13,10 @@
bob::extension::FunctionDoc prune_detections_doc = bob::extension::FunctionDoc(
"prune_detections",
"Prunes the given detected bounding boxes according to their predictions and returns the pruned bounding boxes and their predictions",
"For threshold >= 1., all detections will be returned (i.e., no pruning is performed), but the list will be sorted with descendingly predictions."
"For threshold >= 1., all detections will be returned (i.e., no pruning is performed), but the list will be sorted with descendant predictions."
)
.add_prototype("detections, predictions, threshold, [number_of_detections]", "pruned_detections, pruned_predictions")
.add_parameter("detections", "[:py:class:`BoundingBox`]", "A list of detected bouding boxes")
.add_parameter("detections", "[:py:class:`BoundingBox`]", "A list of detected bounding boxes")
.add_parameter("predictions", "array_like <1D, float>", "The prediction (quality, weight, ...) values for the detections")
.add_parameter("threshold", "float", "The overlap threshold (Jaccard similarity), for which detections should be pruned")
.add_parameter("number_of_detections", "int", "[default: MAX_INT] The number of detections that should be returned")
......@@ -67,6 +67,75 @@ PyObject* PyBobIpFacedetect_PruneDetections(PyObject*, PyObject* args, PyObject*
BOB_CATCH_FUNCTION("in prune_detections", 0)
}
bob::extension::FunctionDoc group_detections_doc = bob::extension::FunctionDoc(
"group_detections",
"Groups the given detected bounding boxes according to their overlap and returns a list of lists of detections, and their according list of predictions",
"Each of the returned lists of bounding boxes contains all boxes that overlap with the first box in the list with at least the given ``overlap_threshold``."
)
.add_prototype("detections, predictions, overlap_threshold, prediction_threshold, box_count_threshold", "grouped_detections, grouped_predictions")
.add_parameter("detections", "[:py:class:`BoundingBox`]", "A list of detected bounding boxes")
.add_parameter("predictions", "array_like <1D, float>", "The prediction (quality, weight, ...) values for the detections")
.add_parameter("overlap_threshold", "float", "The overlap threshold (Jaccard similarity), for which detections should be considered to overlap")
.add_parameter("prediction_threshold", "float", "[Default: ``0``] The prediction threshold, below which the bounding boxes should be disregarded and not added to any group")
.add_parameter("box_count_threshold", "int", "[Default: ``1``] Only bounding boxes with at least the given number of overlapping boxes are considered")
.add_return("grouped_detections", "[[:py:class:`BoundingBox`]]", "The lists of bounding boxes that are grouped by their overlap; each list contains all bounding boxes that overlap with the first entry in the list")
.add_return("grouped_predictions", "[array_like <float, 1D>]", "The according list of grouped predictions (qualities, weights, ...)")
;
PyObject* PyBobIpFacedetect_GroupDetections(PyObject*, PyObject* args, PyObject* kwargs) {
BOB_TRY
char** kwlist = group_detections_doc.kwlist();
PyObject* list;
PyBlitzArrayObject* predictions;
double overlap_threshold, prediction_threshold=0.;
int box_count_threshold = 1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O&d|di", kwlist, &PyList_Type, &list, &PyBlitzArray_Converter, &predictions, &overlap_threshold, &prediction_threshold, &box_count_threshold)) return 0;
auto predictions_ = make_safe(predictions);
auto p = PyBlitzArrayCxx_AsBlitz<double,1>(predictions, "predictions");
if (!p) return 0;
// get bounding box list
std::vector<boost::shared_ptr<bob::ip::facedetect::BoundingBox>> boxes(PyList_GET_SIZE(list));
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(list); ++i){
PyObject* v = PyList_GET_ITEM(list, i);
if (!PyBobIpFacedetectBoundingBox_Check(v)){
PyErr_Format(PyExc_TypeError, "prune_detections : expected a list of BoundingBox objects, but object number %d is of type `%s'", (int)i, Py_TYPE(v)->tp_name);
return 0;
}
boxes[i] = ((PyBobIpFacedetectBoundingBoxObject*)v)->cxx;
}
std::vector<blitz::Array<double,1>> grouped_predictions;
std::vector<std::vector<boost::shared_ptr<bob::ip::facedetect::BoundingBox>>> grouped_boxes;
// perform pruning
bob::ip::facedetect::groupDetections(boxes, *p, overlap_threshold, prediction_threshold, box_count_threshold, grouped_boxes, grouped_predictions);
// re-transform boxes and predictions into python list
PyObject* all_boxes = PyList_New(grouped_boxes.size());
PyObject* all_predictions = PyList_New(grouped_predictions.size());
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(all_boxes); ++i){
// set predictions
PyList_SET_ITEM(all_predictions, i, PyBlitzArrayCxx_AsNumpy(grouped_predictions[i]));
// create list of bounding boxes and add it to the list
PyObject* boxes = PyList_New(grouped_boxes[i].size());
PyList_SET_ITEM(all_boxes, i, boxes);
// now, fill the list
for (Py_ssize_t j = 0; j < PyList_GET_SIZE(boxes); ++j){
PyBobIpFacedetectBoundingBoxObject* bb = reinterpret_cast<PyBobIpFacedetectBoundingBoxObject*>(PyBobIpFacedetectBoundingBox_Type.tp_alloc(&PyBobIpFacedetectBoundingBox_Type, 0));
bb->cxx = grouped_boxes[i][j];
PyList_SET_ITEM(boxes, j, Py_BuildValue("N", bb));
}
}
// return tuple: detections, predictions
return Py_BuildValue("NN", all_boxes, all_predictions);
BOB_CATCH_FUNCTION("group_detections", 0)
}
bob::extension::FunctionDoc overlapping_detections_doc = bob::extension::FunctionDoc(
"overlapping_detections",
......@@ -131,6 +200,12 @@ static PyMethodDef module_methods[] = {
METH_VARARGS|METH_KEYWORDS,
prune_detections_doc.doc()
},
{
group_detections_doc.name(),
(PyCFunction)PyBobIpFacedetect_GroupDetections,
METH_VARARGS|METH_KEYWORDS,
group_detections_doc.doc()
},
{
overlapping_detections_doc.name(),
(PyCFunction)PyBobIpFacedetect_OverlappingDetections,
......
......@@ -23,7 +23,7 @@ def command_line_options(command_line_arguments):
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('test_image', help = "Select the image to detect the face in.")
parser.add_argument('image', help = "Select the image to detect the face in.")
parser.add_argument('--distance', '-s', type=int, default=2, help = "The distance with which the image should be scanned.")
parser.add_argument('--scale-factor', '-S', type=float, default = math.pow(2.,-1./16.), help = "The logarithmic distance between two scales (should be between 0 and 1).")
parser.add_argument('--lowest-scale', '-f', type=float, default = 0.125, help = "Faces which will be lower than the given scale times the image resolution will not be found.")
......@@ -31,6 +31,8 @@ def command_line_options(command_line_arguments):
parser.add_argument('--prediction-threshold', '-t', type = float, help = "If given, all detection above this threshold will be displayed.")
parser.add_argument('--prune-detections', '-p', type=float, default = 1., help = "If given, detections that overlap with the given threshold are pruned")
parser.add_argument('--best-detection-overlap', '-b', type=float, help = "If given, the average of the overlapping detections with this minimum overlap will be considered.")
parser.add_argument('--overlapping-box-count', '-m', type=int, default = 1, help = "The number of overlapping boxes that are required for a detection to be considered")
parser.add_argument('--number-of-detections', '-n', type=int, help = "If specified, show only the top N detections.")
parser.add_argument('--write-detection', '-w', help = "If given, the resulting image will be written to the given file.")
parser.add_argument('--no-display', '-x', action = 'store_true', help = "Disables the display of the detected faces.")
......@@ -44,46 +46,67 @@ def command_line_options(command_line_arguments):
def main(command_line_arguments = None):
args = command_line_options(command_line_arguments)
logger.debug("Loading cascade from file %s", args.cascade_file)
logger.info("Loading cascade from file '%s'", args.cascade_file)
# load classifier and feature extractor
cascade = bob.ip.facedetect.detector.Cascade(bob.io.base.HDF5File(args.cascade_file))
sampler = bob.ip.facedetect.detector.Sampler(patch_size=cascade.extractor.patch_size, distance=args.distance, scale_factor=args.scale_factor, lowest_scale=args.lowest_scale)
# load test file
test_image = bob.io.base.load(args.test_image)
if test_image.ndim == 3:
test_image = bob.ip.color.rgb_to_gray(test_image)
gray_image = bob.io.base.load(args.image)
if gray_image.ndim == 3:
gray_image = bob.ip.color.rgb_to_gray(gray_image)
logger.info("Loaded image '%s' -- starting detection", args.image)
detections = []
predictions = []
# get the detection scores for the image
for prediction, bounding_box in sampler.iterate_cascade(cascade, test_image, args.prediction_threshold):
for prediction, bounding_box in sampler.iterate_cascade(cascade, gray_image, args.prediction_threshold):
detections.append(bounding_box)
predictions.append(prediction)
logger.debug("Found bounding box %s with value %f", str(bounding_box), prediction)
if not predictions:
raise RuntimeError("Could not find a single bounding box in the given image; did you select the prediction threshold '%s' too high?" % args.prediction_threshold)
# prune detections
detections, predictions = bob.ip.facedetect.prune_detections(detections, numpy.array(predictions), args.prune_detections)
logger.info("Number of (pruned) detections: %d", len(detections))
highest_detection = predictions[0]
logger.info("Best detection with value %f at %s: ", highest_detection, str(detections[0]))
if args.best_detection_overlap is not None:
# compute average over the best locations
bb, value = bob.ip.facedetect.best_detection(detections, predictions, args.best_detection_overlap)
detections = [bb]
logger.info("Limiting to a single BoundingBox %s with value %f", str(detections[0]), value)
if args.prediction_threshold is None and args.number_of_detections is None:
# compute average over the best locations
bb, value = bob.ip.facedetect.best_detection(detections, predictions, args.best_detection_overlap)
detections = [bb]
logger.info("Limiting to a single BoundingBox %s with value %f", str(detections[0]), value)
else:
# group detections
detections, predictions = bob.ip.facedetect.group_detections(detections, predictions, args.best_detection_overlap, args.prediction_threshold if args.prediction_threshold is not None else 0, args.overlapping_box_count)
# average them (including averaging the weights)
detections, predictions = zip(*[bob.ip.facedetect.average_detections(b,q) for b,q in zip(detections, predictions)])
# and re-sort them according the the averaged weights (see: http://stackoverflow.com/a/9764364)
# predictions, detections = zip(*sorted(zip(predictions, detections), reverse=True))
# re-group to have the largest detections first
sizes, predictions, detections = zip(*sorted(zip([d.area for d in detections], predictions, detections), reverse=True))
logger.info("Grouping to %d non-overlapping bounding boxes", len(detections))
# compute best location
elif args.prediction_threshold is None:
if args.number_of_detections is not None:
detections = detections[:args.number_of_detections]
logger.info("Limiting to the best %d BoundingBoxes", args.number_of_detections)
elif args.prediction_threshold is None and args.best_detection_overlap is None:
# get the detection with the highest value
detections = detections[:1]
logger.info("Limiting to the best BoundingBox")
logger.info("Limiting to the best BoundingBox %s with value %f", str(detections[0]), predictions[0])
color_image = bob.io.base.load(args.test_image)
highest_detection = predictions[0]
color_image = bob.io.base.load(args.image)
if color_image.ndim == 2:
color_image = bob.ip.color.gray_to_rgb(color_image)
for detection, prediction in zip(detections, predictions):
......
......@@ -31,23 +31,29 @@ This task can be achieved using a single command:
>>> face_image = bob.io.base.load('testimage.jpg') # doctest: +SKIP
>>> bounding_box, quality = bob.ip.facedetect.detect_single_face(face_image)
>>> print (quality, bounding_box.topleft, bounding_box.size)
33.1136586165 (113, 84) (216, 180)
39.209601948 (110, 82) (224, 187)
.. plot:: plot/detect_single_face.py
:include-source: False
As you can see, the bounding box is **not** square as for other face detectors, but has an aspect ratio of :math:`5:6`.
Also, for each detection we provide a ``quality`` value, which specifies, how good the detection is, see :ref:`several` on how to use this ``quality`` value to differentiate between faces and non-faces.
The function :py:func:`detect_single_face` has several optional parameters with proper default values.
The first optional parameter specifies the :py:class:`bob.ip.facedetect.Cascade`, which contains the classifier cascade.
We will see later, how this cascade can be re-trained.
The second parameter is the sampler, which is explained in more detail in the following section :ref:`sampling`.
The ``minimum_overlap`` parameter defines the minimum overlap that patches of multiple detections of the same face might have.
If set to ``1`` (or ``None``), only the bounding box of the best detection is returned, while smaller values will compute the average over more detection, which usually makes the detection more stable.
The related ``relative_prediction_threshold`` parameter defines, which of the bounding boxes to account during averaging, see :py:func:`average_detections`.
.. _sampling:
Sampling
========
The second parameter is a :py:class:`Sampler`, which defines how the image is scanned.
The :py:class:`Sampler` defines how the image is scanned.
The ``scale_factor`` (a value between 0.5 and 1) defines, in which scale granularity the image is scanned.
For higher scale factors like the default :math:`2^{-1/16}` many scales are tested and the detection time is increased.
For lower scale factors like :math:`2^{-1/4}`, fewer scales are tested, which might reduce the stability of the detection.
......@@ -77,6 +83,7 @@ The :py:class:`Sampler` can return an `iterator` of bounding boxes that will be
>>> print (len(patches))
14493
.. _several:
Detecting Several Faces
=======================
......@@ -91,19 +98,23 @@ To extract all faces in a given image, the function :py:func:`detect_all_faces`
.. doctest::
>>> bounding_boxes, qualities = bob.ip.facedetect.detect_all_faces(face_image, threshold=20)
>>> bounding_boxes, qualities = bob.ip.facedetect.detect_all_faces(face_image, threshold=20, overlaps=1)
>>> for i in range(len(bounding_boxes)):
... print ("%3.4f"%qualities[i], bounding_boxes[i].topleft, bounding_boxes[i].size)
74.3045 (88, 66) (264, 220)
39.9663 (110, 82) (224, 187)
24.7024 (264, 192) (72, 60)
24.5685 (379, 126) (126, 105)
22.6990 (379, 128) (117, 97)
The returned list of detected bounding boxes are sorted according to the quality values.
The detections are grouped using the :py:func:`group_detections`.
All groups that have less entries as the given number of ``overlaps`` are discarded, where the default value ``1`` will not discard any group.
Finally, each group is averaged by :py:func:`average_detections`.
Again, ``cascade``, ``sampler`` and ``minimum_overlap`` can be specified to the function.
.. note::
The strategy for merging overlapping detections differ between the two detection functions.
While :py:func:`detect_single_face` uses :py:func:`best_detection` to merge detections, :py:func:`detect_all_faces` simply uses :py:func:`prune_detections` to keep only the detection with the highest quality in the overlapping area.
While :py:func:`detect_single_face` uses :py:func:`best_detection` to merge detections, :py:func:`overlapping_detections` simply uses :py:func:`group_detections` to keep only the detection with the highest quality in the overlapping area.
The difference between :py:func:`overlapping_detections` and :py:func:`group_detections` is that the former uses only the bounding boxes that overlap with **the best detection**, while the latter first groups the detections, so that the **best group average** can be computed.
Iterating over the Sampler
......
2.0.11a0
\ No newline at end of file
2.1.0a0
\ No newline at end of file
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