合并图像处理库,删除图像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
+517
View File
@@ -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.");
}
}
}
+25
View File
@@ -0,0 +1,25 @@
namespace XP.Camera;
/// <summary>所有相机相关错误的基类异常。</summary>
public class CameraException : Exception
{
public CameraException() { }
public CameraException(string message) : base(message) { }
public CameraException(string message, Exception innerException) : base(message, innerException) { }
}
/// <summary>当相机连接意外断开时抛出的异常。</summary>
public class ConnectionLostException : CameraException
{
public ConnectionLostException() { }
public ConnectionLostException(string message) : base(message) { }
public ConnectionLostException(string message, Exception innerException) : base(message, innerException) { }
}
/// <summary>当系统中无可用相机设备时抛出的异常。</summary>
public class DeviceNotFoundException : CameraException
{
public DeviceNotFoundException() { }
public DeviceNotFoundException(string message) : base(message) { }
public DeviceNotFoundException(string message, Exception innerException) : base(message, innerException) { }
}
+18
View File
@@ -0,0 +1,18 @@
namespace XP.Camera;
/// <summary>
/// 统一相机工厂,根据品牌名称创建对应的相机控制器。
/// </summary>
public class CameraFactory : ICameraFactory
{
/// <inheritdoc />
public ICameraController CreateController(string cameraType)
{
return cameraType switch
{
"Basler" => new BaslerCameraController(),
// "Hikvision" => new HikvisionCameraController(),
_ => throw new NotSupportedException($"不支持的相机品牌: {cameraType}")
};
}
}
+39
View File
@@ -0,0 +1,39 @@
namespace XP.Camera;
/// <summary>相机设备信息。</summary>
public record CameraInfo(
string ModelName,
string SerialNumber,
string VendorName,
string DeviceType
);
/// <summary>图像采集成功事件参数。</summary>
public class ImageGrabbedEventArgs : EventArgs
{
public byte[] PixelData { get; }
public int Width { get; }
public int Height { get; }
public string PixelFormat { get; }
public ImageGrabbedEventArgs(byte[] pixelData, int width, int height, string pixelFormat)
{
PixelData = pixelData;
Width = width;
Height = height;
PixelFormat = pixelFormat;
}
}
/// <summary>图像采集失败事件参数。</summary>
public class GrabErrorEventArgs : EventArgs
{
public int ErrorCode { get; }
public string ErrorDescription { get; }
public GrabErrorEventArgs(int errorCode, string errorDescription)
{
ErrorCode = errorCode;
ErrorDescription = errorDescription;
}
}
+53
View File
@@ -0,0 +1,53 @@
namespace XP.Camera;
/// <summary>
/// 相机控制器接口,定义与品牌无关的相机操作契约。
/// </summary>
/// <remarks>
/// <para>所有公共方法(Open/Close/StartGrabbing/StopGrabbing/ExecuteSoftwareTrigger/参数读写)保证线程安全。</para>
/// <para>事件回调在非 UI 线程上触发,调用方如需更新 WPF 界面,应通过 Dispatcher 调度。</para>
/// </remarks>
public interface ICameraController : IDisposable
{
bool IsConnected { get; }
bool IsGrabbing { get; }
/// <summary>打开相机连接并返回设备信息。</summary>
CameraInfo Open();
/// <summary>关闭相机连接并释放资源。</summary>
void Close();
/// <summary>以软件触发模式启动图像采集。</summary>
void StartGrabbing();
/// <summary>发送一次软件触发信号以采集一帧图像。</summary>
void ExecuteSoftwareTrigger();
/// <summary>停止图像采集。</summary>
void StopGrabbing();
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);
event EventHandler<ImageGrabbedEventArgs> ImageGrabbed;
event EventHandler<GrabErrorEventArgs> GrabError;
event EventHandler ConnectionLost;
}
/// <summary>
/// 相机工厂接口,负责根据品牌创建相机控制器实例。
/// </summary>
public interface ICameraFactory
{
/// <summary>根据相机品牌创建控制器实例。</summary>
ICameraController CreateController(string cameraType);
}
+34
View File
@@ -0,0 +1,34 @@
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace XP.Camera;
/// <summary>
/// 提供像素数据到 WPF BitmapSource 的转换工具方法。
/// </summary>
public static class PixelConverter
{
/// <summary>
/// 将原始像素数据转换为 WPF 的 BitmapSource 对象。
/// 返回的 BitmapSource 已调用 Freeze(),可跨线程访问。
/// </summary>
public static BitmapSource ToBitmapSource(byte[] pixelData, int width, int height, string pixelFormat)
{
ArgumentNullException.ThrowIfNull(pixelData);
if (width <= 0) throw new ArgumentException("Width must be a positive integer.", nameof(width));
if (height <= 0) throw new ArgumentException("Height must be a positive integer.", nameof(height));
ArgumentNullException.ThrowIfNull(pixelFormat);
var (format, stride) = pixelFormat switch
{
"Mono8" => (PixelFormats.Gray8, width),
"BGR8" => (PixelFormats.Bgr24, width * 3),
"BGRA8" => (PixelFormats.Bgra32, width * 4),
_ => throw new NotSupportedException($"Pixel format '{pixelFormat}' is not supported.")
};
var bitmap = BitmapSource.Create(width, height, 96, 96, format, null, pixelData, stride);
bitmap.Freeze();
return bitmap;
}
}
+242
View File
@@ -0,0 +1,242 @@
# XP.Camera 使用说明
基于 .NET 8 WPF 的工业相机控制类库,采用工厂模式 + 统一接口设计,支持多品牌相机扩展。当前已实现 Basler pylon SDK 驱动。
## 环境要求
- .NET 8 SDK
- Windows 操作系统
- Basler pylon 8 SDK(已安装并配置环境变量)
## 项目结构
```
XP.Camera/
├── ICameraController.cs # 控制器接口 + 工厂接口
├── CameraFactory.cs # 统一工厂(根据品牌创建控制器)
├── BaslerCameraController.cs # Basler 实现
├── CameraModels.cs # CameraInfo、ImageGrabbedEventArgs、GrabErrorEventArgs
├── CameraExceptions.cs # CameraException、ConnectionLostException、DeviceNotFoundException
├── PixelConverter.cs # 像素数据 → WPF BitmapSource 转换工具
└── XP.Camera.csproj
```
所有类型统一在 `XP.Camera` 命名空间下。
## 项目引用
```xml
<ProjectReference Include="..\XP.Camera\XP.Camera.csproj" />
```
## 快速开始
### 1. 通过工厂创建控制器
```csharp
using XP.Camera;
ICameraFactory factory = new CameraFactory();
using ICameraController camera = factory.CreateController("Basler");
CameraInfo info = camera.Open();
Console.WriteLine($"已连接: {info.ModelName} (SN: {info.SerialNumber})");
```
### 2. 依赖注入方式(推荐)
在 Prism / DI 容器中注册:
```csharp
// App.xaml.cs
var config = AppConfig.Load();
containerRegistry.RegisterSingleton<ICameraFactory, CameraFactory>();
containerRegistry.RegisterSingleton<ICameraController>(() =>
new CameraFactory().CreateController(config.CameraType));
```
ViewModel 中注入使用:
```csharp
public class MyViewModel
{
private readonly ICameraController _camera;
public MyViewModel(ICameraController camera)
{
_camera = camera;
}
}
```
相机品牌通过配置文件 `config.json` 指定:
```json
{
"CameraType": "Basler"
}
```
### 3. 实时图像显示(WPF 绑定)
```csharp
_camera.ImageGrabbed += (s, e) =>
{
// PixelConverter 返回已 Freeze 的 BitmapSource,可跨线程传递
var bitmap = PixelConverter.ToBitmapSource(
e.PixelData, e.Width, e.Height, e.PixelFormat);
Application.Current.Dispatcher.Invoke(() =>
{
CameraImageSource = bitmap;
});
};
```
XAML 绑定:
```xml
<Image Source="{Binding CameraImageSource}" Stretch="Uniform" />
```
### 4. 软件触发采集流程
```csharp
camera.Open();
camera.SetExposureTime(10000); // 10ms
camera.StartGrabbing();
// 每次需要采集时调用(结果通过 ImageGrabbed 事件返回)
camera.ExecuteSoftwareTrigger();
camera.StopGrabbing();
camera.Close();
```
### 5. 实时连续采集(链式触发)
收到上一帧后立即触发下一帧,自动适配任何帧率:
```csharp
private volatile bool _liveViewRunning;
_camera.ImageGrabbed += (s, e) =>
{
var bitmap = PixelConverter.ToBitmapSource(e.PixelData, e.Width, e.Height, e.PixelFormat);
Application.Current.Dispatcher.Invoke(() => CameraImageSource = bitmap);
if (_liveViewRunning)
_camera.ExecuteSoftwareTrigger(); // 链式触发下一帧
};
// 启动实时
_camera.StartGrabbing();
_liveViewRunning = true;
_camera.ExecuteSoftwareTrigger(); // 触发第一帧
// 停止实时
_liveViewRunning = false;
```
## 核心接口
### ICameraController
| 方法 | 说明 |
|------|------|
| `Open()` | 打开连接,返回 `CameraInfo` |
| `Close()` | 关闭连接(自动停止采集) |
| `StartGrabbing()` | 以软件触发模式启动采集 |
| `ExecuteSoftwareTrigger()` | 触发一帧采集 |
| `StopGrabbing()` | 停止采集 |
### 参数读写
| 方法 | 说明 |
|------|------|
| `Get/SetExposureTime(double)` | 曝光时间(微秒) |
| `Get/SetGain(double)` | 增益值 |
| `Get/SetWidth(int)` | 图像宽度(自动校正到有效值) |
| `Get/SetHeight(int)` | 图像高度(自动校正到有效值) |
| `Get/SetPixelFormat(string)` | 像素格式(Mono8 / BGR8 / BGRA8 |
### ICameraFactory
| 方法 | 说明 |
|------|------|
| `CreateController(string cameraType)` | 根据品牌名创建控制器 |
当前支持的 `cameraType` 值:`"Basler"`
## 事件
| 事件 | 说明 | 触发线程 |
|------|------|----------|
| `ImageGrabbed` | 成功采集一帧图像 | StreamGrabber 回调线程 |
| `GrabError` | 图像采集失败 | StreamGrabber 回调线程 |
| `ConnectionLost` | 相机连接意外断开 | pylon SDK 事件线程 |
> 所有事件均在非 UI 线程触发。更新 WPF 界面时需通过 `Dispatcher.Invoke` 调度。
> `PixelConverter.ToBitmapSource()` 返回的 BitmapSource 已调用 `Freeze()`,可直接跨线程传递。
## 异常处理
```csharp
try
{
camera.Open();
}
catch (DeviceNotFoundException)
{
// 无可用相机设备
}
catch (CameraException ex)
{
// 其他相机错误,ex.InnerException 包含原始 SDK 异常
}
```
| 异常类型 | 场景 |
|---------|------|
| `DeviceNotFoundException` | 无可用相机 |
| `ConnectionLostException` | 相机物理断开 |
| `CameraException` | SDK 操作失败(基类) |
| `InvalidOperationException` | 未连接时访问参数,未采集时触发 |
| `TimeoutException` | 软件触发等待超时 |
## 扩展其他品牌相机
1. 实现 `ICameraController` 接口:
```csharp
public class HikvisionCameraController : ICameraController
{
// 实现所有接口方法...
}
```
2.`CameraFactory.cs` 中注册:
```csharp
public ICameraController CreateController(string cameraType)
{
return cameraType switch
{
"Basler" => new BaslerCameraController(),
"Hikvision" => new HikvisionCameraController(),
_ => throw new NotSupportedException($"不支持的相机品牌: {cameraType}")
};
}
```
3. 配置文件切换品牌即可,业务代码无需修改。
## 线程安全
- 所有公共方法(Open / Close / StartGrabbing / StopGrabbing / ExecuteSoftwareTrigger / 参数读写)均线程安全
- 事件回调不持有内部锁,不会导致死锁
- `Open()` / `Close()` 幂等,重复调用安全
## 日志
使用 Serilog 静态 API`Log.ForContext<T>()`),与宿主应用共享同一个日志管道。宿主应用只需在启动时配置 `Log.Logger` 即可。
+19
View File
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>XP.Camera</RootNamespace>
<AssemblyName>XP.Camera</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="Basler.Pylon">
<HintPath>C:\Program Files\Basler\pylon 8\Development\Assemblies\Basler.Pylon\x64\Basler.Pylon.dll</HintPath>
</Reference>
<PackageReference Include="Serilog" Version="4.3.1" />
</ItemGroup>
</Project>