1. 工业相机多品牌兼容架构的必要性
在工业视觉系统开发中,相机品牌的选择往往不是由开发者决定的。客户可能因为价格、供货周期或特殊性能需求,在不同项目中指定使用Basler、海康或堡盟等不同品牌的工业相机。每个品牌的相机都提供自己独特的SDK,这些SDK在接口设计、回调机制和资源管理方式上存在显著差异。
1.1 直接调用原生SDK的问题
当开发者直接在业务代码中调用特定品牌的SDK时,会面临几个严重问题:
- 代码耦合度高:业务逻辑与特定SDK深度绑定,更换相机品牌需要重写大量代码
- 维护成本激增:同一功能需要为不同品牌实现多套相似但不兼容的代码
- 异常处理复杂:不同SDK的错误码和异常类型不统一,难以实现一致的错误处理
- 测试困难:难以在不连接实际硬件的情况下进行单元测试
1.2 统一架构的核心价值
通过设计统一的采集架构,我们可以实现:
- 业务逻辑与硬件解耦:上层应用只依赖抽象接口,不关心具体相机品牌
- 配置驱动切换:通过配置文件即可切换相机品牌,无需修改代码
- 标准化数据格式:不同品牌相机的图像数据统一转换为相同格式
- 可测试性提升:可以轻松创建Mock服务进行单元测试
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构设计与核心组件
2.1 整体架构图
code复制业务应用层
↓ (依赖)
ICameraService (抽象接口)
↑ (实现)
BaslerCameraService HikvisionCameraService BaumerCameraService
↑ (创建)
CameraServiceFactory
↑ (读取配置)
appsettings.json
2.2 关键组件说明
2.2.1 ICameraService接口
这是整个架构的核心抽象,定义了相机服务必须实现的基本操作:
csharp复制public interface ICameraService : IDisposable
{
event Action<ImageFrame>? OnNewFrame;
Task<bool> InitializeAsync(string identifier);
Task StartAcquisitionAsync();
Task StopAcquisitionAsync();
Task<ImageFrame?> GrabFrameAsync(int timeoutMs = 1000);
}
接口设计考虑:
- 使用异步方法适应现代C#编程模式
- 通过事件机制提供图像回调
- 继承IDisposable确保资源释放
2.2.2 ImageFrame数据模型
csharp复制public record ImageFrame
{
public byte[] Data { get; init; } = Array.Empty<byte>();
public int Width { get; init; }
public int Height { get; init; }
public long TimestampUs { get; init; }
public string CameraId { get; init; } = string.Empty;
}
设计特点:
- 使用record类型确保不可变性
- 时间戳统一为微秒级,便于多相机同步
- 包含相机序列号用于多相机系统识别
2.2.3 CameraServiceFactory
工厂类负责根据配置创建具体的相机服务实例:
csharp复制public static class CameraServiceFactory
{
public static ICameraService Create(string type)
{
return type.ToLowerInvariant() switch
{
"basler" => new BaslerCameraService(),
"hikvision" => new HikvisionCameraService(),
"baumer" => new BaumerCameraService(),
_ => throw new ArgumentException($"Unsupported camera type: {type}")
};
}
}
3. 具体品牌实现详解
3.1 Basler相机实现要点
Basler使用pylon SDK,其特点是事件驱动的图像采集模式:
csharp复制public class BaslerCameraService : ICameraService
{
private Camera? _camera;
public event Action<ImageFrame>? OnNewFrame;
public async Task<bool> InitializeAsync(string serialNumber)
{
await Task.Run(() => {
_camera = new Camera();
// 查找并连接指定序列号的相机
var deviceInfo = new CameraFinder().Find(
new List<ICameraInfo> { new CameraInfo(serialNumber, CameraInfoKey.SerialNumber) }
).FirstOrDefault();
if (deviceInfo != null) {
_camera.Attach(deviceInfo);
_
