Initial commit
This commit is contained in:
commit
1f7b9770c9
14 changed files with 393 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/CrestronOpenCvSharp/bin/
|
||||
/CrestronOpenCvSharp/obj
|
||||
13
.idea/.idea.CrestronOpenCvSharp/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.CrestronOpenCvSharp/.idea/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/contentModel.xml
|
||||
/projectSettingsUpdater.xml
|
||||
/modules.xml
|
||||
/.idea.CrestronOpenCvSharp.iml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
4
.idea/.idea.CrestronOpenCvSharp/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.CrestronOpenCvSharp/.idea/encodings.xml
generated
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
.idea/.idea.CrestronOpenCvSharp/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.CrestronOpenCvSharp/.idea/indexLayout.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
16
CrestronOpenCvSharp.sln
Normal file
16
CrestronOpenCvSharp.sln
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrestronOpenCvSharp", "CrestronOpenCvSharp\CrestronOpenCvSharp.csproj", "{82AA53EC-314A-4435-9C78-F90441031C22}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{82AA53EC-314A-4435-9C78-F90441031C22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{82AA53EC-314A-4435-9C78-F90441031C22}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{82AA53EC-314A-4435-9C78-F90441031C22}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{82AA53EC-314A-4435-9C78-F90441031C22}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
BIN
CrestronOpenCvSharp/.DS_Store
vendored
Normal file
BIN
CrestronOpenCvSharp/.DS_Store
vendored
Normal file
Binary file not shown.
116
CrestronOpenCvSharp/Capture/FacialRecognition.cs
Normal file
116
CrestronOpenCvSharp/Capture/FacialRecognition.cs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
using FaceAiSharp;
|
||||
using FaceAiSharp.Extensions;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace CrestronOpenCvSharp.Capture;
|
||||
|
||||
public class FacialRecognition
|
||||
{
|
||||
private readonly IFaceDetectorWithLandmarks _detector;
|
||||
private readonly IFaceEmbeddingsGenerator _recognizer;
|
||||
private readonly string? _baseDirectory;
|
||||
|
||||
private Image<Rgb24>? _image;
|
||||
private float[]? _referenceEmbeddings;
|
||||
|
||||
private readonly Dictionary<string, string> _faceImagesDict;
|
||||
private string? FaceImagePath { get; set; }
|
||||
|
||||
public FacialRecognition(string? baseDirectory)
|
||||
{
|
||||
_baseDirectory = baseDirectory;
|
||||
_detector = FaceAiSharpBundleFactory.CreateFaceDetectorWithLandmarks();
|
||||
_recognizer = FaceAiSharpBundleFactory.CreateFaceEmbeddingsGenerator();
|
||||
|
||||
if (_baseDirectory != null)
|
||||
FaceImagePath = Path.Combine(_baseDirectory, "aligned.png");
|
||||
|
||||
// Let's load the default stuff in this dictionary
|
||||
_faceImagesDict = new Dictionary<string, string>
|
||||
{
|
||||
{ "Yuri Staal", "https://ise2025.local.staal.one/VirtualControl/MA/Rooms/MYFIRSTAI/Html/yuri.jpg" },
|
||||
{ "Toine C. Leerentveld", "https://ise2025.local.staal.one/VirtualControl/MA/Rooms/MYFIRSTAI/Html/toine.jpg" },
|
||||
{ "Oliver Hall", "https://ise2025.local.staal.one/VirtualControl/MA/Rooms/MYFIRSTAI/Html/oliver.jpg" }
|
||||
};
|
||||
}
|
||||
|
||||
public bool CheckForFace(string imageFilePath)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
// Load the photo
|
||||
var photo = File.ReadAllBytes(imageFilePath);
|
||||
// Convert it
|
||||
_image = Image.Load<Rgb24>(photo);
|
||||
// Detect faces in this photo
|
||||
var faces = _detector.DetectFaces(_image);
|
||||
|
||||
if (faces.Count != 0)
|
||||
{
|
||||
_recognizer.AlignFaceUsingLandmarks(_image, faces.First().Landmarks!);
|
||||
_referenceEmbeddings = _recognizer.GenerateEmbedding(_image);
|
||||
_image.Save(FaceImagePath!);
|
||||
Console.WriteLine("Aligned faces!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No faces were found!");
|
||||
}
|
||||
|
||||
// Return true or false
|
||||
return faces.Any();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Exception detecting faces: {e.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> CompareFaces()
|
||||
{
|
||||
foreach (var (name, value) in _faceImagesDict)
|
||||
{
|
||||
var faceImage = await LoadImageAsync(value);
|
||||
var detectedFace = _detector.DetectFaces(faceImage).FirstOrDefault();
|
||||
|
||||
// Generate embedding for the detected face
|
||||
_recognizer.AlignFaceUsingLandmarks(faceImage, detectedFace.Landmarks!);
|
||||
var faceEmbedding = _recognizer.GenerateEmbedding(faceImage);
|
||||
|
||||
// Compare embeddings
|
||||
var similarity = _referenceEmbeddings?.Dot(faceEmbedding);
|
||||
Console.WriteLine($"Similarity with {name}: {similarity}");
|
||||
if (similarity >= 0.42)
|
||||
{
|
||||
//Console.WriteLine("Assessment: Both pictures show the same person.");
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void AddPersonToDatabase(string name)
|
||||
{
|
||||
var shortName = name.Replace(" ", "");
|
||||
// Copy the aligned image to a new image
|
||||
if (_baseDirectory != null)
|
||||
{
|
||||
var newFile = Path.Combine(_baseDirectory, $"{shortName}.jpg");
|
||||
Console.WriteLine($"Saved new image to {newFile}");
|
||||
File.Copy(FaceImagePath!, newFile, overwrite: true);
|
||||
}
|
||||
|
||||
_faceImagesDict.Add(name, $"https://ise2025.local.staal.one/VirtualControl/MA/Rooms/MYFIRSTAI/Html/{shortName}.jpg");
|
||||
Console.WriteLine($"Added new image to dictionary");
|
||||
}
|
||||
|
||||
private async Task<Image<Rgb24>> LoadImageAsync(string path)
|
||||
{
|
||||
using var hc = new HttpClient();
|
||||
var imageBytes = await hc.GetByteArrayAsync(path);
|
||||
return Image.Load<Rgb24>(imageBytes);
|
||||
}
|
||||
}
|
||||
129
CrestronOpenCvSharp/Capture/MjpegCapture.cs
Normal file
129
CrestronOpenCvSharp/Capture/MjpegCapture.cs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
using RandomNameGeneratorLibrary;
|
||||
|
||||
namespace CrestronOpenCvSharp.Capture;
|
||||
|
||||
public class MjpegCapture
|
||||
{
|
||||
private readonly HttpClient _client = new HttpClient();
|
||||
private readonly string? _mjpegUrl;
|
||||
private readonly string? _directory;
|
||||
private readonly FacialRecognition? _facialRecognition;
|
||||
private Timer? _timer;
|
||||
private readonly PersonNameGenerator _personNameGenerator;
|
||||
|
||||
public bool CaptureRunning { get; private set; }
|
||||
|
||||
public MjpegCapture(string? url, string? directory, FacialRecognition? facialRecognition)
|
||||
{
|
||||
_mjpegUrl = url;
|
||||
_directory = Path.Combine(directory!, "captures");
|
||||
_facialRecognition = facialRecognition;
|
||||
|
||||
_personNameGenerator = new PersonNameGenerator();
|
||||
}
|
||||
|
||||
public void StartCapture(int timeout)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_directory))
|
||||
{
|
||||
Console.WriteLine("Directory variable was null or empty.");
|
||||
CaptureRunning = false;
|
||||
return;
|
||||
}
|
||||
CheckIfDirectoryExists(_directory);
|
||||
// Only create if it's null
|
||||
_timer = new Timer(CaptureImage, null, 0, timeout);
|
||||
CaptureRunning = true;
|
||||
}
|
||||
|
||||
public void StopCapture()
|
||||
{
|
||||
if (_timer is not null)
|
||||
{
|
||||
// Stop the timer
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
// Dispose it
|
||||
_timer.Dispose();
|
||||
}
|
||||
CaptureRunning = false;
|
||||
}
|
||||
|
||||
private void CheckIfDirectoryExists(string directory)
|
||||
{
|
||||
Console.WriteLine($"Checking if directory {directory} exists.");
|
||||
if (Directory.Exists(directory))
|
||||
{
|
||||
// Remove the directory and all its contents
|
||||
Directory.Delete(directory, true);
|
||||
Console.WriteLine($"Directory '{directory}' and all its contents have been removed.");
|
||||
Directory.CreateDirectory(directory);
|
||||
Console.WriteLine($"Directory '{directory}' has been created.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create the directory
|
||||
Directory.CreateDirectory(directory);
|
||||
Console.WriteLine($"Directory '{directory}' has been created.");
|
||||
}
|
||||
}
|
||||
|
||||
private async void CaptureImage(object? state)
|
||||
{
|
||||
try
|
||||
{
|
||||
CaptureRunning = true;
|
||||
var fileName = Path.Combine(_directory!, $"frame_{DateTime.Now:yyyyMMdd_HHmmss}.jpg");
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await _client.GetAsync(_mjpegUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await using (var stream = await response.Content.ReadAsStreamAsync())
|
||||
await using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Saved frame to {fileName}");
|
||||
// TODO Send to image preview window on touchpanel
|
||||
|
||||
if (_facialRecognition!.CheckForFace(fileName))
|
||||
{
|
||||
StopCapture();
|
||||
Console.WriteLine("Face detected, stopping capture");
|
||||
// TODO Send image to preview window on touchpanel
|
||||
|
||||
//Path.Combine(_baseDirectory, "aligned.png"));
|
||||
|
||||
Console.WriteLine("Comparing captured face against database");
|
||||
var result = await _facialRecognition.CompareFaces();
|
||||
if (result is not null)
|
||||
{
|
||||
Console.WriteLine($"We have found a match! The person in front of the camera is: {result}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No match was found in our database, let's add this person!");
|
||||
var randomFullName = _personNameGenerator.GenerateRandomFirstAndLastName();
|
||||
_facialRecognition.AddPersonToDatabase(randomFullName);
|
||||
Thread.Sleep(2000);
|
||||
Console.WriteLine("Person added to database, restarting capture");
|
||||
StartCapture(2000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No face detected, carrying on!");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error capturing image: {ex.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Error capturing image: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
48
CrestronOpenCvSharp/ControlSystem.cs
Normal file
48
CrestronOpenCvSharp/ControlSystem.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using CrestronOpenCvSharp.Capture;
|
||||
using Directory = Crestron.SimplSharp.CrestronIO.Directory;
|
||||
using Path = Crestron.SimplSharp.CrestronIO.Path;
|
||||
|
||||
namespace CrestronOpenCvSharp;
|
||||
|
||||
public class ControlSystem : CrestronControlSystem
|
||||
{
|
||||
private FacialRecognition? _facialRecognition;
|
||||
private MjpegCapture? _capture;
|
||||
private UiHandler? _uiHandler;
|
||||
|
||||
// Default snapshot URL of a Bosch camera
|
||||
private const string Url = "http://192.168.1.221/snap.jpg";
|
||||
private readonly string? _directory;
|
||||
|
||||
public ControlSystem() : base()
|
||||
{
|
||||
try
|
||||
{
|
||||
Crestron.SimplSharpPro.CrestronThread.Thread.MaxNumberOfUserThreads = 20;
|
||||
_directory = Path.Combine(Directory.GetApplicationRootDirectory(), $"html");
|
||||
Console.WriteLine($"Home directory = {_directory}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorLog.Error("Error in the constructor: {0}", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeSystem()
|
||||
{
|
||||
try
|
||||
{
|
||||
_facialRecognition = new FacialRecognition(_directory);
|
||||
_capture = new MjpegCapture(Url, _directory, _facialRecognition);
|
||||
_uiHandler = new UiHandler(_capture);
|
||||
|
||||
_capture.StartCapture(5000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorLog.Error("Error in InitializeSystem: {0}", e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
CrestronOpenCvSharp/CrestronOpenCvSharp.csproj
Normal file
37
CrestronOpenCvSharp/CrestronOpenCvSharp.csproj
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.90" />
|
||||
<PackageReference Include="FaceAiSharp" Version="0.5.23" />
|
||||
<PackageReference Include="FaceAiSharp.Bundle" Version="0.5.23" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.20.1" />
|
||||
<PackageReference Include="RandomNameGeneratorLibrary" Version="1.2.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="bin\Debug\net8.0\runtimes\linux-x64\native\libOpenCvSharpExtern.so" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="bin\Debug\net8.0\runtimes\linux-x64\native\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="libOpenCvSharpExtern.so" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="libonnxruntime.so" />
|
||||
<EmbeddedResource Include="libonnxruntime.so">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
13
CrestronOpenCvSharp/UiHandler.cs
Normal file
13
CrestronOpenCvSharp/UiHandler.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using CrestronOpenCvSharp.Capture;
|
||||
|
||||
namespace CrestronOpenCvSharp;
|
||||
|
||||
public class UiHandler
|
||||
{
|
||||
private MjpegCapture _capture;
|
||||
|
||||
public UiHandler(MjpegCapture capture)
|
||||
{
|
||||
_capture = capture;
|
||||
}
|
||||
}
|
||||
BIN
CrestronOpenCvSharp/libonnxruntime.so
Normal file
BIN
CrestronOpenCvSharp/libonnxruntime.so
Normal file
Binary file not shown.
7
global.json
Normal file
7
global.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "8.0.0",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue