第四章:视觉控制器端开发
好,咱们进入实战环节了。前面几章聊了硬件接线、协议配置,都是打基础。这一章,才是真正让视觉控制器「动起来」的关键——环境搭建、相机初始化、图像采集,再到最核心的定位、测量、识别。
说实话,我最早做视觉项目时,最头疼的就是环境配置。明明照着文档一步步来,就是死活连不上相机。后来才发现,SDK版本和.NET框架不匹配。嗯,这种坑,我替你们踩过了。
4.1 开发环境搭建:C#还是C++?
先解决一个老生常谈的问题:用C#还是C++?
我个人习惯是:原型验证用C#,量产部署用C++。为什么?C#开发效率高,调试方便,适合快速迭代。C++性能更好,内存控制更精细,适合嵌入式环境。
但如果你问我实际项目怎么选?我建议看相机SDK。有些厂商只提供C++的SDK,那就别纠结了,老老实实C++。如果都支持,那新手我推荐C#起步。
我的推荐组合:
- 开发工具:Visual Studio 2022(社区版免费)
- 框架:.NET 6.0 或 .NET Framework 4.8
- 相机SDK:海康MVS / 大恒Galaxy / Basler Pylon
- 图像处理库:OpenCvSharp(C#版OpenCV)
这里有个小技巧:安装SDK时,记得勾选「开发包」选项。我见过有人只装了运行库,结果找不到头文件,折腾半天。
4.2 相机SDK初始化
环境搭好了,下一步就是让电脑认识相机。说白了,就是调用SDK的API,完成枚举设备、创建句柄、打开设备这三步。
以海康MVS为例,C#代码大概长这样:
// 枚举设备
List<IMVDeviceInfo> deviceList = new List<IMVDeviceInfo>();
IMV_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, ref deviceList);
if (deviceList.Count == 0)
{
Console.WriteLine("没找到相机,检查网线或USB线");
return;
}
// 创建句柄并打开设备
IMV_HANDLE hCamera = IMV_CreateHandle(deviceList[0]);
IMV_OpenDevice(hCamera);
Console.WriteLine($"相机 {deviceList[0].GetModelName()} 已连接");
你看,代码其实不多。但有个细节:枚举设备后一定要判断返回值。我在项目中遇到过,USB相机插拔后设备列表变了,没做判断直接取第一个,结果崩溃了。
避坑指南:
我曾经在产线上遇到过一个问题:相机偶尔连不上,重启又好了。查了两天才发现,是USB3.0线太长,信号衰减。后来换成5米以内的有源延长线,问题解决。
所以,相机线缆长度不要超过5米,超过就用光纤转接。
4.3 图像采集:连续抓拍还是触发模式?
相机初始化成功,接下来就是采集图像。这里有两种模式:
| 模式 | 适用场景 | 优缺点 |
|---|---|---|
| 连续采集 | 流水线实时监控 | 帧率高,但CPU占用大 |
| 软触发 | PLC发指令后拍照 | 节省资源,但延迟略高 |
| 硬触发 | 高速产线(>100个/分钟) | 延迟最低,但需要接线 |
我个人建议:能用硬触发就别用软触发。为什么?因为PLC发指令到相机响应,中间有网络延迟。硬触发是电平信号,微秒级响应。
来看一段连续采集的代码:
// 设置采集模式为连续
IMV_SetAcquisitionMode(hCamera, MV_ACQ_MODE_CONTINUOUS);
// 开始采集
IMV_StartGrabbing(hCamera);
// 循环取图
while (true)
{
IMV_Frame frame = new IMV_Frame();
IMV_GetOneFrameTimeout(hCamera, ref frame, 1000);
if (frame.pBufAddr != IntPtr.Zero)
{
// 将图像数据转为Mat
Mat img = new Mat(frame.nHeight, frame.nWidth, MatType.CV_8UC3, frame.pBufAddr);
// 处理图像...
}
}
注意那个超时参数1000毫秒。如果1秒内没取到图,说明出问题了。我习惯在超时后重新初始化相机,而不是一直等。
4.4 图像处理基础:定位、测量、识别
图像拿到手了,接下来就是重头戏。视觉控制器的核心任务就三个:定位、测量、识别。
4.4.1 定位:找到目标在哪
定位说白了就是找特征。比如找工件上的圆孔、边角、二维码。最常用的方法是模板匹配和找圆。
模板匹配的代码示例:
// 加载模板图像
Mat template = Cv2.ImRead("template.bmp", ImreadModes.Grayscale);
// 在采集图像中匹配
Mat result = new Mat();
Cv2.MatchTemplate(grayImg, template, result, TemplateMatchModes.CCoeffNormed);
// 找到最佳匹配点
Cv2.MinMaxLoc(result, out _, out double maxVal, out _, out Point maxLoc);
if (maxVal > 0.8) // 匹配度阈值
{
Console.WriteLine($"目标位置:({maxLoc.X}, {maxLoc.Y})");
// 画框标记
Cv2.Rectangle(colorImg, new Rect(maxLoc, template.Size()), Scalar.Red, 2);
}
阈值0.8是我常用的值。但不同场景要调。比如光照稳定的实验室,可以设到0.95。产线上光照变化大,0.7可能更合适。
我的经验:
模板匹配最怕旋转。如果工件角度会变,建议用特征点匹配(SIFT/ORB)或者形状匹配。我在一个手机壳定位项目里,就因为没考虑旋转,废了三天时间。
4.4.2 测量:精度是关键
测量就是算尺寸。比如测螺丝直径、测缝隙宽度。核心思路是:先找边缘,再算距离。
举个例子,测量两个圆孔的中心距:
// 找圆
CircleSegment[] circles = Cv2.HoughCircles(
grayImg,
HoughMethods.Gradient,
1, // dp
20, // 最小距离
100, // 高阈值
30, // 低阈值
10, 50 // 半径范围
);
if (circles.Length >= 2)
{
double distance = Math.Sqrt(
Math.Pow(circles[0].Center.X - circles[1].Center.X, 2) +
Math.Pow(circles[0].Center.Y - circles[1].Center.Y, 2)
);
// 像素转毫米(需要标定)
double realDistance = distance * pixelToMmRatio;
Console.WriteLine($"中心距:{realDistance:F2} mm");
}
这里有个关键点:像素转毫米的系数怎么来? 用标定板。拍一张已知尺寸的标定板,算像素数,就能得到比例。我习惯在项目开始时先做标定,后面所有测量都基于这个系数。
注意:
镜头畸变会影响测量精度。如果要求0.1mm以内的精度,必须做畸变校正。OpenCV有现成的函数,别偷懒。
4.4.3 识别:读码和OCR
识别最常见的就是读二维码和OCR字符识别。
读二维码用ZXing库,C#版本叫ZXing.Net:
using ZXing;
BarcodeReader reader = new BarcodeReader();
Result result = reader.Decode(grayImg);
if (result != null)
{
Console.WriteLine($"条码内容:{result.Text}");
Console.WriteLine($"条码类型:{result.BarcodeFormat}");
}
else
{
Console.WriteLine("未识别到条码");
}
OCR的话,我推荐Tesseract。虽然识别率不如商业库,但免费啊。记得训练自己的字体库,通用模型对工业字符识别率不高。
我曾经在一个PCB板字符识别项目里,用通用模型识别率只有60%。后来采集了1000张样本,训练了专用模型,识别率提到了95%。
4.5 本章小结
好,这一章内容不少。从环境搭建到相机初始化,再到图像采集和处理,基本覆盖了视觉控制器端开发的完整流程。
最后说一句:代码写出来只是第一步,调试才是大头。我建议你在开发时,把每一步的中间结果都保存下来。比如模板匹配后的匹配图、边缘检测后的二值图。这样出了问题,一看就知道是哪步错了。
嗯,今天就到这。下一章咱们聊视觉控制器和PLC的通讯实战,到时候会用到本章写的代码。