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 Include="Microsoft.EntityFrameworkCore.Sqlite" 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="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>
|
||||
<ProjectReference Include="..\GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj" />
|
||||
<ProjectReference Include="..\GSS2.FrameDecoders\GSS2.FrameDecoders.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -269,10 +269,10 @@ public class CameraService : IDisposable
|
||||
}
|
||||
private void ImageCaptureFrameBuffersMap(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_frameBufferAllocator is null)
|
||||
throw new InvalidOperationException("FrameBufferAllocator not initialized");
|
||||
if (_imageCaptureStream is null)
|
||||
throw new InvalidOperationException("ImageCaptureStream not initialized");
|
||||
if (_frameBufferAllocator is null)
|
||||
throw new InvalidOperationException("FrameBufferAllocator not initialized");
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -304,7 +304,7 @@ public class CameraService : IDisposable
|
||||
if (address == new IntPtr(-1))
|
||||
{
|
||||
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
|
||||
@@ -320,6 +320,95 @@ public class CameraService : IDisposable
|
||||
_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)
|
||||
{
|
||||
if (_mappedBuffers is null)
|
||||
@@ -373,8 +462,10 @@ public class CameraService : IDisposable
|
||||
_frameBufferAllocator?.Dispose();
|
||||
_cameraManager?.Dispose();
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error while CameraService cleanup");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cameraManager = null;
|
||||
@@ -386,5 +477,4 @@ public class CameraService : IDisposable
|
||||
_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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.LightsControl.CSharpClient", "GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj", "{D0130B90-6ECA-49CA-A126-EFEFF481FC74}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.FrameDecoders", "GSS2.FrameDecoders\GSS2.FrameDecoders.csproj", "{05085293-E710-41D6-8F61-701ADBA52FA9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x86.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Reference in New Issue
Block a user