Skip to content

Commit

Permalink
First version.
Browse files Browse the repository at this point in the history
  • Loading branch information
jernejk committed Jan 8, 2019
1 parent 4c4ae79 commit 2918e0c
Show file tree
Hide file tree
Showing 18 changed files with 599 additions and 0 deletions.
28 changes: 28 additions & 0 deletions RealTimeFaceApi.Cmd/ImageStateExtensions.cs
@@ -0,0 +1,28 @@
using OpenCvSharp;
using RealTimeFaceApi.Core.Data;
using System.Linq;

namespace RealTimeFaceApi.Cmd
{
public static class ImageStateExtensions
{
public static FrameState ToImageState(this Rect[] faces)
{
var newFaces = faces.Select(face => new Face
{
Center = new System.Drawing.Point
{
X = (int)(face.X + face.Width * 0.5),
Y = (int)(face.Y + face.Height * 0.5)
},
Size = new System.Drawing.Size
{
Width = (int)(face.Width * 0.5) + 10,
Height = (int)(face.Height * 0.5) + 10
}
});

return new FrameState(newFaces);
}
}
}
128 changes: 128 additions & 0 deletions RealTimeFaceApi.Cmd/Program.cs
@@ -0,0 +1,128 @@
using OpenCvSharp;
using RealTimeFaceApi.Core.Data;
using RealTimeFaceApi.Core.Filters;
using RealTimeFaceApi.Core.Trackers;
using System;

namespace RealTimeFaceApi.Cmd
{
class Program
{
private static readonly Scalar _staticColor = new Scalar(0, 0, 255);

static void Main(string[] args)
{
VideoCapture capture = InitializeCapture();
if (capture == null)
{
Console.ReadKey();
return;
}

CascadeClassifier haarCascade = InitializeFaceClassifier();
int timePerFrame = (int)Math.Round(1000 / capture.Fps);

var filtering = new SimpleFaceFiltering(new IFaceFilter[]
{
new TooSmallFacesFilter(20, 20)
});

var trackingChanges = new SimpleFaceTracking(new IFaceTrackingChanged[]
{
new TrackNumberOfFaces(),
new TrackDistanceOfFaces { Threshold = 2000 }
});

using (Window window = new Window("capture"))
using (Mat image = new Mat())
{
while (true)
{
capture.Read(image);
if (image.Empty())
continue;

// Detect faces
var faces = DetectFaces(haarCascade, image);

// Filter faces
var state = faces.ToImageState();
state = filtering.FilterFaces(state);

// Determine change
var hasChange = trackingChanges.ShouldUpdateRecognition(state);

// Identify faces if changed.
if (hasChange)
{
Console.WriteLine(DateTime.Now.Ticks + ": Changed, getting new identity!");

// TODO: Call Microsoft Cognitive Services.
}

using (var renderedFaces = RenderFaces(state, image, _staticColor))
{
window.ShowImage(renderedFaces);
}

Cv2.WaitKey(timePerFrame);
}
}
}

private static CascadeClassifier InitializeFaceClassifier()
{
return new CascadeClassifier("haarcascade_frontalface_alt.xml");
}

private static VideoCapture InitializeCapture()
{
VideoCapture capture = new VideoCapture();
capture.Open(CaptureDevice.MSMF, 1);

if (!capture.IsOpened())
{
Console.WriteLine("Unable to open capture.");
return null;
}

return capture;
}

private static Rect[] DetectFaces(CascadeClassifier cascadeClassifier, Mat image)
{
return cascadeClassifier
.DetectMultiScale(
image,
1.08,
2,
HaarDetectionType.ScaleImage,
new Size(60, 60));
}

private static Mat RenderFaces(FrameState state, Mat original, Scalar color)
{
Mat result = original.Clone();
Cv2.CvtColor(original, original, ColorConversionCodes.BGR2GRAY);

// Render all detected faces
foreach (var face in state.Faces)
{
var center = new Point
{
X = face.Center.X,
Y = face.Center.Y
};
var axes = new Size
{
Width = (int)(face.Size.Width * 0.5) + 10,
Height = (int)(face.Size.Height * 0.5) + 10
};

Cv2.Ellipse(result, center, axes, 0, 0, 360, new Scalar(0, 0, 255), 4);
}

return result;
}
}
}
17 changes: 17 additions & 0 deletions RealTimeFaceApi.Cmd/RealTimeFaceApi.Cmd.csproj
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\RealTimeFaceApi.Core\RealTimeFaceApi.Core.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenCvSharp4" Version="4.0.0.20181225" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.0.0.20181225" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions RealTimeFaceApi.Core.Tests/RealTimeFaceApi.Core.Tests.csproj
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="Shouldly" Version="3.0.2" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\RealTimeFaceApi.Core\RealTimeFaceApi.Core.csproj" />
</ItemGroup>

</Project>
110 changes: 110 additions & 0 deletions RealTimeFaceApi.Core.Tests/TrackDistanceOfFacesTests.cs
@@ -0,0 +1,110 @@
using RealTimeFaceApi.Core.Data;
using RealTimeFaceApi.Core.Trackers;
using Shouldly;
using System.Collections.Generic;
using System.Drawing;
using Xunit;

