diff --git a/.idea/.idea.CrestronOpenCvSharp/.idea/vcs.xml b/.idea/.idea.CrestronOpenCvSharp/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.CrestronOpenCvSharp/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CrestronOpenCvSharp/.DS_Store b/CrestronOpenCvSharp/.DS_Store index a6f738b..a6fa8d4 100644 Binary files a/CrestronOpenCvSharp/.DS_Store and b/CrestronOpenCvSharp/.DS_Store differ diff --git a/CrestronOpenCvSharp/Capture/FacialRecognition.cs b/CrestronOpenCvSharp/Capture/FacialRecognition.cs index 30eee1e..a7d6e27 100644 --- a/CrestronOpenCvSharp/Capture/FacialRecognition.cs +++ b/CrestronOpenCvSharp/Capture/FacialRecognition.cs @@ -1,7 +1,11 @@ +using Crestron.SimplSharp; using FaceAiSharp; using FaceAiSharp.Extensions; +using PhotoBooth; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Processing; namespace CrestronOpenCvSharp.Capture; @@ -16,15 +20,22 @@ public class FacialRecognition private readonly Dictionary _faceImagesDict; private string? FaceImagePath { get; set; } + + private Contract _contract; - public FacialRecognition(string? baseDirectory) + private const string BaseUrl = "https://ise2025.local.staal.one/VirtualControl/MA/Rooms/MYFIRSTAI/Html/"; + + public FacialRecognition(string? baseDirectory, Contract contract) { _baseDirectory = baseDirectory; + _contract = contract; _detector = FaceAiSharpBundleFactory.CreateFaceDetectorWithLandmarks(); _recognizer = FaceAiSharpBundleFactory.CreateFaceEmbeddingsGenerator(); - if (_baseDirectory != null) + if (_baseDirectory != null) + { FaceImagePath = Path.Combine(_baseDirectory, "aligned.png"); + } // Let's load the default stuff in this dictionary _faceImagesDict = new Dictionary @@ -49,7 +60,19 @@ public class FacialRecognition if (faces.Count != 0) { + foreach (var face in faces) + { + var box = face.Box; + _image.Mutate(ctx => ctx.DrawPolygon(Color.Red, 2, new PointF(box.Left, box.Top), new PointF(box.Right, box.Top), new PointF(box.Right, box.Bottom), new PointF(box.Left, box.Bottom))); + } + + var fileName = GenerateUniqueFilename(); + _image.Save(Path.Combine(_baseDirectory!, fileName!)); + _contract.Main.ImagePreview_Url(string.Format($"{BaseUrl}{fileName!}")); + Console.WriteLine($"Sending URL {string.Format($"{BaseUrl}{fileName!}")}"); + _recognizer.AlignFaceUsingLandmarks(_image, faces.First().Landmarks!); + _referenceEmbeddings = _recognizer.GenerateEmbedding(_image); _image.Save(FaceImagePath!); Console.WriteLine("Aligned faces!"); @@ -113,4 +136,26 @@ public class FacialRecognition var imageBytes = await hc.GetByteArrayAsync(path); return Image.Load(imageBytes); } + + /// + /// Generate a unique filename for the floor plan + /// + /// A dynamic string with a randomized number appended to "image" + private string? GenerateUniqueFilename() + { + try + { + // We are generating random numbers to append to the filename + // We do this to force an update of the image in the browser + var rnd = new Random(); + var num = rnd.Next(); + var fileName = $"image{num}.jpg"; + return fileName; + } + catch (Exception exception) + { + ErrorLog.Exception($"Exception in GenerateUniqueFilename()", exception); + return null; + } + } } \ No newline at end of file diff --git a/CrestronOpenCvSharp/Capture/MjpegCapture.cs b/CrestronOpenCvSharp/Capture/MjpegCapture.cs index 63549cf..fb7df18 100644 --- a/CrestronOpenCvSharp/Capture/MjpegCapture.cs +++ b/CrestronOpenCvSharp/Capture/MjpegCapture.cs @@ -1,4 +1,7 @@ +using Crestron.SimplSharp; +using PhotoBooth; using RandomNameGeneratorLibrary; +using Timeout = System.Threading.Timeout; namespace CrestronOpenCvSharp.Capture; @@ -10,15 +13,19 @@ public class MjpegCapture private readonly FacialRecognition? _facialRecognition; private Timer? _timer; private readonly PersonNameGenerator _personNameGenerator; + private Contract _contract; public bool CaptureRunning { get; private set; } - public MjpegCapture(string? url, string? directory, FacialRecognition? facialRecognition) + private const string BaseUrl = "https://ise2025.local.staal.one/VirtualControl/MA/Rooms/MYFIRSTAI/Html/"; + + public MjpegCapture(string? url, string? directory, FacialRecognition? facialRecognition, Contract contract) { _mjpegUrl = url; _directory = Path.Combine(directory!, "captures"); _facialRecognition = facialRecognition; - + _contract = contract; + _personNameGenerator = new PersonNameGenerator(); } @@ -72,7 +79,8 @@ public class MjpegCapture try { CaptureRunning = true; - var fileName = Path.Combine(_directory!, $"frame_{DateTime.Now:yyyyMMdd_HHmmss}.jpg"); + var uniqueName = $"frame_{DateTime.Now:yyyyMMdd_HHmmss}.jpg"; + var fileName = Path.Combine(_directory!, uniqueName); try { HttpResponseMessage response = await _client.GetAsync(_mjpegUrl, HttpCompletionOption.ResponseHeadersRead); @@ -85,35 +93,36 @@ public class MjpegCapture } Console.WriteLine($"Saved frame to {fileName}"); - // TODO Send to image preview window on touchpanel + _contract.Main.ImagePreview_Url(string.Format($"{BaseUrl}/captures/{uniqueName}")); + Console.WriteLine($"Sending URL {string.Format($"{BaseUrl}/captures/{uniqueName}")}"); if (_facialRecognition!.CheckForFace(fileName)) { StopCapture(); + _contract.Main.ImagePreview_Url(string.Empty); + Thread.Sleep(1000); 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}"); + _contract.Main.txtFeedback_Indirect($"{result} detected!"); } 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); + Console.WriteLine("Person added to database"); + _contract.Main.txtFeedback_Indirect($"{randomFullName} added to database"); } } else { Console.WriteLine("No face detected, carrying on!"); + _contract.Main.txtFeedback_Indirect($"No face detected!"); } } catch (Exception ex) diff --git a/CrestronOpenCvSharp/ControlSystem.cs b/CrestronOpenCvSharp/ControlSystem.cs index 978e030..d9af829 100644 --- a/CrestronOpenCvSharp/ControlSystem.cs +++ b/CrestronOpenCvSharp/ControlSystem.cs @@ -1,6 +1,8 @@ using Crestron.SimplSharp; using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.UI; using CrestronOpenCvSharp.Capture; +using PhotoBooth; using Directory = Crestron.SimplSharp.CrestronIO.Directory; using Path = Crestron.SimplSharp.CrestronIO.Path; @@ -11,6 +13,9 @@ public class ControlSystem : CrestronControlSystem private FacialRecognition? _facialRecognition; private MjpegCapture? _capture; private UiHandler? _uiHandler; + + private XpanelForHtml5 _xpanel; + private Contract _contract; // Default snapshot URL of a Bosch camera private const string Url = "http://192.168.1.221/snap.jpg"; @@ -34,15 +39,31 @@ public class ControlSystem : CrestronControlSystem { try { - _facialRecognition = new FacialRecognition(_directory); - _capture = new MjpegCapture(Url, _directory, _facialRecognition); + _xpanel = new XpanelForHtml5(0x03, this); + _xpanel.Register(); + _contract = new Contract(_xpanel); + _facialRecognition = new FacialRecognition(_directory, _contract); + _capture = new MjpegCapture(Url, _directory, _facialRecognition, _contract); _uiHandler = new UiHandler(_capture); - _capture.StartCapture(5000); + _contract.Main.btnStartCapture_PressEvent += MainOnbtnStartCapture_PressEvent; + _contract.Main.btnStopCapture_PressEvent += MainOnbtnStopCapture_PressEvent; + + //_capture.StartCapture(5000); } catch (Exception e) { ErrorLog.Error("Error in InitializeSystem: {0}", e.Message); } } + + private void MainOnbtnStopCapture_PressEvent(object? sender, UIEventArgs uiEventArgs) + { + _capture?.StopCapture(); + } + + private void MainOnbtnStartCapture_PressEvent(object? sender, UIEventArgs uiEventArgs) + { + _capture?.StartCapture(2000); + } } \ No newline at end of file diff --git a/CrestronOpenCvSharp/CrestronOpenCvSharp.csproj b/CrestronOpenCvSharp/CrestronOpenCvSharp.csproj index 16ea2c5..c401ffe 100644 --- a/CrestronOpenCvSharp/CrestronOpenCvSharp.csproj +++ b/CrestronOpenCvSharp/CrestronOpenCvSharp.csproj @@ -34,4 +34,22 @@ + + + UI\ComponentMediator.g.cs + + + UI\Contract.g.cs + + + UI\IndexedEventArgs.g.cs + + + UI\Main.g.cs + + + UI\UIEventArgs.g.cs + + +