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 命名空间下。
项目引用
<ProjectReference Include="..\XP.Camera\XP.Camera.csproj" />
快速开始
1. 通过工厂创建控制器
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 容器中注册:
// App.xaml.cs
var config = AppConfig.Load();
containerRegistry.RegisterSingleton<ICameraFactory, CameraFactory>();
containerRegistry.RegisterSingleton<ICameraController>(() =>
new CameraFactory().CreateController(config.CameraType));
ViewModel 中注入使用:
public class MyViewModel
{
private readonly ICameraController _camera;
public MyViewModel(ICameraController camera)
{
_camera = camera;
}
}
相机品牌通过配置文件 config.json 指定:
{
"CameraType": "Basler"
}
3. 实时图像显示(WPF 绑定)
_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 绑定:
<Image Source="{Binding CameraImageSource}" Stretch="Uniform" />
4. 软件触发采集流程
camera.Open();
camera.SetExposureTime(10000); // 10ms
camera.StartGrabbing();
// 每次需要采集时调用(结果通过 ImageGrabbed 事件返回)
camera.ExecuteSoftwareTrigger();
camera.StopGrabbing();
camera.Close();
5. 实时连续采集(链式触发)
收到上一帧后立即触发下一帧,自动适配任何帧率:
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(),可直接跨线程传递。
异常处理
try
{
camera.Open();
}
catch (DeviceNotFoundException)
{
// 无可用相机设备
}
catch (CameraException ex)
{
// 其他相机错误,ex.InnerException 包含原始 SDK 异常
}
| 异常类型 | 场景 |
|---|---|
DeviceNotFoundException |
无可用相机 |
ConnectionLostException |
相机物理断开 |
CameraException |
SDK 操作失败(基类) |
InvalidOperationException |
未连接时访问参数,未采集时触发 |
TimeoutException |
软件触发等待超时 |
扩展其他品牌相机
- 实现
ICameraController接口:
public class HikvisionCameraController : ICameraController
{
// 实现所有接口方法...
}
- 在
CameraFactory.cs中注册:
public ICameraController CreateController(string cameraType)
{
return cameraType switch
{
"Basler" => new BaslerCameraController(),
"Hikvision" => new HikvisionCameraController(),
_ => throw new NotSupportedException($"不支持的相机品牌: {cameraType}")
};
}
- 配置文件切换品牌即可,业务代码无需修改。
线程安全
- 所有公共方法(Open / Close / StartGrabbing / StopGrabbing / ExecuteSoftwareTrigger / 参数读写)均线程安全
- 事件回调不持有内部锁,不会导致死锁
Open()/Close()幂等,重复调用安全
日志
使用 Serilog 静态 API(Log.ForContext<T>()),与宿主应用共享同一个日志管道。宿主应用只需在启动时配置 Log.Logger 即可。