# 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 ``` ## 快速开始 ### 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(); containerRegistry.RegisterSingleton(() => 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 ``` ### 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()`),与宿主应用共享同一个日志管道。宿主应用只需在启动时配置 `Log.Logger` 即可。