2. 相机选型与驱动:面阵相机与线阵相机、分辨率与帧率、GigE Vision与USB3 Vision协议、SDK初始化与图像采集
相机选型,说白了就是给项目配眼睛。眼睛选错了,后面的算法再牛也白搭。我见过太多项目,算法调了三个月发现是相机分辨率不够,或者帧率跟不上产线速度。嗯,咱们今天就把这事聊透。
2.1 面阵相机 vs 线阵相机:怎么选?
先搞清楚一个核心问题:你的被测物体是静止的,还是在高速运动?
面阵相机,就像你手机摄像头,一次拍一张完整的矩形图像。适合拍小件、静止件、或者运动速度不快的场景。比如电子元器件检测、PCB焊点检查、药品泡罩包装检测。
线阵相机,每次只拍一条线,靠物体运动或相机扫描来拼成一张长图。适合拍大幅面、连续运动、圆柱体展开的场景。比如印刷品检测、纺织布匹、锂电池极片、钢管表面。
| 对比项 | 面阵相机 | 线阵相机 |
|---|---|---|
| 成像方式 | 一次曝光,整幅图像 | 逐行扫描,拼接成图 |
| 适用场景 | 静止或低速运动、小视场 | 高速连续运动、大幅面 |
| 分辨率 | 固定宽高比(如1920×1080) | 行分辨率高(如8K、16K),长度不限 |
| 光源要求 | 面光源或环形光源 | 线光源,光照均匀性要求高 |
| 触发方式 | 软触发或外触发 | 必须配合编码器或外触发 |
| 成本 | 相对较低 | 相机+镜头+光源+编码器,整体较高 |
我的经验: 如果物体宽度超过300mm且产线速度大于0.5m/s,我建议优先考虑线阵。面阵在高速下容易产生运动模糊,补光又很难做到大视场均匀。我曾经在一个印刷检测项目里硬用面阵,结果补光花了三周才搞定,后来换成线阵两天就解决了。
2.2 分辨率与帧率:别被参数忽悠了
分辨率决定你能看清多小的缺陷,帧率决定你一秒能拍多少张。但这两个参数是互相制约的——分辨率越高,帧率通常越低。
怎么算?我一般按这个流程来:
- 先定精度:客户要求检测的最小缺陷是0.1mm?那你的像素精度至少得0.05mm/pixel。
- 再算分辨率:视场宽度100mm ÷ 0.05mm/pixel = 2000像素。所以至少选200万像素的相机。
- 最后算帧率:产线节拍是每秒5个产品,每个产品拍2张,那帧率至少10fps。留点余量,选15fps以上。
避坑指南: 别只看标称帧率。实际帧率受曝光时间、传输带宽、处理延迟影响很大。我曾经被一个标称60fps的相机坑过——实际跑到30fps就开始丢帧了。后来发现是曝光时间设太长,带宽也撑不住。
分辨率与帧率的常见搭配,我整理了一张表:
| 分辨率 | 像素数 | 典型帧率(GigE) | 典型帧率(USB3) | 适用场景 |
|---|---|---|---|---|
| 640×480 | 30万 | 100-200fps | 200-300fps | 高速定位、条码读取 |
| 1920×1080 | 200万 | 30-60fps | 60-120fps | 通用检测、尺寸测量 |
| 2448×2048 | 500万 | 15-30fps | 30-60fps | 精密测量、缺陷检测 |
| 4096×3000 | 1200万 | 8-15fps | 15-30fps | 大视场高精度检测 |
2.3 GigE Vision vs USB3 Vision:协议怎么选?
这两个协议是工业相机的主流接口。选哪个?我个人的习惯是:
- GigE Vision:传输距离远(100米),抗干扰强,适合分布式部署。但CPU占用高,需要优化驱动。
- USB3 Vision:带宽高(5Gbps),延迟低,CPU占用小。但传输距离短(3-5米),线缆贵。
你想想看,如果你的相机离工控机只有1米,那USB3是更好的选择。如果相机分布在产线各处,距离超过10米,那只能走GigE。
注意: GigE Vision的网卡和交换机必须支持巨帧(Jumbo Frame),否则实际带宽会打折扣。我遇到过客户用普通千兆网卡,结果带宽只有300Mbps,丢帧丢到怀疑人生。
协议对比表:
| 特性 | GigE Vision | USB3 Vision |
|---|---|---|
| 带宽 | 1Gbps(实际约900Mbps) | 5Gbps(实际约3.5Gbps) |
| 传输距离 | 100米(Cat6) | 3-5米(USB3.0线缆) |
| CPU占用 | 较高(需优化) | 较低(硬件卸载) |
| 线缆成本 | 低(网线) | 高(专用线缆) |
| 多相机同步 | 支持IEEE 1588 | 需外部触发 |
| 热插拔 | 支持 | 支持 |
2.4 SDK初始化与图像采集:从零开始跑通第一帧
协议选好了,相机买回来了,接下来就是写代码。我以最常见的GigE相机为例,用C++演示SDK初始化和图像采集的流程。
先看整体流程:
下面是一段完整的SDK初始化代码,我用的是HikRobot的MVS SDK,其他品牌(Basler、Daheng、AVT)流程大同小异:
#include <MvCameraControl.h>
#include <iostream>
int main() {
// 1. 枚举设备
MV_CC_DEVICE_INFO_LIST stDeviceList;
memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE, &stDeviceList);
if (nRet != MV_OK || stDeviceList.nDeviceNum == 0) {
std::cerr << "未找到GigE相机" << std::endl;
return -1;
}
// 2. 创建句柄
void* handle = nullptr;
nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);
if (nRet != MV_OK) {
std::cerr << "创建句柄失败" << std::endl;
return -1;
}
// 3. 打开设备
nRet = MV_CC_OpenDevice(handle);
if (nRet != MV_OK) {
std::cerr << "打开设备失败" << std::endl;
MV_CC_DestroyHandle(handle);
return -1;
}
// 4. 设置参数:分辨率、帧率、曝光
MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BayerRG8);
MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", 30.0f);
MV_CC_SetFloatValue(handle, "ExposureTime", 5000.0f); // 5000us
// 5. 开始采集
nRet = MV_CC_StartGrabbing(handle);
if (nRet != MV_OK) {
std::cerr << "开始采集失败" << std::endl;
MV_CC_CloseDevice(handle);
MV_CC_DestroyHandle(handle);
return -1;
}
// 6. 获取一帧图像
MV_FRAME_OUT stFrame = {0};
nRet = MV_CC_GetImageBuffer(handle, &stFrame, 1000);
if (nRet == MV_OK) {
std::cout << "采集成功,图像大小: " << stFrame.stFrameInfo.nFrameLen << " bytes" << std::endl;
// 处理图像...
MV_CC_FreeImageBuffer(handle, &stFrame);
} else {
std::cerr << "采集超时或失败" << std::endl;
}
// 7. 停止采集
MV_CC_StopGrabbing(handle);
// 8. 关闭设备并销毁句柄
MV_CC_CloseDevice(handle);
MV_CC_DestroyHandle(handle);
return 0;
}
我的习惯: 每次采集完一定要调用
MV_CC_FreeImageBuffer 释放缓冲区,否则内存会越涨越高。我见过有人忘了释放,跑了一晚上内存爆了,整个产线停摆。
2.5 图像采集的常见坑与优化
代码跑通了,但实际部署时还有一堆坑等着你。我列几个最常见的:
- 丢帧:检查网卡是否开启巨帧,检查CPU是否过载,检查曝光时间是否太长。
- 图像撕裂:线阵相机必须配合编码器,否则行频和运动速度不匹配。
- 缓存溢出:采集线程和处理线程要分离,用环形缓冲区解耦。
- 触发延迟:外触发信号要用差分信号(RS422),别用单端信号。
我曾经踩过的坑: 在一个锂电池检测项目里,相机一直丢帧,查了两天发现是网线质量太差,用了普通五类线。换成六类屏蔽线后,问题立刻解决。嗯,网线这东西真不能省。