在智能制造和自动化检测领域,工业相机如同视觉系统的"眼睛"。但不同品牌的SDK接口差异,常常让开发者陷入"重复造轮子"的困境。我曾参与过一条汽车零部件检测线改造项目,产线上同时存在Basler、海康和堡盟三个品牌的相机,每更换一次设备就要重写采集逻辑——这种体验促使我设计了一套通用采集架构。
这套架构的核心价值在于:用统一的C#接口封装不同厂商的底层SDK,实现"一次编写,多处运行"。在实际产线环境中测试表明,切换相机品牌时代码修改量减少90%以上,新设备接入时间从原来的2-3天缩短到2小时内。
采用经典的三层架构模式:
code复制[应用层] ←→ [统一接口层] ←→ [厂商适配层] ←→ [硬件]
关键在于接口层的抽象设计。通过分析20+个工业相机项目需求,提炼出6个核心操作:
用接口隔离原则(ISP)定义IIndustrialCamera接口:
csharp复制public interface IIndustrialCamera {
event Action<Bitmap> OnFrameReceived;
void Connect(string serialNumber);
void Disconnect();
void StartGrabbing();
void StopGrabbing();
void SetParameter(CameraParam param, object value);
//...其他核心方法
}
不同厂商SDK的差异主要体现在三个方面:
通过适配器模式(Adapter Pattern)实现统一封装。以海康相机为例:
csharp复制class HikCameraAdapter : IIndustrialCamera {
private IntPtr _handle;
private MV_CC_DEVICE_INFO _deviceInfo;
public void Connect(string sn) {
MV_CC_CreateDevice(ref _deviceInfo, ref _handle);
MV_CC_OpenDevice(_handle);
// 设置转换回调
MV_CC_RegisterImageCallBack(_handle, OnHikFrameReceived);
}
private void OnHikFrameReceived(IntPtr pData, ref MV_FRAME_OUT_INFO info) {
// 将海康原始数据转换为标准Bitmap
var bitmap = ConvertHikToBitmap(pData, info);
OnFrameReceived?.Invoke(bitmap);
}
//...其他接口实现
}
工业场景对实时性要求苛刻,我们通过以下手段保证性能:
实测在200万像素@30fps场景下,CPU占用率控制在15%以下,延迟<50ms。
通过反射加载所有适配器DLL,自动识别可用相机类型:
csharp复制var adapters = Directory.GetFiles("Adapters", "*Adapter.dll")
.Select(Assembly.LoadFrom)
.SelectMany(a => a.GetTypes())
.Where(t => typeof(IIndustrialCamera).IsAssignableFrom(t));
foreach(var adapter in adapters) {
var camera = (IIndustrialCamera)Activator.CreateInstance(adapter);
if(camera.TryPing()) {
availableCameras.Add(camera);
}
}
不同厂商的参数命名差异很大,我们建立映射表:
csharp复制static Dictionary<CameraParam, Tuple<string, Type>> _paramMappings = new() {
[CameraParam.ExposureTime] = Tuple.Create("ExposureTime", typeof(double)), // Basler
[CameraParam.ExposureTime] = Tuple.Create("ExposureTimeUs", typeof(int)), // 海康
[CameraParam.ExposureTime] = Tuple.Create("Shutter", typeof(float)), // 堡盟
};
通过动态编译生成最优化的参数设置代码:
csharp复制var setter = Expression.Lambda<Action<object, object>>(
Expression.Assign(
Expression.PropertyOrField(
Expression.Convert(paramObj, type),
propName),
Expression.Convert(valueParam, propType)),
paramObj, valueParam).Compile();
设计可扩展的图像转换管道:
csharp复制interface IImageConverter {
bool CanConvert(string sourceFormat, string targetFormat);
Bitmap Convert(byte[] sourceData);
}
// 注册常用转换器
Converters.Add(new BayerRG8ToRGBConverter());
Converters.Add(new Mono8ToGrayConverter());
Converters.Add(new YUV422ToRGBConverter());
在某新能源电池项目中,需要同时控制:
通过统一架构实现:
csharp复制var cameras = new CameraCollection();
cameras.Add(new BaslerCamera("SN123"));
cameras.Add(new HikCamera("SN456"));
cameras.Add(new BaumerCamera("SN789"));
// 统一配置参数
cameras.ForEach(c => {
c.SetParameter(CameraParam.ExposureTime, 2000);
c.SetParameter(CameraParam.TriggerMode, HardwareTrigger);
});
// 同步触发
cameras.StartGrabbing();
在3D视觉项目中,需要精确控制多相机触发时序:
csharp复制// 配置硬件触发级联
var master = cameras[0];
master.SetParameter(CameraParam.TriggerSource, GPIOInput);
master.SetParameter(CameraParam.TriggerDelay, 0);
for(int i=1; i<cameras.Count; i++) {
cameras[i].SetParameter(CameraParam.TriggerSource, Software);
master.OnFrameReceived += _ => {
Task.Delay(50).ContinueWith(_ =>
cameras[i].SetParameter(CameraParam.SoftwareTrigger, null));
};
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 防火墙阻止SDK通信 | 添加SDK可执行文件到白名单 |
| 图像撕裂 | 缓冲区不足 | 增加OutputQueueSize参数 |
| 帧率不稳 | 网络带宽不足 | 启用JPEG压缩或降低分辨率 |
| 颜色异常 | Bayer格式不匹配 | 检查PixelFormat映射关系 |
工业相机SDK常存在非托管资源泄漏风险,建议采用以下模式:
csharp复制class SafeCameraHandle : SafeHandle {
protected override bool ReleaseHandle() {
// 保证SDK资源释放
NativeMethods.CameraRelease(handle);
return true;
}
}
// 在适配器中包装
class CameraAdapter : IDisposable {
private SafeCameraHandle _handle;
public void Dispose() {
_handle?.Dispose();
GC.SuppressFinalize(this);
}
}
网络配置:
SDK参数:
csharp复制camera.SetParameter(CameraParam.GevSCPD, 100); // 流控制包间隔
camera.SetParameter(CameraParam.PacketSize, 1500); // 优化网络包大小
线程模型:
csharp复制// 使用专用线程处理回调
var callbackThread = new Thread(() => {
SetThreadPriority(ThreadPriority.Highest);
while(!stopped) {
WaitForCallbackEvent();
ProcessImage();
}
}) { IsBackground = true };
这套架构在实践中展现出良好的扩展性:
IIndustrialCamera接口在最近的项目中,我们进一步集成了AI推理框架,形成完整的"采集-处理-分析"管道:
csharp复制camera.OnFrameReceived += frame => {
var preprocessed = Pipeline.Execute(frame);
var result = AIModel.Infer(preprocessed);
PublishToMES(result);
};
实际部署数据显示,采用统一架构后: