合并图像处理库,删除图像lib库
This commit is contained in:
@@ -0,0 +1,517 @@
|
||||
using Basler.Pylon;
|
||||
using Serilog;
|
||||
|
||||
using CameraImageGrabbedEventArgs = XP.Camera.ImageGrabbedEventArgs;
|
||||
using CameraGrabErrorEventArgs = XP.Camera.GrabErrorEventArgs;
|
||||
|
||||
namespace XP.Camera;
|
||||
|
||||
/// <summary>
|
||||
/// Basler 相机控制器,封装 Basler pylon .NET SDK 实现 <see cref="ICameraController"/>。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>所有公共方法通过内部 <c>_syncLock</c> 对象进行 lock 同步,保证线程安全。</para>
|
||||
/// <para>事件回调(ImageGrabbed、GrabError)在 StreamGrabber 回调线程上触发,不持有 _syncLock,避免死锁。</para>
|
||||
/// <para>ConnectionLost 事件在 pylon SDK 事件线程上触发。</para>
|
||||
/// </remarks>
|
||||
public class BaslerCameraController : ICameraController
|
||||
{
|
||||
private static readonly ILogger _logger = Log.ForContext<BaslerCameraController>();
|
||||
private readonly object _syncLock = new();
|
||||
|
||||
private Basler.Pylon.Camera? _camera;
|
||||
private CameraInfo? _cachedCameraInfo;
|
||||
private bool _isConnected;
|
||||
private bool _isGrabbing;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="BaslerCameraController"/> 实例。
|
||||
/// </summary>
|
||||
public BaslerCameraController()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsConnected
|
||||
{
|
||||
get { lock (_syncLock) { return _isConnected; } }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsGrabbing
|
||||
{
|
||||
get { lock (_syncLock) { return _isGrabbing; } }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<CameraImageGrabbedEventArgs>? ImageGrabbed;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<CameraGrabErrorEventArgs>? GrabError;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? ConnectionLost;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CameraInfo Open()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_isConnected && _cachedCameraInfo != null)
|
||||
{
|
||||
_logger.Information("Camera already connected, returning cached info.");
|
||||
return _cachedCameraInfo;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information("Opening camera connection...");
|
||||
_camera = new Basler.Pylon.Camera(CameraSelectionStrategy.FirstFound);
|
||||
_camera.CameraOpened += (sender, e) => Configuration.SoftwareTrigger(sender!, e);
|
||||
_camera.ConnectionLost += OnConnectionLost;
|
||||
_camera.Open();
|
||||
|
||||
_cachedCameraInfo = new CameraInfo(
|
||||
ModelName: _camera.CameraInfo![CameraInfoKey.ModelName] ?? "",
|
||||
SerialNumber: _camera.CameraInfo[CameraInfoKey.SerialNumber] ?? "",
|
||||
VendorName: _camera.CameraInfo[CameraInfoKey.VendorName] ?? "",
|
||||
DeviceType: _camera.CameraInfo[CameraInfoKey.DeviceType] ?? ""
|
||||
);
|
||||
|
||||
_isConnected = true;
|
||||
_logger.Information("Camera connected: {ModelName} (SN: {SerialNumber})",
|
||||
_cachedCameraInfo.ModelName, _cachedCameraInfo.SerialNumber);
|
||||
|
||||
return _cachedCameraInfo;
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
// Clean up partially created camera on failure
|
||||
_camera?.Dispose();
|
||||
_camera = null;
|
||||
|
||||
if (ex.Message.Contains("No device", StringComparison.OrdinalIgnoreCase)
|
||||
|| ex.Message.Contains("not found", StringComparison.OrdinalIgnoreCase)
|
||||
|| ex is InvalidOperationException)
|
||||
{
|
||||
_logger.Error(ex, "No camera device found.");
|
||||
throw new DeviceNotFoundException("No Basler camera device found.", ex);
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Failed to open camera.");
|
||||
throw new CameraException("Failed to open camera device.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Close()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (!_isConnected)
|
||||
{
|
||||
_logger.Information("Camera not connected, Close() ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_isGrabbing)
|
||||
{
|
||||
StopGrabbingInternal();
|
||||
}
|
||||
|
||||
_logger.Information("Closing camera connection...");
|
||||
_camera?.Close();
|
||||
_camera?.Dispose();
|
||||
_camera = null;
|
||||
_isConnected = false;
|
||||
_cachedCameraInfo = null;
|
||||
_logger.Information("Camera connection closed.");
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Error while closing camera.");
|
||||
// Still clean up state even if close fails
|
||||
_camera = null;
|
||||
_isConnected = false;
|
||||
_isGrabbing = false;
|
||||
_cachedCameraInfo = null;
|
||||
throw new CameraException("Failed to close camera device.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StartGrabbing()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
|
||||
if (_isGrabbing)
|
||||
{
|
||||
_logger.Information("Already grabbing, StartGrabbing() ignored.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information("Starting grabbing with software trigger...");
|
||||
|
||||
// Register ImageReady event for grab results (task 6.2 will implement the handler)
|
||||
_camera!.StreamGrabber!.ImageGrabbed += OnImageGrabbed;
|
||||
|
||||
// Start grabbing using the grab loop thread provided by StreamGrabber
|
||||
_camera.StreamGrabber!.Start(GrabStrategy.OneByOne, GrabLoop.ProvidedByStreamGrabber);
|
||||
|
||||
_isGrabbing = true;
|
||||
_logger.Information("Grabbing started.");
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to start grabbing.");
|
||||
throw new CameraException("Failed to start grabbing.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ExecuteSoftwareTrigger()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (!_isGrabbing)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot execute software trigger: camera is not grabbing.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Wait until the camera is ready to accept the next frame trigger
|
||||
if (!_camera!.WaitForFrameTriggerReady(1000, TimeoutHandling.Return))
|
||||
{
|
||||
throw new TimeoutException("Camera was not ready for frame trigger within 1000 ms.");
|
||||
}
|
||||
|
||||
_camera.ExecuteSoftwareTrigger();
|
||||
_logger.Debug("Software trigger executed.");
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
throw; // Re-throw our own TimeoutException
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException and not InvalidOperationException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to execute software trigger.");
|
||||
throw new CameraException("Failed to execute software trigger.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopGrabbing()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (!_isGrabbing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopGrabbingInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetExposureTime()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
return _camera!.Parameters[PLCamera.ExposureTime].GetValue();
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to get exposure time.");
|
||||
throw new CameraException("Failed to get exposure time.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetExposureTime(double microseconds)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
_camera!.Parameters[PLCamera.ExposureTime].SetValue(microseconds);
|
||||
_logger.Information("Exposure time set to {Microseconds} µs.", microseconds);
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to set exposure time to {Microseconds} µs.", microseconds);
|
||||
throw new CameraException("Failed to set exposure time.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetGain()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
return _camera!.Parameters[PLCamera.Gain].GetValue();
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to get gain.");
|
||||
throw new CameraException("Failed to get gain.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetGain(double value)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
_camera!.Parameters[PLCamera.Gain].SetValue(value);
|
||||
_logger.Information("Gain set to {Value}.", value);
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to set gain to {Value}.", value);
|
||||
throw new CameraException("Failed to set gain.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int GetWidth()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
return (int)_camera!.Parameters[PLCamera.Width].GetValue();
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to get width.");
|
||||
throw new CameraException("Failed to get width.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetWidth(int value)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
_camera!.Parameters[PLCamera.Width].SetValue(value, IntegerValueCorrection.Nearest);
|
||||
_logger.Information("Width set to {Value}.", value);
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to set width to {Value}.", value);
|
||||
throw new CameraException("Failed to set width.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int GetHeight()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
return (int)_camera!.Parameters[PLCamera.Height].GetValue();
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to get height.");
|
||||
throw new CameraException("Failed to get height.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetHeight(int value)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
_camera!.Parameters[PLCamera.Height].SetValue(value, IntegerValueCorrection.Nearest);
|
||||
_logger.Information("Height set to {Value}.", value);
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to set height to {Value}.", value);
|
||||
throw new CameraException("Failed to set height.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetPixelFormat()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
return _camera!.Parameters[PLCamera.PixelFormat].GetValue();
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to get pixel format.");
|
||||
throw new CameraException("Failed to get pixel format.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetPixelFormat(string format)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
EnsureConnected();
|
||||
try
|
||||
{
|
||||
_camera!.Parameters[PLCamera.PixelFormat].SetValue(format);
|
||||
_logger.Information("Pixel format set to {Format}.", format);
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_logger.Error(ex, "Failed to set pixel format to {Format}.", format);
|
||||
throw new CameraException("Failed to set pixel format.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Close();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// StreamGrabber.ImageGrabbed 事件处理。在 StreamGrabber 回调线程上调用,不持有 _syncLock。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>当图像采集成功时,提取像素数据、宽高和像素格式,触发 <see cref="ImageGrabbed"/> 事件。</para>
|
||||
/// <para>当图像采集失败时,提取错误码和错误描述,触发 <see cref="GrabError"/> 事件。</para>
|
||||
/// <para>此方法在 StreamGrabber 回调线程上执行,不持有 _syncLock,以避免死锁。
|
||||
/// 调用方如需在 WPF UI 线程上处理事件,应自行通过 Dispatcher 调度。</para>
|
||||
/// </remarks>
|
||||
private void OnImageGrabbed(object? sender, Basler.Pylon.ImageGrabbedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
IGrabResult grabResult = e.GrabResult;
|
||||
|
||||
if (grabResult.GrabSucceeded)
|
||||
{
|
||||
byte[] pixelData = grabResult.PixelData as byte[] ?? Array.Empty<byte>();
|
||||
int width = grabResult.Width;
|
||||
int height = grabResult.Height;
|
||||
string pixelFormat = grabResult.PixelTypeValue.ToString();
|
||||
|
||||
var args = new CameraImageGrabbedEventArgs(pixelData, width, height, pixelFormat);
|
||||
ImageGrabbed?.Invoke(this, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
int errorCode = (int)grabResult.ErrorCode;
|
||||
string errorDescription = grabResult.ErrorDescription ?? "Unknown grab error.";
|
||||
|
||||
_logger.Error("Image grab failed. ErrorCode: {ErrorCode}, Description: {ErrorDescription}",
|
||||
errorCode, errorDescription);
|
||||
|
||||
var args = new CameraGrabErrorEventArgs(errorCode, errorDescription);
|
||||
GrabError?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Exception in OnImageGrabbed handler.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ConnectionLost 事件处理。在 pylon SDK 事件线程上调用。
|
||||
/// </summary>
|
||||
private void OnConnectionLost(object? sender, EventArgs e)
|
||||
{
|
||||
_logger.Warning("Camera connection lost.");
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
_isGrabbing = false;
|
||||
_isConnected = false;
|
||||
_cachedCameraInfo = null;
|
||||
}
|
||||
|
||||
// Raise event outside lock to avoid deadlock
|
||||
ConnectionLost?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal stop grabbing without lock (caller must hold _syncLock).
|
||||
/// </summary>
|
||||
private void StopGrabbingInternal()
|
||||
{
|
||||
if (!_isGrabbing)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_camera?.StreamGrabber?.Stop();
|
||||
if (_camera != null)
|
||||
_camera.StreamGrabber!.ImageGrabbed -= OnImageGrabbed;
|
||||
_isGrabbing = false;
|
||||
_logger.Information("Grabbing stopped.");
|
||||
}
|
||||
catch (Exception ex) when (ex is not CameraException)
|
||||
{
|
||||
_isGrabbing = false;
|
||||
_logger.Error(ex, "Error while stopping grabbing.");
|
||||
throw new CameraException("Failed to stop grabbing.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws <see cref="InvalidOperationException"/> if the camera is not connected.
|
||||
/// Must be called within a lock on <see cref="_syncLock"/>.
|
||||
/// </summary>
|
||||
private void EnsureConnected()
|
||||
{
|
||||
if (!_isConnected)
|
||||
{
|
||||
throw new InvalidOperationException("Camera is not connected. Call Open() first.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user