在自动化检测、智能产线等工业场景中,多品牌相机混用是常态。上周刚帮朋友调试一条锂电池检测线,产线上同时出现了Basler的ace系列、海康的MV-CA系列和堡盟的CX系列相机——这简直是工业视觉领域的标准配置组合。每个品牌的相机都带着自己专属的SDK和API文档,开发人员不得不为每个品牌维护独立的采集模块。
最头疼的是当产线需要更换相机型号时,原本写好的海康相机代码对Basler完全无效,所有采集逻辑都要重写。我曾见过有团队为了适配新相机,把整个采集模块推倒重来了三次。这种重复劳动不仅浪费开发资源,更会导致系统稳定性下降——毕竟每次重构都可能引入新的bug。
解决这个问题的核心在于建立硬件抽象层(HAL)。就像打印机驱动一样,无论什么品牌的打印机,操作系统都通过统一的打印接口进行调用。我们的架构也要实现类似的抽象:
在C#中,这样的抽象最适合用接口来实现。我们定义一个IIndustrialCamera接口,包含所有相机都必须实现的方法:
csharp复制public interface IIndustrialCamera {
void Connect(string serialNumber);
void Disconnect();
void StartGrabbing();
void StopGrabbing();
event Action<Bitmap> OnImageGrabbed;
void SetParameter(string key, object value);
object GetParameter(string key);
}
不同厂商SDK的差异主要体现在三个方面:
我们的解决方案是为每个品牌创建适配器类(Adapter),在内部处理这些差异。以Basler为例:
csharp复制public class BaslerCameraAdapter : IIndustrialCamera {
private Basler.Pylon.Camera _camera;
public void StartGrabbing() {
_camera.StreamGrabber.Start(
GrabStrategy.LatestImages,
GrabLoop.ProvidedByStreamGrabber);
}
// 将Basler的图像事件转换为统一事件
private void OnBaslerImageGrabbed(object sender, ImageGrabbedEventArgs e) {
using (var grabResult = e.GrabResult) {
if (grabResult.GrabSucceeded) {
var bitmap = new Bitmap(grabResult.Width, grabResult.Height);
// 像素数据转换...
OnImageGrabbed?.Invoke(bitmap);
}
}
}
}
在工厂环境中,相机的连接可能随时变化。我们通过以下流程实现即插即用:
csharp复制private CameraBrand DetectBrand(string modelName) {
if (modelName.Contains("Basler")) return CameraBrand.Basler;
if (modelName.Contains("Hikrobot")) return CameraBrand.Hikvision;
if (modelName.Contains("Baumer")) return CameraBrand.Baumer;
throw new NotSupportedException("Unrecognized camera brand");
}
csharp复制public IIndustrialCamera CreateCamera(string serialNumber) {
var brand = DetectBrand(GetModelName(serialNumber));
switch (brand) {
case CameraBrand.Basler: return new BaslerCameraAdapter(serialNumber);
case CameraBrand.Hikvision: return new HikCameraAdapter(serialNumber);
case CameraBrand.Baumer: return new BaumerCameraAdapter(serialNumber);
default: throw new NotSupportedException();
}
}
不同品牌对相同参数的命名可能完全不同。我们建立了参数映射表来解决这个问题:
| 通用参数名 | Basler参数路径 | 海康参数ID | 堡盟寄存器地址 |
|---|---|---|---|
| Exposure | "ExposureTimeRaw" | 0x00300020 | 0x0C1A |
| Gain | "GainRaw" | 0x00300030 | 0x0C1B |
| ROI_X | "OffsetX" | 0x00300040 | 0x0C20 |
实现时使用字典存储映射关系:
csharp复制private static readonly Dictionary<string, CameraParameter> _paramMappings = new() {
["Exposure"] = new() {
Basler = "ExposureTimeRaw",
Hikvision = 0x00300020,
Baumer = 0x0C1A
},
// 其他参数...
};
工业相机通常以每秒几十帧的速度传输图像,必须谨慎处理内存:
Bitmap对象池:预分配固定数量的Bitmap对象循环使用
csharp复制private readonly ConcurrentQueue<Bitmap> _bitmapPool = new();
private void InitializePool(int width, int height) {
for (int i = 0; i < 10; i++) {
_bitmapPool.Enqueue(new Bitmap(width, height));
}
}
零拷贝技巧:对于海康相机,可以直接锁定Bitmap的内存:
csharp复制var bitmap = _bitmapPool.Take();
var data = bitmap.LockBits(/*...*/);
HCAMERA.MV_CC_SetImageBUF_NET(_handle, data.Scan0, (uint)data.Stride * height);
在3D检测等场景中,需要精确控制多台相机同时曝光:
csharp复制var sw = Stopwatch.StartNew();
var targetTick = sw.ElapsedTicks + (long)(interval * Stopwatch.Frequency);
while (sw.ElapsedTicks < targetTick) { /* 等待 */ }
TriggerAllCameras();
SDK初始化顺序:必须严格按照以下顺序:
csharp复制HCAMERA.MV_CC_InitializeNET(); // 第一步
var handle = HCAMERA.MV_CC_CreateDevice_NET(); // 第二步
HCAMERA.MV_CC_CreateHandle_NET(ref handle); // 第三步
顺序错误会导致内存泄漏
GigE连接超时:默认超时时间太长(约10秒),需要手动设置:
csharp复制HCAMERA.MV_CC_SetDeviceNetTimeOut_NET(_handle, 2000); // 2秒超时
API路径问题:必须将pylon的Runtime路径加入环境变量:
csharp复制var path = Environment.GetEnvironmentVariable("PATH");
var pylonPath = @"C:\Program Files\Basler\pylon 6\Runtime\x64";
Environment.SetEnvironmentVariable("PATH", $"{path};{pylonPath}");
帧丢失处理:Basler相机在丢帧时不会抛出异常,需要主动检查:
csharp复制if (grabResult.GrabSucceeded) {
// 正常处理
} else {
_logger.Warn($"帧丢失! 错误码:{grabResult.ErrorCode}");
}
当需要支持新品牌时,只需三个步骤:
IIndustrialCamera例如添加大华相机支持:
csharp复制public class DahuaCameraAdapter : IIndustrialCamera {
// 实现接口方法...
}
// 在CameraFactory中:
if (modelName.Contains("Dahua")) return new DahuaCameraAdapter();
通过引入消息队列,可以轻松扩展为分布式架构:
mermaid复制graph LR
A[相机节点1] -->|MQTT| B[中央服务器]
C[相机节点2] -->|MQTT| B
D[相机节点3] -->|MQTT| B
具体实现时,每个相机节点运行独立的采集服务,通过MQTT协议将图像和状态信息发送到中央服务器。这种架构特别适合跨产线的集中监控场景。