forked from amkovkov/GranuSightSoftware2
feat: CameraService camera image capture
Add GSS2.FrameDecoders project for image decoding realizations Add IFrameDecoder interface and FrameDecoderHelper class Add CameraService.CaptureImage method
This commit is contained in:
@@ -17,13 +17,15 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||||
|
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
|
||||||
<PackageReference Include="System.Device.Gpio" Version="4.0.1" />
|
<PackageReference Include="System.Device.Gpio" Version="4.0.1" />
|
||||||
<PackageReference Include="Iot.Device.Bindings" Version="4.0.1" />
|
<PackageReference Include="Iot.Device.Bindings" Version="4.0.1" />
|
||||||
<PackageReference Include="LibCameraSharp" Version="0.5.2-20251028" />
|
<PackageReference Include="LibCameraSharp" Version="0.5.2-20251214" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj" />
|
<ProjectReference Include="..\GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj" />
|
||||||
|
<ProjectReference Include="..\GSS2.FrameDecoders\GSS2.FrameDecoders.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -269,10 +269,10 @@ public class CameraService : IDisposable
|
|||||||
}
|
}
|
||||||
private void ImageCaptureFrameBuffersMap(CancellationToken cancellationToken)
|
private void ImageCaptureFrameBuffersMap(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_frameBufferAllocator is null)
|
|
||||||
throw new InvalidOperationException("FrameBufferAllocator not initialized");
|
|
||||||
if (_imageCaptureStream is null)
|
if (_imageCaptureStream is null)
|
||||||
throw new InvalidOperationException("ImageCaptureStream not initialized");
|
throw new InvalidOperationException("ImageCaptureStream not initialized");
|
||||||
|
if (_frameBufferAllocator is null)
|
||||||
|
throw new InvalidOperationException("FrameBufferAllocator not initialized");
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ public class CameraService : IDisposable
|
|||||||
if (address == new IntPtr(-1))
|
if (address == new IntPtr(-1))
|
||||||
{
|
{
|
||||||
int error = Marshal.GetLastWin32Error();
|
int error = Marshal.GetLastWin32Error();
|
||||||
throw new InvalidOperationException($"mmap failed for fd={fd}, error={error}");
|
throw new InvalidOperationException($"Error while buffers mapping, mmap failed for fd={fd}, error={error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
_mappedBuffers.Add(new MappedBuffer
|
_mappedBuffers.Add(new MappedBuffer
|
||||||
@@ -320,6 +320,95 @@ public class CameraService : IDisposable
|
|||||||
_state |= State.ImageCaptureFrameBuffersMapped;
|
_state |= State.ImageCaptureFrameBuffersMapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<OpenCvSharp.Mat?> CaptureImage(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_camera is null)
|
||||||
|
throw new InvalidOperationException("Camera not initialized");
|
||||||
|
if (_imageCaptureStream is null)
|
||||||
|
throw new InvalidOperationException("ImageCaptureStream not initialized");
|
||||||
|
if (_frameBufferAllocator is null)
|
||||||
|
throw new InvalidOperationException("FrameBufferAllocator not initialized");
|
||||||
|
if (_mappedBuffers is null)
|
||||||
|
throw new InvalidOperationException("Buffers not mapped");
|
||||||
|
|
||||||
|
var effectiveCancellationToken = CancellationTokenSource
|
||||||
|
.CreateLinkedTokenSource(_disposeCts.Token, cancellationToken)
|
||||||
|
.Token;
|
||||||
|
|
||||||
|
OpenCvSharp.Mat? image = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
effectiveCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var buffers = _frameBufferAllocator.Buffers(_imageCaptureStream);
|
||||||
|
var buffer = buffers.Where(b => b.Metadata.Status == FrameMetadata.StatusEnum.FrameStartup || b.Metadata.Status == FrameMetadata.StatusEnum.FrameSuccess).First();
|
||||||
|
var request = _camera.CreateRequest(checked((ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()));
|
||||||
|
var result = request.AddBuffer(_imageCaptureStream, buffer);
|
||||||
|
|
||||||
|
if (result < 0)
|
||||||
|
throw new Exception("Error while request create");
|
||||||
|
|
||||||
|
effectiveCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var cts = new TaskCompletionSource();
|
||||||
|
void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Request.Handle == request.Handle)
|
||||||
|
cts.SetResult();
|
||||||
|
};
|
||||||
|
|
||||||
|
_camera.RequestCompleted += RequestCompleted;
|
||||||
|
await cts.Task.WaitAsync(effectiveCancellationToken);
|
||||||
|
_camera.RequestCompleted -= RequestCompleted;
|
||||||
|
|
||||||
|
var planesData = new List<Memory<byte>>();
|
||||||
|
foreach (var plane in buffer.Planes)
|
||||||
|
{
|
||||||
|
effectiveCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var pointer = GetPlanePointer(plane);
|
||||||
|
var data = new byte[plane.Length];
|
||||||
|
Marshal.Copy(pointer, data, 0, checked((int)plane.Length));
|
||||||
|
planesData.Add(new Memory<byte>(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
effectiveCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var config = _imageCaptureStream.Configuration();
|
||||||
|
var size = config.Size;
|
||||||
|
var fourcc = config.PixelFormat.Fourcc;
|
||||||
|
var modifier = config.PixelFormat.Modifier;
|
||||||
|
var primariesEnum = config.ColorSpace!.Primaries;
|
||||||
|
var transferFunctionEnum = config.ColorSpace.TransferFunction;
|
||||||
|
var ycbcrEncodingEnum = config.ColorSpace.YcbcrEncoding;
|
||||||
|
var rangeEnum = config.ColorSpace.Range;
|
||||||
|
var stride = config.Stride;
|
||||||
|
|
||||||
|
effectiveCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var decoder = FrameDecoderHelper.FindDecoder(fourcc, modifier, primariesEnum, transferFunctionEnum, ycbcrEncodingEnum, rangeEnum);
|
||||||
|
if (decoder is null)
|
||||||
|
throw new Exception($"Cannot find decoder for fourcc={fourcc} modifier={modifier} primariesEnum={primariesEnum} transferFunctionEnum={transferFunctionEnum} ycbcrEncodingEnum={ycbcrEncodingEnum} rangeEnum={rangeEnum}");
|
||||||
|
|
||||||
|
effectiveCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
image = decoder.Decode(checked((int)size.Width), checked((int)size.Height), checked((int)stride), planesData);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error while image decode");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error while image capture");
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
private IntPtr GetPlanePointer(FrameBuffer.Plane plane)
|
private IntPtr GetPlanePointer(FrameBuffer.Plane plane)
|
||||||
{
|
{
|
||||||
if (_mappedBuffers is null)
|
if (_mappedBuffers is null)
|
||||||
@@ -373,8 +462,10 @@ public class CameraService : IDisposable
|
|||||||
_frameBufferAllocator?.Dispose();
|
_frameBufferAllocator?.Dispose();
|
||||||
_cameraManager?.Dispose();
|
_cameraManager?.Dispose();
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception e)
|
||||||
{ }
|
{
|
||||||
|
_logger.LogError(e, "Error while CameraService cleanup");
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_cameraManager = null;
|
_cameraManager = null;
|
||||||
@@ -386,5 +477,4 @@ public class CameraService : IDisposable
|
|||||||
_state = State.NotInitialized;
|
_state = State.NotInitialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using GSS2.FrameDecoders;
|
||||||
|
|
||||||
|
using LibCameraSharp;
|
||||||
|
|
||||||
|
public static class FrameDecoderHelper
|
||||||
|
{
|
||||||
|
private static readonly IReadOnlyList<IFrameDecoder> _decoders;
|
||||||
|
|
||||||
|
static FrameDecoderHelper()
|
||||||
|
{
|
||||||
|
_decoders = LoadDecoders();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<IFrameDecoder> LoadDecoders()
|
||||||
|
{
|
||||||
|
var decoderInterface = typeof(IFrameDecoder);
|
||||||
|
var assembly = decoderInterface.Assembly;
|
||||||
|
|
||||||
|
var decoders = assembly
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t =>
|
||||||
|
!t.IsAbstract &&
|
||||||
|
!t.IsInterface &&
|
||||||
|
decoderInterface.IsAssignableFrom(t))
|
||||||
|
.Select(CreateDecoderInstance)
|
||||||
|
.Where(d => d is not null)
|
||||||
|
.Cast<IFrameDecoder>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return decoders;
|
||||||
|
}
|
||||||
|
private static IFrameDecoder? CreateDecoderInstance(Type type)
|
||||||
|
{
|
||||||
|
if (type.GetConstructor(Type.EmptyTypes) is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (IFrameDecoder?)Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IFrameDecoder? FindDecoder(
|
||||||
|
uint fourcc,
|
||||||
|
ulong modifier,
|
||||||
|
ColorSpace.PrimariesEnum primariesEnum,
|
||||||
|
ColorSpace.TransferFunctionEnum transferFunctionEnum,
|
||||||
|
ColorSpace.YcbcrEncodingEnum ycbcrEncodingEnum,
|
||||||
|
ColorSpace.RangeEnum rangeEnum)
|
||||||
|
{
|
||||||
|
return _decoders.FirstOrDefault(d =>
|
||||||
|
d.Fourcc == fourcc &&
|
||||||
|
d.Modifier == modifier &&
|
||||||
|
d.PrimariesEnum == primariesEnum &&
|
||||||
|
d.TransferFunctionEnum == transferFunctionEnum &&
|
||||||
|
d.YcbcrEncodingEnum == ycbcrEncodingEnum &&
|
||||||
|
d.RangeEnum == rangeEnum);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FFmpeg.AutoGen" Version="7.1.1" />
|
||||||
|
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
|
||||||
|
<PackageReference Include="LibCameraSharp" Version="0.5.2-20251028" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using LibCameraSharp;
|
||||||
|
|
||||||
|
using OpenCvSharp;
|
||||||
|
|
||||||
|
namespace GSS2.FrameDecoders;
|
||||||
|
|
||||||
|
public interface IFrameDecoder
|
||||||
|
{
|
||||||
|
public uint Fourcc { get; }
|
||||||
|
public ulong Modifier { get; }
|
||||||
|
public ColorSpace.PrimariesEnum PrimariesEnum { get; }
|
||||||
|
public ColorSpace.TransferFunctionEnum TransferFunctionEnum { get; }
|
||||||
|
public ColorSpace.YcbcrEncodingEnum YcbcrEncodingEnum { get; }
|
||||||
|
public ColorSpace.RangeEnum RangeEnum { get; }
|
||||||
|
|
||||||
|
public Mat Decode(int width, int height, int stride, List<Memory<byte>> planes);
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CSharpClient", "CSharpClien
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.LightsControl.CSharpClient", "GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj", "{D0130B90-6ECA-49CA-A126-EFEFF481FC74}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.LightsControl.CSharpClient", "GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj", "{D0130B90-6ECA-49CA-A126-EFEFF481FC74}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.FrameDecoders", "GSS2.FrameDecoders\GSS2.FrameDecoders.csproj", "{05085293-E710-41D6-8F61-701ADBA52FA9}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -59,6 +61,18 @@ Global
|
|||||||
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x64.Build.0 = Release|Any CPU
|
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x86.ActiveCfg = Release|Any CPU
|
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x86.Build.0 = Release|Any CPU
|
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user