合并图像处理库,删除图像lib库

This commit is contained in:
李伟
2026-04-13 13:40:37 +08:00
parent 2a762396d5
commit c7ce4ea6a1
105 changed files with 16341 additions and 133 deletions
@@ -0,0 +1,194 @@
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media.Imaging;
using XP.Camera;
using XP.Common.Logging.Interfaces;
using XplorePlane.Models;
using XplorePlane.Services.AppState;
namespace XplorePlane.Services.Camera
{
/// <summary>
/// 相机管理服务实现,封装 ICameraController 并将状态同步到 IAppStateService。
/// </summary>
public class CameraService : ICameraService
{
private readonly ICameraController _controller;
private readonly IAppStateService _appState;
private readonly ILoggerService _logger;
private volatile bool _liveViewRunning;
private bool _disposed;
private readonly Stopwatch _fpsStopwatch = new();
private int _frameCount;
public bool IsConnected => _controller.IsConnected;
public bool IsGrabbing => _controller.IsGrabbing;
public bool IsLiveView => _liveViewRunning;
public event EventHandler<BitmapSource> FrameArrived;
public event EventHandler ConnectionLost;
public CameraService(
ICameraController controller,
IAppStateService appState,
ILoggerService logger)
{
_controller = controller ?? throw new ArgumentNullException(nameof(controller));
_appState = appState ?? throw new ArgumentNullException(nameof(appState));
_logger = logger?.ForModule<CameraService>() ?? throw new ArgumentNullException(nameof(logger));
_controller.ImageGrabbed += OnImageGrabbed;
_controller.GrabError += OnGrabError;
_controller.ConnectionLost += OnConnectionLost;
}
public CameraInfo Connect()
{
var info = _controller.Open();
_logger.Info("相机已连接: {ModelName} (SN: {SerialNumber})", info.ModelName, info.SerialNumber);
var resolution = $"{_controller.GetWidth()}x{_controller.GetHeight()}";
_appState.UpdateCameraState(new CameraState(
IsConnected: true,
IsStreaming: false,
CurrentFrame: null,
Width: _controller.GetWidth(),
Height: _controller.GetHeight(),
FrameRate: 0));
return info;
}
public void Disconnect()
{
_liveViewRunning = false;
_controller.Close();
_appState.UpdateCameraState(CameraState.Default);
_logger.Info("相机已断开");
}
public void StartGrabbing()
{
_controller.StartGrabbing();
_fpsStopwatch.Restart();
_frameCount = 0;
UpdateStreamingState(true);
}
public void TriggerOnce()
{
_controller.ExecuteSoftwareTrigger();
}
public void StopGrabbing()
{
_liveViewRunning = false;
_controller.StopGrabbing();
_fpsStopwatch.Stop();
UpdateStreamingState(false);
}
public void StartLiveView()
{
if (!_controller.IsGrabbing)
StartGrabbing();
_liveViewRunning = true;
_controller.ExecuteSoftwareTrigger();
_logger.Info("实时预览已启动");
}
public void StopLiveView()
{
_liveViewRunning = false;
_logger.Info("实时预览已停止");
}
// ── 参数读写(直接委托给 controller)──
public double GetExposureTime() => _controller.GetExposureTime();
public void SetExposureTime(double microseconds) => _controller.SetExposureTime(microseconds);
public double GetGain() => _controller.GetGain();
public void SetGain(double value) => _controller.SetGain(value);
public int GetWidth() => _controller.GetWidth();
public void SetWidth(int value) => _controller.SetWidth(value);
public int GetHeight() => _controller.GetHeight();
public void SetHeight(int value) => _controller.SetHeight(value);
public string GetPixelFormat() => _controller.GetPixelFormat();
public void SetPixelFormat(string format) => _controller.SetPixelFormat(format);
// ── 事件处理 ──
private void OnImageGrabbed(object sender, ImageGrabbedEventArgs e)
{
try
{
var bitmap = PixelConverter.ToBitmapSource(e.PixelData, e.Width, e.Height, e.PixelFormat);
// 计算帧率
_frameCount++;
double fps = 0;
if (_fpsStopwatch.ElapsedMilliseconds > 0)
fps = _frameCount / (_fpsStopwatch.ElapsedMilliseconds / 1000.0);
// 更新全局状态
_appState.UpdateCameraState(new CameraState(
IsConnected: true,
IsStreaming: true,
CurrentFrame: bitmap,
Width: e.Width,
Height: e.Height,
FrameRate: fps));
// 通知 UI
var app = Application.Current;
app?.Dispatcher.BeginInvoke(() => FrameArrived?.Invoke(this, bitmap));
// 链式触发下一帧
if (_liveViewRunning)
_controller.ExecuteSoftwareTrigger();
}
catch (Exception ex)
{
_logger.Error(ex, "处理相机图像帧时出错");
}
}
private void OnGrabError(object sender, GrabErrorEventArgs e)
{
_logger.Warn($"相机采集错误: ErrorCode={e.ErrorCode}, {e.ErrorDescription}");
}
private void OnConnectionLost(object sender, EventArgs e)
{
_liveViewRunning = false;
_appState.UpdateCameraState(CameraState.Default);
_logger.Warn("相机连接意外断开");
var app = Application.Current;
app?.Dispatcher.BeginInvoke(() => ConnectionLost?.Invoke(this, EventArgs.Empty));
}
private void UpdateStreamingState(bool isStreaming)
{
var current = _appState.CameraState;
_appState.UpdateCameraState(current with { IsStreaming = isStreaming });
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_liveViewRunning = false;
_controller.ImageGrabbed -= OnImageGrabbed;
_controller.GrabError -= OnGrabError;
_controller.ConnectionLost -= OnConnectionLost;
_controller.Dispose();
_logger.Info("CameraService 已释放");
}
}
}
@@ -0,0 +1,55 @@
using System;
using System.Windows.Media.Imaging;
using XP.Camera;
namespace XplorePlane.Services.Camera
{
/// <summary>
/// 相机管理服务接口,封装 ICameraController 并桥接到 AppStateService。
/// </summary>
public interface ICameraService : IDisposable
{
bool IsConnected { get; }
bool IsGrabbing { get; }
bool IsLiveView { get; }
/// <summary>连接相机,返回设备信息。</summary>
CameraInfo Connect();
/// <summary>断开相机连接。</summary>
void Disconnect();
/// <summary>启动单帧采集(软件触发模式)。</summary>
void StartGrabbing();
/// <summary>触发一次采集。</summary>
void TriggerOnce();
/// <summary>停止采集。</summary>
void StopGrabbing();
/// <summary>启动实时预览(链式触发)。</summary>
void StartLiveView();
/// <summary>停止实时预览。</summary>
void StopLiveView();
// ── 参数读写 ──
double GetExposureTime();
void SetExposureTime(double microseconds);
double GetGain();
void SetGain(double value);
int GetWidth();
void SetWidth(int value);
int GetHeight();
void SetHeight(int value);
string GetPixelFormat();
void SetPixelFormat(string format);
/// <summary>最新一帧图像(已 Freeze,可跨线程)。</summary>
event EventHandler<BitmapSource> FrameArrived;
/// <summary>相机连接断开事件。</summary>
event EventHandler ConnectionLost;
}
}
@@ -22,12 +22,5 @@ namespace XplorePlane.Services
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);
}
}
@@ -58,27 +58,5 @@ namespace XplorePlane.Services
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;
}
}
}
@@ -16,13 +16,11 @@ namespace XplorePlane.Services
{
private readonly ILoggerService _logger;
private readonly ConcurrentDictionary<string, ImageProcessorBase> _processorRegistry;
private readonly ConcurrentDictionary<string, ImageProcessorBase16> _processorRegistry16;
public ImageProcessingService(ILoggerService logger)
{
_logger = logger?.ForModule<ImageProcessingService>() ?? throw new ArgumentNullException(nameof(logger));
_processorRegistry = new ConcurrentDictionary<string, ImageProcessorBase>();
_processorRegistry16 = new ConcurrentDictionary<string, ImageProcessorBase16>();
RegisterBuiltInProcessors();
}
@@ -39,20 +37,10 @@ namespace XplorePlane.Services
_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.Info("Registered {Count8} 8-bit and {Count16} 16-bit built-in image processors",
_processorRegistry.Count, _processorRegistry16.Count);
_logger.Info("Registered {Count} built-in image processors", _processorRegistry.Count);
}
public IReadOnlyList<string> GetAvailableProcessors()
{
var all = new List<string>(_processorRegistry.Keys);
all.AddRange(_processorRegistry16.Keys);
return all.AsReadOnly();
}
public IReadOnlyList<string> GetAvailableProcessors() => new List<string>(_processorRegistry.Keys).AsReadOnly();
public void RegisterProcessor(string name, ImageProcessorBase processor)
{
@@ -66,8 +54,6 @@ namespace XplorePlane.Services
{
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));
}
@@ -82,8 +68,6 @@ namespace XplorePlane.Services
{
if (_processorRegistry.TryGetValue(processorName, out var p))
return string.IsNullOrWhiteSpace(p.Name) ? processorName : p.Name;
if (_processorRegistry16.TryGetValue(processorName, out var p16))
return string.IsNullOrWhiteSpace(p16.Name) ? processorName : p16.Name;
return processorName;
}
@@ -142,49 +126,6 @@ namespace XplorePlane.Services
}, 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)
@@ -194,12 +135,6 @@ namespace XplorePlane.Services
}
_processorRegistry.Clear();
foreach (var processor in _processorRegistry16.Values)
{
if (processor is IDisposable disposable)
disposable.Dispose();
}
_processorRegistry16.Clear();
}
}
}
}