Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
599 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
20
RealTimeFaceApi.Core.Tests/RealTimeFaceApi.Core.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
110
RealTimeFaceApi.Core.Tests/TrackDistanceOfFacesTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System.Drawing; | ||
|
||
namespace RealTimeFaceApi.Core.Data | ||
{ | ||
public struct Face | ||
{ | ||
public Point Center { get; set; } | ||
public Size Size { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using RealTimeFaceApi.Core.Data; | ||
|
||
namespace RealTimeFaceApi.Core.Filters | ||
{ | ||
public interface IFaceFilter | ||
{ | ||
FrameState Filter(FrameState previousState, FrameState newState); | ||
} | ||
} |
Oops, something went wrong.