Skip to content

Commit

Permalink
Revamp face detection to reduce false positives.
Browse files Browse the repository at this point in the history
  • Loading branch information
lilith committed Nov 28, 2017
1 parent b7b544c commit 900348b
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 20 deletions.
64 changes: 51 additions & 13 deletions Plugins/Faces/FaceDetection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,20 @@ public class FaceDetection:FeatureDetectionBase<Face>{
public FaceDetection():base(){
MinFaces = 1;
MaxFaces = 10;
MinSizePercent = 3;
ConfidenceLevelThreshold = 2;
MinConfidenceLevel = 1;
MinSizePercent = 4;
ConfidenceLevelThreshold = 5;
MinConfidenceLevel = 3;

ExpandX = 0;
ExpandY = 0;
fileNames = new Dictionary<string, string>(){
{"FaceCascade",@"haarcascade_frontalface_default.xml"}, {"FaceCascadeAlt",@"haarcascade_frontalface_alt.xml"} };
{"FaceCascade",@"haarcascade_frontalface_default.xml"},
{ "FaceCascadeAlt",@"haarcascade_frontalface_alt.xml"},
{ "FaceCascadeAlt2",@"haarcascade_frontalface_alt2.xml"},
{"FaceCascadeAltTree",@"haarcascade_frontalface_alt_tree.xml"},
{"FaceProfile",@"haarcascade_profileface.xml"},
{"Eye",@"haarcascade_eye.xml"},
};
}
/// <summary>
/// The minimum number of faces expected
Expand Down Expand Up @@ -106,8 +112,9 @@ public FaceDetection():base(){
/// The percentage by which to expand each face rectangle vertically after detection. To expand 20% on the top and bottom, set to 0.4
/// </summary>
public double ExpandY { get; set; }



static long totalTime = 0;
static long count = 0;
/// <summary>
/// Detects features on a grayscale image.
/// </summary>
Expand All @@ -117,28 +124,59 @@ public FaceDetection():base(){
protected override List<Face> DetectFeatures(IplImage img, CvMemStorage storage) {

//Determine minimum face size
var minSize = (int)Math.Round((double)MinSizePercent / 100.0 * Math.Min(img.Width, img.Height));
var minSize = Math.Max(12, (int)Math.Round((double)MinSizePercent / 100.0 * Math.Min(img.Width, img.Height)));


//Detect faces (frontal). TODO: side
//Detect faces (frontal).
Stopwatch watch = Stopwatch.StartNew();
// HaarDetectionType.DoCannyPruning | HaarDetectionType.ScaleImage

CvAvgComp[] faces = BorrowCascade("FaceCascade", c => Cv.HaarDetectObjects(img, c, storage, 1.0850, MinConfidenceLevel, HaarDetectionType.Zero, new CvSize(minSize, minSize), new CvSize(0,0)).ToArrayAndDispose());
watch.Stop();
Debug.WriteLine($"Face detection time: {watch.ElapsedMilliseconds}ms");


CvAvgComp[] faces = BorrowCascade("FaceCascadeAlt", c => Cv.HaarDetectObjects(img, c, storage, 1.0850, MinConfidenceLevel, HaarDetectionType.DoCannyPruning, new CvSize(minSize, minSize), new CvSize(0,0)).ToArrayAndDispose());

//Sort by accuracy
Array.Sort<CvAvgComp>(faces, CompareByNeighbors);

//Convert into feature objects list
List<Face> features = new List<Face>(faces.Length);
foreach (CvAvgComp face in faces) features.Add(new Face(PolygonMath.ScaleRect(face.Rect.ToRectangleF(),ExpandX,ExpandY), face.Neighbors));

// Doesn't add much, and would have to be deduplicated.
//CvAvgComp[] profiles = BorrowCascade("FaceProfile", c => Cv.HaarDetectObjects(img, c, storage, 1.2, MinConfidenceLevel + 2, HaarDetectionType.FindBiggestObject | HaarDetectionType.DoRoughSearch | HaarDetectionType.DoCannyPruning, new CvSize(img.Width / 8, img.Height / 8), new CvSize(0, 0)).ToArrayAndDispose());
//foreach (CvAvgComp face in profiles) features.Add(new Face(PolygonMath.ScaleRect(face.Rect.ToRectangleF(), ExpandX, ExpandY), face.Neighbors));


// Test for eyes, if faces > 20 pixels
foreach (var face in features) {
var w = (int) (face.X2 - face.X);
var h = (int) ((face.Y2 - face.Y) * 0.6);
if (w > 20) {
img.SetROI((int) face.X, (int) face.Y, w, h);
storage.Clear();
CvAvgComp[] eyes = BorrowCascade("Eye",
c => Cv.HaarDetectObjects(img, c, storage, 1.0850, 4, HaarDetectionType.FindBiggestObject | HaarDetectionType.DoRoughSearch,
new CvSize(4, 4), new CvSize(img.Width / 2, img.Height / 2))
.ToArrayAndDispose());
if (eyes.Length == 0) {
// Halve the estimated accuracy if there are no eyes detected
face.Accuracy = face.Accuracy / 2;
// We never want to boost accuracy, because the walls have eyes
}
}
}




//Unless we're below MinFaces, filter out the low confidence matches.
while (features.Count > MinFaces && features[features.Count - 1].Accuracy < ConfidenceLevelThreshold) features.RemoveAt(features.Count - 1);


watch.Stop();
totalTime += watch.ElapsedMilliseconds;
count++;
Debug.WriteLine($"Face detection time: {watch.ElapsedMilliseconds}ms (avg {totalTime / count}ms)");


//Never return more than [MaxFaces]
return (features.Count > MaxFaces) ? features.GetRange(0, MaxFaces) : features;
}
Expand Down
8 changes: 4 additions & 4 deletions Plugins/Faces/FacesPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ protected override RequestedAction PostRenderImage(ImageState s) {

g.DrawRectangle(Pens.Green, new Rectangle((int)x1, (int)y1, (int)(x2 - x1), (int)(y2 - y1)));
}

g.DrawString((newPoints.Length / 2).ToString(), new Font(FontFamily.GenericSansSerif, 24.0f, FontStyle.Bold), new SolidBrush(Color.Green),0.0f,24.0f);
}



return RequestedAction.None;
Expand Down Expand Up @@ -187,10 +190,7 @@ public FaceDetection ConfigureDetection(NameValueCollection s) {

//Parse min/max faces
int[] count = s.GetList<int>("f.faces",null,1,2);
if (count == null) {
f.MinFaces = 1;
f.MaxFaces = 8;
}else if (count.Length > 0){
if (count != null && count.Length > 0){
f.MinFaces = f.MaxFaces = count[0];
if (count.Length > 1) f.MaxFaces = count[1];
}
Expand Down
9 changes: 7 additions & 2 deletions Plugins/Faces/FeatureDetectionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected TR BorrowCascade<TR>(string fileNameKey, Func<CvHaarClassifierCascade,
/// Large images will be scaled down to less than scaledBounds X scaledBounds for feature detection.
/// Defaults to 1000
/// </summary>
protected int scaledBounds = 1000;
protected int scaledBounds = 800;

public List<T> DetectFeatures(Bitmap b)
{
Expand All @@ -92,16 +92,20 @@ public List<T> DetectFeatures(Bitmap b)

IplImage orig = null;
IplImage gray = null;
IplImage gray2 = null;
IplImage small = null;
try {

orig = OpenCvSharp.Extensions.BitmapConverter.ToIplImage(b);


gray = new IplImage(orig.Size, BitDepth.U8, 1);
//gray2 = new IplImage(orig.Size, BitDepth.U8, 1);

//Make grayscale version
Cv.CvtColor(orig, gray, ColorConversion.BgrToGray); //TODO, try a different color space

//Cv.EqualizeHist(gray, gray2);

var w = orig.Width;
var h = orig.Height;
Cv.ReleaseImage(orig);
Expand Down Expand Up @@ -140,6 +144,7 @@ public List<T> DetectFeatures(Bitmap b)
}
} finally {
if (gray != null) Cv.ReleaseImage(gray);
if (gray2 != null) Cv.ReleaseImage(gray2);
if (orig != null) Cv.ReleaseImage(orig);
if (small != null) Cv.ReleaseImage(small);
}
Expand Down
2 changes: 1 addition & 1 deletion Plugins/Faces/Native.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<files>
<file url="https://d3ndcb4i803ljg.cloudfront.net/opencv/2.4.10/cascades/haarcascade_frontalface_default.xml" name="haarcascade_frontalface_default.xml" fileBytes="1254733" />

<file url="https://d3ndcb4i803ljg.cloudfront.net/opencv/2.4.10/cascades/haarcascade_frontalface_alt.xml" name="haarcascade_frontalface_alt.xml" fileBytes="919871" />
<file url="https://d3ndcb4i803ljg.cloudfront.net/opencv/2.4.10/cascades/haarcascade_eye.xml" name="haarcascade_eye.xml" fileBytes="506314" />


<file url="https://d3ndcb4i803ljg.cloudfront.net/opencv/2.4.10/x86/opencv_legacy2410.dll" name="opencv_legacy2410.dll" bitness="32" fileBytes="1239040" />
Expand Down

0 comments on commit 900348b

Please sign in to comment.