ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LabVIEW与Halcon联合开发:九点标定完整实战指南

LabVIEW与Halcon联合开发:九点标定完整实战指南 做机器视觉项目的人早晚会遇到一个很尴尬的场景程序写完了画面也调出来了Halcon找圆一找一个准LabVIEW界面上数据显示也飞一样可一到机器人抓取就歪三扭四。我去年做一套三轴平台上料定位的项目就卡在这个问题上一个多星期。当时以为是视觉算法的问题反反复复试了各种找边、拟合、匹配的办法结果最后才发现真正出问题的不是“看见”而是“看见之后怎么告诉机器人目标在哪里”——也就是LabVIEW与Halcon联合开发里最基础也最重要的环节九点标定。这篇文章就把我实际搭建整套系统时用到的九点标定流程完整写一遍包括原理、Halcon脚本、DLL封装、LabVIEW调用、精度验证以及我踩过的几个坑。如果你是正准备用手眼系统做定位或者已经在做但标定误差总差那么零点几毫米这篇内容应该能帮你少走不少弯路。1. 九点标定到底在解决什么问题1.1 两套坐标系之间的“翻译官”视觉系统和运动系统各说各话。相机看到的是一张图像Halcon处理之后给出的坐标单位是像素——也就是这张图像的第几行第几列即Row和Column。而机器人和运动平台走的是世界坐标单位是毫米通常是X、Y轴上的实际物理位置。这两套坐标系如果不打通就会出现我在项目里遇到的情况算法在图像里找到的特征点明明在正中央可机器人收到的坐标却指向了别的方向。九点标定的本质就是通过若干组“像素坐标机器人坐标”的对应点对计算出一个变换矩阵把这个像素坐标翻译成机器人坐标系下的位置。1.2 9个点背后的数学原理最常见的标定模型是仿射变换。它假设两幅平面之间的映射关系可以由一个2×3矩阵描述公式非常简单X机器人 a * Column b * Row c Y机器人 d * Column e * Row f也就是说我们需要求解a、b、c、d、e、f这6个未知数。理论上只要采集3个不共线的点就能解出来。但实际情况中找圆、提取特征点都存在微小的测量噪声3个点算出来的矩阵对噪声异常敏感稍微有点偏差映射结果就会跑偏。所以我们铺了9个点均匀覆盖整个视野。9组方程去解6个未知数属于超定方程组用最小二乘拟合可以把随机误差平均掉一部分算出来的矩阵更稳。这也是“九点标定”这个名字的由来——不是必须9个点而是9个点在稳定性和操作耗材之间比较平衡。用4、5、12个点也可以但9个是最常见的工程实践。在Halcon中我们不需要手动解最小二乘有个现成算子叫vector_to_hom_mat2d输入两组点坐标数组直接输出齐次变换矩阵之后用affine_trans_point_2d做坐标转换就可以。1.3 为什么是九点而不是“三点”或“十二点”三点标定速度快几秒钟就能做完但它对单点提取误差几乎没有容错能力十二点或更多点精度理论上会更稳可标定过程又太费时间而且需要更大的标定行程并不是每个设备都有那么大的工作空间。九点配合3×3的网格布局既能覆盖视野的四个角落和中心又能保证数据量足以压制噪声对大多数固定相机的2D定位场景来说是一个性价比很高的选择。这里我建议你记住一个原则参与标定的9个点一定要铺满实际工作范围。如果9个点挤在视野中间一小块区域而实际抓取位置在视野边缘定位就会逐渐偏出去因为仿射变换模型在外推时误差会被放大。后文我还会专门讲到这个坑。2. LabVIEW和Halcon联动的三种搭建方式2.1 最省事的路径传参方案Halcon原生没有提供给LabVIEW的直接接口所以LabVIEW和Halcon的联合本质上是把Halcon算法封装成DLL再在LabVIEW里通过“调用库函数节点”去加载执行。最开始的阶段我建议新手使用“路径传参”的方式即LabVIEW把图像保存成PNG或BMP然后把文件路径传给DLLDLL内部用Halcon的read_image读图处理完再把坐标返回。这种方式虽然落盘读写但优点是调试起来非常直观哪里有问题直接把图片打开看就行内存管理的风险也小。典型代码如下extern C __declspec(dllexport) int GetPointPos(char* imgPath, double* row, double* col) { HalconCpp::HImage image; image.ReadImage(imgPath); HalconCpp::HObject circle; HalconCpp::HTuple hvRow, hvCol, hvRadius; HalconCpp::FindCircle(image, circle, 100, 100, positive, 10, 30, 1.0, hvRow, hvCol, hvRadius); *row hvRow[0].D(); *col hvCol[0].D(); return 0; }在LabVIEW里添加“调用库函数”节点时库里选择这个DLL函数名选择GetPointPos参数里第一个字符串选“C String Pointer”后面两个double参数要配置为“数值”并勾选“指针”这样DLL修改的坐标值才能传回LabVIEW。如果调用约定是__cdecl记得在配置窗口里选“C”而不是“Stdcall”否则会报崩溃或返回无效值。2.2 工业项目里更稳的内存共享方案路径传参适合原型验证但工业现场通常等不了那几十毫秒的存盘读盘时间。更稳的做法是LabVIEW采集到的图像数据不落盘直接把图像内存传给DLL在DLL中用Halcon的GenImage1算子重新生成Halcon图像再做找点处理。C侧的函数签名长这样extern C __declspec(dllexport) int ProcessImageU8(unsigned char* data, int width, int height, double* row, double* col) { HalconCpp::HObject img; HalconCpp::GenImage1(img, byte, width, height, (Hlong)data); HalconCpp::HObject circle; HalconCpp::HTuple hvRow, hvCol, hvRadius; HalconCpp::FindCircle(img, circle, 100, 100, positive, 10, 30, 1.0, hvRow, hvCol, hvRadius); *row hvRow[0].D(); *col hvCol[0].D(); return 0; }LabVIEW侧的做法是先用图像处理函数把IMAQ图像或相机采集到的灰度图转换为U8一维数组比如用IMAQ ImageToArray再把数组指针传给DLL。这里需要注意如果采集的是彩色图像需要先转成8位灰度图否则通道排列会让图像错乱。这个方案真正跑起来之后单帧图像从采集到输出坐标的耗时能从几十毫秒降到十几毫秒甚至更低而且不用担心磁盘文件残留。我第一次把项目从路径读取切到内存共享时节拍时间直接缩短了将近三分之一生产线上这一块的价值立刻体现出来了。2.3 环境配置的几个常见卡点不管用哪种联动方式环境配置都是绕不开的坎。我这几年的经验总结成几条位数必须一致。LabVIEW是64位的DLL就必须编译成64位Halcon也要安装64位运行时混用32位和64位会直接加载失败。环境变量和依赖路径。Halcon在安装时会自动配置环境变量但有些精简安装或便携版会漏掉。DLL加载失败时先用Dependencies工具检查那个DLL依赖的halcon.dll、halconcpp.dll等文件能不能被找到。开发机上License要提前确认。Halcon的运行时需要合法的授权文件开发调试时如果License失效算子可能会直接报异常或弹窗。我习惯每两三天检查一次授权状态免得调试到一半突然罢工。工程路径不要有中文和特殊符号。老版本Halcon对中文路径支持不好虽然新版好一些但规避风险的成本很低统一用英文路径最省心。另外如果你发现LabVIEW启动时卡在启动界面很慢这通常和联合开发本身没关系多半是NI授权服务、杀毒软件扫描或者显卡驱动冲突的问题别在标定工程里去排查那是另外一个方向了。3. 从Halcon找点到LabVIEW算矩阵的完整流程3.1 标定前要准备什么先明确一点九点标定不一定非要用专业的标定板。我当时用的就是一块PCB上的圆孔只要特征点对比度高、边缘锐利、形状规则都可以当标定点。圆孔比实心圆点更好因为找圆算法对光照变化的鲁棒性更高亚像素定位也更稳定。在Halcon里处理圆孔最合适的算子是find_circle也就是很多人常说的“halcon找圆”。标定前把相机固定好相机光轴尽量垂直于工作平面然后把特征点移动到视野中的9个位置按3×3网格布局9个点尽量铺满整个视野。操作顺序建议先把设备走到标定范围的左下起始位置设为起点沿X方向走固定步距采第一行3个点回到起始点的X位置Y方向走一步采第二行3个点重复直到凑齐9个点。每个点都要记录两组坐标机器人当前的X、Y命令值以及Halcon找出来的像素Row和Column。3.2 Halcon提取Mark点的脚本标定采图阶段Halcon侧的核心脚本不复杂。假设我已经把9张图命名为calib_1.png到calib_9.png脚本可以这样写Rows : [] Cols : [] for I : 1 to 9 by 1 read_image (Image, calib_ I .png) find_circle (Image, Circle, 100, 100, positive, 10, 30, 1.0, Row, Column, Radius) if (Circle 1) tuple_concat (Rows, Row, Rows) tuple_concat (Cols, Column, Cols) endif endforfind_circle后面的参数分别是搜索中心行列、极性positive表示找暗背景下的亮圆或反过来要看具体配置、半径大致范围、边缘阈值等。如果画面里特征区域大致位置固定给个小搜索窗口就行速度也更快。如果圆孔边缘有毛刺、反光或者对比度不稳定别硬上find_circle可以先做高斯滤波或者用edges_sub_pix提取亚像素轮廓再用fit_circle_contour_xld拟合圆这样得到的圆心坐标更稳。表面反光导致边缘偏移是高频问题我后面会再详细说。另外调试时有个很实用的小技巧在Halcon的图像窗口里把当前点的编号写在特征旁边方便核对采集顺序没乱。disp_message (WindowHandle, I, image, Row - 30, Column - 20, black, true)3.3 标定数据采集和DLL调用怎么衔接如果是离线标定直接按3.2的方式把数据存下来就够。但如果在LabVIEW里做半自动化标定流程应该是这样的LabVIEW通过运动控制卡让平台移动到第1个位置读取平台到位信号而不是盲等延时然后触发相机拍照将图像数据传给Halcon DLLDLL返回Mark点的Row和ColumnLabVIEW把当前的机器人坐标和像素坐标写入CSV文件循环9次。这里最重要的是“等待到位信号”。我见过很多新手用延时100ms这种土办法结果平台还没停稳照片就拍了同一个机械位置拍出来的像素坐标每次都差好几个像素标定矩阵自然怎么算都不准。改成读取运动控制卡的到位状态之后重复性从0.4毫米直接降到了0.02毫米以内。3.4 标定矩阵的计算与在线转换数据齐了之后把9组点对喂给Halcon的vector_to_hom_mat2d。这里有个极其容易被搞混的细节Halcon算子的参数顺序是源坐标在前目标坐标在后。如果我们把图像坐标作为源机器人坐标作为目标那么调用时先传像素Column再传像素Row然后传机器人X最后传机器人Yvector_to_hom_mat2d (Cols, Rows, RobotXs, RobotYs, HomMat2D)注意看参数顺序是“列在前、行在后”跟传统的“先行后列”习惯正好相反。我第一次联调就栽在这个顺序上算出来的矩阵看起来也能用但到了现场一跑就发现坐标映射全乱。在线运行时每来一个产品相机拍照、Halcon找点然后执行affine_trans_point_2d (HomMat2D, Column, Row, RobotX, RobotY)得到的RobotXRobotY就是可以直接发给运动控制的目标坐标。如果产品方向不确定还需要找第二个特征点或者边缘方向来计算角度角度可以传给机器人端做旋转补偿。九点标定解决的是位置问题角度补偿是另一个话题这里先点一句。4. 精度验证与误差排查全记录4.1 用重投影误差判断标定质量标定完成不等于标定正确。我自己有个铁律参与标定的9个点不能用来自我验证那是自欺欺人。正确做法是额外再采一组验证点比如10个不在那9个位置上的新点拍照找像素坐标然后通过矩阵反算出一组机器人坐标跟实际机器人坐标做差。逐个点算误差distance_pp (RobotXActual, RobotYActual, RobotXCalc, RobotYCalc, Distance)最后计算均方根误差。以我实际项目的经验这个RMSE控制在0.1mm以内算优秀0.1~0.3mm可以接受超过0.5mm就必须排查问题了问题一般不在算法而在标定过程中的某些环节。如果误差有规律比如视野中心小、边缘越来越大大概率是镜头畸变或相机安装不垂直于工作平面如果误差整体往一个方向飘优先怀疑坐标轴方向搞反了。4.2 我踩过的三个坑第一个坑是行列对应关系搞反。机器人X方向为正时图像中的Mark点Row和Column到底怎么变很多人想当然。我的排查办法很笨但很有效让机器人沿X正方向移动10mm拍一张图看像素坐标变化方向再沿Y正方向移动10mm再看一次。用数据确定对应关系而不是靠主观记忆。这一步只要做一次就能避免后面所有坐标错乱。第二个坑是标定范围小于工作范围。之前提过9个点标定在视野中间30%的区域结果边缘抓取时偏置越来越大。这是因为仿射矩阵在已知数据范围之外外推时微小误差会被放大。后来我把9个点铺满整个视野让标定范围覆盖实际工作区域大一圈问题立刻消失。第三个坑是光源和反光干扰。项目里有个阶段同一个圆孔在同一个位置找圆出来的中心重复测量差了0.3mm。排查到最后发现是光源亮度过曝圆孔边缘高光溢出一侧导致亚像素中心往亮侧偏移。把光源调暗或者加偏振片之后重复性恢复到0.02mm以内。所以标定Repeatability如果变差先检查成像质量不要急着改标定算法。4.3 用Halcon测量算子复核标定除了重投影误差我还会做一步额外的复核用Halcon的测量算子来验证标定后的尺寸是否准确。比如找一块已知边长的矩形工件用edges_sub_pix抓边再fit_line_contour_xld拟合直线计算两条平行线之间的距离和实际用卡尺量出来的尺寸对比。这就是为什么热搜词里总有“halcon测量”、“视觉halcon抓边拟合直线方式”的原因——九点标定直接影响后面所有测量的准确性。如果在未标定的像素坐标系里看到一条边是100个像素你没法判断它物理上到底是1mm还是10mm标定之后像素距离换算成世界坐标单位测量才真正有意义。5. 九点标定和标定板标定到底怎么选5.1 两者的数学本质差异“标定板标定和九点标定的区别”是我见过最多的搜索词之一也是新手最容易混淆的概念。九点标定解决的是“像素坐标到机器人坐标”的变换它不区分这个误差是来自镜头畸变还是相机倾斜而是把这所有偏差都一股脑塞进仿射矩阵而标定板标定解决的是“相机本身”的问题——通过拍摄标定板上的已知特征求解相机内参焦距、主点、畸变系数和外参相机相对标定板的位姿。Halcon中可以用create_caltab生成标定板描述文件配合find_caltab和camera_calibration算子完成这一过程。一句话总结九点标定是外部的坐标映射标定板标定是内部的光学标定两者解决的问题不同。5.2 高精度场景的配合方案如果项目对精度要求很高或者镜头畸变明显、相机安装有一定倾角我惯用的做法是两步走先用标定板做畸变校正得到校正后的图像在已校正图像上再做九点标定。这样镜头的径向畸变、切向畸变问题被前面的标定板流程消化掉九点标定只负责像素坐标到机器人坐标的线性变换误差会小很多。在大视野搭配广角镜头的项目里这个组合效果尤其明显如果只是常规固定相机、变焦很小的镜头直接九点标定通常也够。5.3 一个快速的决策清单我给自己定了几个判断标准遇到项目先过一遍相机是否固定安装且光轴垂直工作平面是优先九点。镜头畸变是否明显不大优先九点。是否需要测量工件的物理尺寸需要建议先标定板。目标在Z方向是否有较大变化有标定板3D测量方案。精度要求是否在0.1mm以下是标定板九点配合。相机是否安装在机器人手上随动这种情况已经不属于九点标定而是手眼标定需要结合标定板求外参。如果你刚接触视觉系统这个清单能帮你快速判断该往哪个方向投入精力。6. 给初次搭建这套系统的落地建议最后分享几条实操层面的建议都是我在项目里磨出来的。第一先在Halcon环境里把九点标定完整跑通再开始写LabVIEW联动的DLL。你可以先用仿真数据或者离线图片验证vector_to_hom_mat2d、affine_trans_point_2d这两个算子的参数顺序和计算结果换了顺序、搞清了行列关系再上DLL封装效率会高很多。第二在LabVIEW端把标定做成一个独立的一键标定VI同时把每次标定的9组数据、验证点误差记录到CSV文件。这样设备用了一段时间后如果发现定位精度漂移你可以调出历史记录看到底是哪个点的数据在变还是整个矩阵已偏移。第三养成“物理环境变化就重新标定”的习惯。换镜头、换相机、换光源位置、动过工装都要重新走一遍九点标定流程。很多现场问题看起来是算法随机跳变实际是标定矩阵和当前物理状态已经不匹配了。我在实际使用中体会最深的一点是九点标定不是一次性的调试步骤它应该是整套视觉系统里最核心、也最应该被规范化的一个环节。把这一步做好了后面的Halcon找圆、LabVIEW上位机界面、运动控制配合都会一下顺畅起来。
返回列表