TURBO-569:更新工程结构;将导航相机标定和校准功能迁移到XP.Camera类
This commit is contained in:
+97
-124
@@ -1,6 +1,6 @@
|
||||
# XP.Camera 使用说明
|
||||
|
||||
基于 .NET 8 WPF 的工业相机控制类库,采用工厂模式 + 统一接口设计,支持多品牌相机扩展。当前已实现 Basler pylon SDK 驱动。
|
||||
基于 .NET 8 WPF 的工业相机控制与标定类库,采用工厂模式 + 统一接口设计,支持多品牌相机扩展。当前已实现 Basler pylon SDK 驱动。
|
||||
|
||||
## 环境要求
|
||||
|
||||
@@ -12,16 +12,36 @@
|
||||
|
||||
```
|
||||
XP.Camera/
|
||||
├── ICameraController.cs # 控制器接口 + 工厂接口
|
||||
├── CameraFactory.cs # 统一工厂(根据品牌创建控制器)
|
||||
├── BaslerCameraController.cs # Basler 实现
|
||||
├── CameraModels.cs # CameraInfo、ImageGrabbedEventArgs、GrabErrorEventArgs
|
||||
├── CameraExceptions.cs # CameraException、ConnectionLostException、DeviceNotFoundException
|
||||
├── PixelConverter.cs # 像素数据 → WPF BitmapSource 转换工具
|
||||
├── Core/ # 相机核心抽象
|
||||
│ ├── ICameraController.cs # 控制器接口 + 工厂接口
|
||||
│ ├── CameraFactory.cs # 统一工厂(根据品牌创建控制器)
|
||||
│ ├── CameraModels.cs # CameraInfo、ImageGrabbedEventArgs、GrabErrorEventArgs
|
||||
│ └── CameraExceptions.cs # CameraException、ConnectionLostException、DeviceNotFoundException
|
||||
├── Basler/ # Basler 品牌实现
|
||||
│ └── BaslerCameraController.cs # Basler pylon SDK 实现
|
||||
├── Converters/ # 数据转换工具
|
||||
│ └── PixelConverter.cs # 像素数据 → WPF BitmapSource 转换
|
||||
├── Calibration/ # 相机标定模块
|
||||
│ ├── CalibrationProcessor.cs # 九点标定(DLT 单应性矩阵,像素→世界坐标)
|
||||
│ ├── ChessboardCalibrator.cs # 棋盘格标定(Zhang's 方法,内参 + 畸变校正)
|
||||
│ ├── IDialogService.cs # ICalibrationDialogService 接口
|
||||
│ ├── DefaultCalibrationDialogService.cs # 默认实现(标准 WPF MessageBox)
|
||||
│ ├── CalibrationLocalizedStrings.cs # XAML 本地化绑定辅助
|
||||
│ ├── Controls/ # 标定 UI 控件(UserControl)
|
||||
│ │ ├── CalibrationControl.xaml/.cs # 九点标定界面
|
||||
│ │ ├── ChessboardCalibrationControl.xaml/.cs # 棋盘格标定界面
|
||||
│ │ └── ImageCanvasControl.xaml/.cs # 图像画布(缩放/平移)
|
||||
│ ├── ViewModels/ # 标定视图模型
|
||||
│ │ ├── CalibrationViewModel.cs
|
||||
│ │ └── ChessboardCalibrationViewModel.cs
|
||||
│ └── Resources/ # 本地化资源
|
||||
│ ├── CalibrationResources.resx # 中文(默认)
|
||||
│ ├── CalibrationResources.en-US.resx # 英文
|
||||
│ └── CalibrationResources.Designer.cs
|
||||
└── XP.Camera.csproj
|
||||
```
|
||||
|
||||
所有类型统一在 `XP.Camera` 命名空间下。
|
||||
所有相机核心类型在 `XP.Camera` 命名空间下,标定模块在 `XP.Camera.Calibration` 命名空间下。
|
||||
|
||||
## 项目引用
|
||||
|
||||
@@ -48,27 +68,12 @@ Console.WriteLine($"已连接: {info.ModelName} (SN: {info.SerialNumber})");
|
||||
在 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
|
||||
@@ -82,60 +87,85 @@ public class MyViewModel
|
||||
```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;
|
||||
});
|
||||
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. 实时连续采集(链式触发)
|
||||
### 5. 使用标定模块
|
||||
|
||||
收到上一帧后立即触发下一帧,自动适配任何帧率:
|
||||
标定模块完全自包含,可独立使用,无需外部依赖。
|
||||
|
||||
#### 棋盘格标定(相机内参 + 畸变校正)
|
||||
|
||||
```csharp
|
||||
private volatile bool _liveViewRunning;
|
||||
using XP.Camera.Calibration;
|
||||
using XP.Camera.Calibration.ViewModels;
|
||||
using XP.Camera.Calibration.Controls;
|
||||
|
||||
_camera.ImageGrabbed += (s, e) =>
|
||||
// 使用默认对话框服务(标准 WPF MessageBox)
|
||||
var dialogService = new DefaultCalibrationDialogService();
|
||||
var viewModel = new ChessboardCalibrationViewModel(dialogService);
|
||||
|
||||
var window = new Window
|
||||
{
|
||||
var bitmap = PixelConverter.ToBitmapSource(e.PixelData, e.Width, e.Height, e.PixelFormat);
|
||||
Application.Current.Dispatcher.Invoke(() => CameraImageSource = bitmap);
|
||||
|
||||
if (_liveViewRunning)
|
||||
_camera.ExecuteSoftwareTrigger(); // 链式触发下一帧
|
||||
Title = "棋盘格标定",
|
||||
Width = 1600, Height = 900,
|
||||
Content = new ChessboardCalibrationControl { DataContext = viewModel }
|
||||
};
|
||||
window.ShowDialog();
|
||||
```
|
||||
|
||||
// 启动实时
|
||||
_camera.StartGrabbing();
|
||||
_liveViewRunning = true;
|
||||
_camera.ExecuteSoftwareTrigger(); // 触发第一帧
|
||||
#### 九点标定(像素→世界坐标)
|
||||
|
||||
// 停止实时
|
||||
_liveViewRunning = false;
|
||||
```csharp
|
||||
var dialogService = new DefaultCalibrationDialogService();
|
||||
var viewModel = new CalibrationViewModel(dialogService);
|
||||
|
||||
var window = new Window
|
||||
{
|
||||
Title = "九点标定",
|
||||
Width = 1400, Height = 850,
|
||||
Content = new CalibrationControl { DataContext = viewModel }
|
||||
};
|
||||
window.ShowDialog();
|
||||
```
|
||||
|
||||
#### 纯算法调用(不使用 UI)
|
||||
|
||||
```csharp
|
||||
// 棋盘格标定
|
||||
var calibrator = new ChessboardCalibrator();
|
||||
calibrator.CalibrateFromImages(imagePaths, boardWidth: 11, boardHeight: 8, squareSize: 15f, out string error);
|
||||
calibrator.SaveCalibration("camera_calibration.json");
|
||||
|
||||
// 九点标定
|
||||
var processor = new CalibrationProcessor();
|
||||
processor.Calibrate(points);
|
||||
var worldPoint = processor.PixelToWorld(new PointF(100, 200));
|
||||
```
|
||||
|
||||
#### 自定义对话框服务
|
||||
|
||||
如需自定义弹框样式,实现 `ICalibrationDialogService` 接口即可:
|
||||
|
||||
```csharp
|
||||
public class MyDialogService : ICalibrationDialogService
|
||||
{
|
||||
// 实现所有接口方法,使用自定义 UI 组件...
|
||||
}
|
||||
```
|
||||
|
||||
## 核心接口
|
||||
@@ -149,94 +179,37 @@ _liveViewRunning = false;
|
||||
| `StartGrabbing()` | 以软件触发模式启动采集 |
|
||||
| `ExecuteSoftwareTrigger()` | 触发一帧采集 |
|
||||
| `StopGrabbing()` | 停止采集 |
|
||||
| `Get/SetExposureTime` | 曝光时间(微秒) |
|
||||
| `Get/SetGain` | 增益值 |
|
||||
| `Get/SetWidth/Height` | 图像尺寸 |
|
||||
| `Get/SetPixelFormat` | 像素格式(Mono8 / BGR8 / BGRA8) |
|
||||
|
||||
### 参数读写
|
||||
### 事件
|
||||
|
||||
| 方法 | 说明 |
|
||||
| 事件 | 说明 |
|
||||
|------|------|
|
||||
| `Get/SetExposureTime(double)` | 曝光时间(微秒) |
|
||||
| `Get/SetGain(double)` | 增益值 |
|
||||
| `Get/SetWidth(int)` | 图像宽度(自动校正到有效值) |
|
||||
| `Get/SetHeight(int)` | 图像高度(自动校正到有效值) |
|
||||
| `Get/SetPixelFormat(string)` | 像素格式(Mono8 / BGR8 / BGRA8) |
|
||||
| `ImageGrabbed` | 成功采集一帧图像 |
|
||||
| `GrabError` | 图像采集失败 |
|
||||
| `ConnectionLost` | 相机连接意外断开 |
|
||||
|
||||
### ICameraFactory
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `CreateController(string cameraType)` | 根据品牌名创建控制器 |
|
||||
|
||||
当前支持的 `cameraType` 值:`"Basler"`
|
||||
|
||||
## 事件
|
||||
|
||||
| 事件 | 说明 | 触发线程 |
|
||||
|------|------|----------|
|
||||
| `ImageGrabbed` | 成功采集一帧图像 | StreamGrabber 回调线程 |
|
||||
| `GrabError` | 图像采集失败 | StreamGrabber 回调线程 |
|
||||
| `ConnectionLost` | 相机连接意外断开 | pylon SDK 事件线程 |
|
||||
|
||||
> 所有事件均在非 UI 线程触发。更新 WPF 界面时需通过 `Dispatcher.Invoke` 调度。
|
||||
> `PixelConverter.ToBitmapSource()` 返回的 BitmapSource 已调用 `Freeze()`,可直接跨线程传递。
|
||||
> 所有事件均在非 UI 线程触发,更新 WPF 界面时需通过 `Dispatcher.Invoke` 调度。
|
||||
|
||||
## 异常处理
|
||||
|
||||
```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. 配置文件切换品牌即可,业务代码无需修改。
|
||||
1. 在 `Basler/` 同级创建新文件夹,实现 `ICameraController` 接口
|
||||
2. 在 `CameraFactory.cs` 中注册新品牌
|
||||
3. 配置文件切换品牌即可,业务代码无需修改
|
||||
|
||||
## 线程安全
|
||||
|
||||
- 所有公共方法(Open / Close / StartGrabbing / StopGrabbing / ExecuteSoftwareTrigger / 参数读写)均线程安全
|
||||
- 所有公共方法均线程安全
|
||||
- 事件回调不持有内部锁,不会导致死锁
|
||||
- `Open()` / `Close()` 幂等,重复调用安全
|
||||
|
||||
## 日志
|
||||
|
||||
使用 Serilog 静态 API(`Log.ForContext<T>()`),与宿主应用共享同一个日志管道。宿主应用只需在启动时配置 `Log.Logger` 即可。
|
||||
|
||||
Reference in New Issue
Block a user