#0015 初步集成图像库
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
using ImageProcessing.Core;
|
||||
|
||||
namespace XplorePlane.Services
|
||||
{
|
||||
public interface IImageProcessingService : IDisposable
|
||||
{
|
||||
IReadOnlyList<string> GetAvailableProcessors();
|
||||
IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName);
|
||||
void RegisterProcessor(string name, ImageProcessorBase processor);
|
||||
|
||||
Task<BitmapSource> ProcessImageAsync(
|
||||
BitmapSource source,
|
||||
string processorName,
|
||||
IDictionary<string, object> parameters,
|
||||
IProgress<double> progress = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<ushort[]> ProcessRawFrameAsync(
|
||||
ushort[] pixelData,
|
||||
int width,
|
||||
int height,
|
||||
string processorName,
|
||||
IDictionary<string, object> parameters,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.Structure;
|
||||
|
||||
namespace XplorePlane.Services
|
||||
{
|
||||
public static class ImageConverter
|
||||
{
|
||||
public static Image<Gray, byte> ToEmguCV(BitmapSource bitmapSource)
|
||||
{
|
||||
if (bitmapSource == null) throw new ArgumentNullException(nameof(bitmapSource));
|
||||
|
||||
var formatted = new FormatConvertedBitmap(bitmapSource, PixelFormats.Gray8, null, 0);
|
||||
int width = formatted.PixelWidth;
|
||||
int height = formatted.PixelHeight;
|
||||
int stride = width;
|
||||
byte[] pixels = new byte[height * stride];
|
||||
formatted.CopyPixels(pixels, stride, 0);
|
||||
|
||||
var image = new Image<Gray, byte>(width, height);
|
||||
image.Bytes = pixels;
|
||||
return image;
|
||||
}
|
||||
|
||||
public static BitmapSource ToBitmapSource(Image<Gray, byte> emguImage)
|
||||
{
|
||||
if (emguImage == null) throw new ArgumentNullException(nameof(emguImage));
|
||||
|
||||
int width = emguImage.Width;
|
||||
int height = emguImage.Height;
|
||||
int stride = width;
|
||||
byte[] pixels = emguImage.Bytes;
|
||||
|
||||
return BitmapSource.Create(width, height, 96, 96, PixelFormats.Gray8, null, pixels, stride);
|
||||
}
|
||||
|
||||
public static Image<Gray, ushort> ToEmguCV16(BitmapSource bitmapSource)
|
||||
{
|
||||
if (bitmapSource == null) throw new ArgumentNullException(nameof(bitmapSource));
|
||||
|
||||
var formatted = new FormatConvertedBitmap(bitmapSource, PixelFormats.Gray16, null, 0);
|
||||
int width = formatted.PixelWidth;
|
||||
int height = formatted.PixelHeight;
|
||||
int stride = width * 2; // 2 bytes per pixel for 16-bit
|
||||
byte[] rawBytes = new byte[height * stride];
|
||||
formatted.CopyPixels(rawBytes, stride, 0);
|
||||
|
||||
ushort[] pixels = new ushort[width * height];
|
||||
Buffer.BlockCopy(rawBytes, 0, pixels, 0, rawBytes.Length);
|
||||
|
||||
var image = new Image<Gray, ushort>(width, height);
|
||||
// Copy pixel data row by row
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
image.Data[y, x, 0] = pixels[y * width + x];
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace XplorePlane.Services
|
||||
{
|
||||
public class ImageProcessingException : Exception
|
||||
{
|
||||
public ImageProcessingException(string message) : base(message) { }
|
||||
public ImageProcessingException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.Structure;
|
||||
using ImageProcessing.Core;
|
||||
using ImageProcessing.Processors;
|
||||
using Serilog;
|
||||
|
||||
namespace XplorePlane.Services
|
||||
{
|
||||
public class ImageProcessingService : IImageProcessingService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ConcurrentDictionary<string, ImageProcessorBase> _processorRegistry;
|
||||
private readonly ConcurrentDictionary<string, ImageProcessorBase16> _processorRegistry16;
|
||||
|
||||
public ImageProcessingService(ILogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_processorRegistry = new ConcurrentDictionary<string, ImageProcessorBase>();
|
||||
_processorRegistry16 = new ConcurrentDictionary<string, ImageProcessorBase16>();
|
||||
RegisterBuiltInProcessors();
|
||||
}
|
||||
|
||||
private void RegisterBuiltInProcessors()
|
||||
{
|
||||
// 8-bit processors
|
||||
_processorRegistry["GaussianBlur"] = new GaussianBlurProcessor();
|
||||
_processorRegistry["Threshold"] = new ThresholdProcessor();
|
||||
_processorRegistry["Division"] = new DivisionProcessor();
|
||||
_processorRegistry["Contrast"] = new ContrastProcessor();
|
||||
_processorRegistry["Gamma"] = new GammaProcessor();
|
||||
_processorRegistry["Morphology"] = new MorphologyProcessor();
|
||||
_processorRegistry["Contour"] = new ContourProcessor();
|
||||
_processorRegistry["ShockFilter"] = new ShockFilterProcessor();
|
||||
_processorRegistry["BandPassFilter"] = new BandPassFilterProcessor();
|
||||
|
||||
// 16-bit processors (separate registry due to different base class)
|
||||
_processorRegistry16["GaussianBlur16"] = new GaussianBlurProcessor16();
|
||||
_processorRegistry16["FlatFieldCorrection16"] = new FlatFieldCorrectionProcessor16();
|
||||
|
||||
_logger.Information("Registered {Count8} 8-bit and {Count16} 16-bit built-in image processors",
|
||||
_processorRegistry.Count, _processorRegistry16.Count);
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> GetAvailableProcessors()
|
||||
{
|
||||
var all = new List<string>(_processorRegistry.Keys);
|
||||
all.AddRange(_processorRegistry16.Keys);
|
||||
return all.AsReadOnly();
|
||||
}
|
||||
|
||||
public void RegisterProcessor(string name, ImageProcessorBase processor)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Processor name cannot be empty", nameof(name));
|
||||
if (processor == null) throw new ArgumentNullException(nameof(processor));
|
||||
_processorRegistry[name] = processor;
|
||||
_logger.Information("Registered processor: {ProcessorName}", name);
|
||||
}
|
||||
|
||||
public IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName)
|
||||
{
|
||||
if (_processorRegistry.TryGetValue(processorName, out var processor))
|
||||
return processor.GetParameters().AsReadOnly();
|
||||
if (_processorRegistry16.TryGetValue(processorName, out var processor16))
|
||||
return processor16.GetParameters().AsReadOnly();
|
||||
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
|
||||
}
|
||||
|
||||
public async Task<BitmapSource> ProcessImageAsync(
|
||||
BitmapSource source,
|
||||
string processorName,
|
||||
IDictionary<string, object> parameters,
|
||||
IProgress<double> progress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!_processorRegistry.TryGetValue(processorName, out var processor))
|
||||
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var emguImage = ImageConverter.ToEmguCV(source);
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
foreach (var kvp in parameters)
|
||||
processor.SetParameter(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
progress?.Report(0.1);
|
||||
var processedEmgu = processor.Process(emguImage);
|
||||
progress?.Report(0.9);
|
||||
|
||||
var result = ImageConverter.ToBitmapSource(processedEmgu);
|
||||
progress?.Report(1.0);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Image processing failed for processor: {ProcessorName}", processorName);
|
||||
throw new ImageProcessingException($"Image processing failed: {ex.Message}", ex);
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ProcessRawFrameAsync(
|
||||
ushort[] pixelData,
|
||||
int width,
|
||||
int height,
|
||||
string processorName,
|
||||
IDictionary<string, object> parameters,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (pixelData == null)
|
||||
throw new ArgumentException("pixelData cannot be null", nameof(pixelData));
|
||||
if (pixelData.Length != width * height)
|
||||
throw new ArgumentException(
|
||||
$"pixelData length {pixelData.Length} does not match width*height {width * height}");
|
||||
|
||||
if (!_processorRegistry16.TryGetValue(processorName, out var processor))
|
||||
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var image = new Image<Gray, ushort>(width, height);
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
image.Data[y, x, 0] = pixelData[y * width + x];
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
foreach (var kvp in parameters)
|
||||
processor.SetParameter(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
var processed = processor.Process(image);
|
||||
|
||||
var result = new ushort[width * height];
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
result[y * width + x] = processed.Data[y, x, 0];
|
||||
|
||||
return result;
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var processor in _processorRegistry.Values)
|
||||
{
|
||||
if (processor is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
_processorRegistry.Clear();
|
||||
|
||||
foreach (var processor in _processorRegistry16.Values)
|
||||
{
|
||||
if (processor is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
_processorRegistry16.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user