namespace RealTimeFaceApi.Core.Tests
{
public class TrackDistanceOfFacesTests
{
[Fact]
public void ShouldNotHaveMovingFace()
{
// Arrange
var tracking = CreateTracker();
var face = new Face { Center = new Point(50, 50) };

// Act
var result = tracking.HasChanged(new FrameState(new List<Face> { face }), new FrameState(new List<Face> { face }));

// Assert
result.ShouldBeFalse();
}

[Fact]
public void ShouldHaveMovingFace()
{
// Arrange
var tracking = CreateTracker(14);
var oldFace = new Face { Center = new Point(50, 50) };
var newFace = new Face { Center = new Point(60, 60) };

// Act
var result = tracking.HasChanged(new FrameState(new List<Face> { oldFace }), new FrameState(new List<Face> { newFace }));

// Assert
result.ShouldBeTrue();
}

[Fact]
public void ShouldNotHaveMovingFaceWhenHigherThreshold()
{
// Arrange
var tracking = CreateTracker(15);
var oldFace = new Face { Center = new Point(50, 50) };
var newFace = new Face { Center = new Point(60, 60) };

// Act
var result = tracking.HasChanged(new FrameState(new List<Face> { oldFace }), new FrameState(new List<Face> { newFace }));

// Assert
result.ShouldBeFalse();
}

[Fact]
public void ShouldNotHaveMovingFaces()
{
// Arrange
var tracking = CreateTracker(15);
var oldFaces = new List<Face>
{
new Face { Center = new Point(50, 50) },
new Face { Center = new Point(200, 200) }
};
var newFaces = new List<Face>
{
new Face { Center = new Point(55, 44) },
new Face { Center = new Point(210, 198) }
};

// Act
var result = tracking.HasChanged(new FrameState(oldFaces), new FrameState(newFaces));

// Assert
result.ShouldBeFalse();
}

[Fact]
public void ShouldHaveMovingFaces()
{
// Arrange
var tracking = CreateTracker(15);
var oldFaces = new List<Face>
{
new Face { Center = new Point(50, 50) },
new Face { Center = new Point(200, 200) }
};
var newFaces = new List<Face>
{
new Face { Center = new Point(55, 44) },
new Face { Center = new Point(210, 185) }
};

// Act
var result = tracking.HasChanged(new FrameState(oldFaces), new FrameState(newFaces));

// Assert
result.ShouldBeTrue();
}

private static TrackDistanceOfFaces CreateTracker(double threshold = 0)
{
return new TrackDistanceOfFaces
{
Threshold = threshold
};
}
}
}
52 changes: 52 additions & 0 deletions RealTimeFaceApi.Core.Tests/TrackNumberOfFacesTests.cs
@@ -0,0 +1,52 @@
using RealTimeFaceApi.Core.Data;
using RealTimeFaceApi.Core.Trackers;
using Shouldly;
using System.Collections.Generic;
using Xunit;

namespace RealTimeFaceApi.Core.Tests
{
public class TrackNumberOfFacesTests
{
[Fact]
public void ShouldChange()
{
// Arrange
var tracking = new TrackNumberOfFaces();

// Act
var result = tracking.HasChanged(new FrameState(new List<Face>()), new FrameState(new List<Face> { new Face() }));

// Assert
result.ShouldBeTrue();
}

[Fact]
public void ShouldNotChange()
{
// Arrange
var tracking = new TrackNumberOfFaces();
var face = new Face();

// Act
var result = tracking.HasChanged(new FrameState(new List<Face> { face }), new FrameState(new List<Face> { face }));

// Assert
result.ShouldBeFalse();
}

[Fact]
public void ShouldChangeEvenWhenNull()
{
// Arrange
var tracking = new TrackNumberOfFaces();
var face = new Face();

// Act
var result = tracking.HasChanged(null, new FrameState(new List<Face> { face }));

// Assert
result.ShouldBeTrue();
}
}
}
10 changes: 10 additions & 0 deletions RealTimeFaceApi.Core/Data/Face.cs
@@ -0,0 +1,10 @@
using System.Drawing;

namespace RealTimeFaceApi.Core.Data
{
public struct Face
{
public Point Center { get; set; }
public Size Size { get; set; }
}
}
15 changes: 15 additions & 0 deletions RealTimeFaceApi.Core/Data/FrameState.cs
@@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Linq;

namespace RealTimeFaceApi.Core.Data
{
public class FrameState
{
public FrameState(IEnumerable<Face> faces)
{
Faces = faces.ToList().AsReadOnly();
}

public IReadOnlyList<Face> Faces { get; }
}
}
9 changes: 9 additions & 0 deletions RealTimeFaceApi.Core/Filters/IFaceFilter.cs
@@ -0,0 +1,9 @@
using RealTimeFaceApi.Core.Data;

namespace RealTimeFaceApi.Core.Filters
{
public interface IFaceFilter
{
FrameState Filter(FrameState previousState, FrameState newState);
}
}

0 comments on commit 2918e0c

Please sign in to comment.