VisionPro联合编程相机拍照 九点标定实战
前言
机器视觉标定,也就是通常说的“手眼标定”,相机拍照的图像是基于像素坐标,而机器人用的是空间坐标系,所以手眼标定就是得到像素坐标系和机器人空间坐标系两者之间的坐标转换关系。
手眼标定的作用是,建立相机坐标系和机器人坐标系之间的变换关系,即给机器人装上眼睛,让它去哪就去哪。
为了确定像素坐标系和空间机械手坐标系的转换关系,我们介绍一种工业上广泛使用的二维手眼标定方法——九点标定法。在实际运动中,相机检测到目标在图像中的像素位置后,通过标定好的坐标转换矩阵,将相机的像素坐标,变换到机械手的空间坐标系中,然后根据机械手坐标系,计算出各个电机该如何运动,从而控制机械手到达指定位置。
九点标定就是通过九个点计算出像素坐标系到机械手坐标系下的一个仿射变换,(实际上空间中的二维平面的仿射变换只需要3个点就足够了)。在实际应用过程中,需要获取像素下特征点的坐标和对应机械手的坐标。联立方程组求解即可得到对应仿射变换的矩阵,实际应用场景主要分为眼在手上和眼在手外。
九点标定是常用的标定方法,在区域中均匀设定9个标定点,让机器人的工具末端去走这9个点得到在机器人坐标系中的坐标,同时用相机识别9个点得到像素坐标。这样就得到了9组对应的坐标,把数据代入到上述的公式中就能反推出转换系数,解得转换矩阵。
九点标定法的本质: 无需进行相机内参标定,只能识别x,y坐标,属于2D平面标定,在标定过程中z是未知的。
实现效果: 对相机拍下的物品通过图像像素坐标转换物理空间坐标 三个窗体分别是对拍的照片 找到他的像素坐标 在去九点标定工具添加九个点的像素坐标 在添加物理坐标 在计算RMS值 低于10以下就可以
Form1代码展示
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Cognex.VisionPro;
using Cognex.VisionPro.ToolBlock;
using Microsoft.Win32;namespace 九点标定_像素坐标转换为物理坐标
{public partial class Form1 : Form{public Form1(){InitializeComponent();}CogToolBlock tb1;CogToolBlock tb2;CogAcqFifoTool acqFifoTool;ICogAcqFifo acq { get { return acqFifoTool.Operator; } }private void 未校正TBlockToolStripMenuItem_Click(object sender, EventArgs e){Not_corrected not_Corrected = new Not_corrected(tb1);not_Corrected.Show();}private void 已校正TBlockToolStripMenuItem_Click(object sender, EventArgs e){double x = (double)tb1.Outputs["X"].Value;double y = (double)tb1.Outputs["Y"].Value;Corrected corrected = new Corrected(tb2 ,x,y);corrected.Show();}ICogImage image;private void Form1_Load(object sender, EventArgs e){//tb1 = CogSerializer.LoadObjectFromFile(@"未校正的坐标.vpp") as CogToolBlock; 9-1实验室图片//tb2 = CogSerializer.LoadObjectFromFile(@"已校正的坐标.vpp") as CogToolBlock;try{tb1 = CogSerializer.LoadObjectFromFile(@"未校正的积木坐标.vpp") as CogToolBlock;tb2 = CogSerializer.LoadObjectFromFile(@"已校正的积木坐标.vpp") as CogToolBlock;acqFifoTool = CogSerializer.LoadObjectFromFile(@"相机配置.vpp") as CogAcqFifoTool;}catch (Exception ex){MessageBox.Show("加载相机配置文件时出错:" + ex.Message);}}public CogImage8Grey myImage;private void Acq_Complete(object sender, CogCompleteEventArgs e){try{int numPending, numReady; //等待状态,准备状态bool isBusy;//繁忙状态//获取当前相机的状态acq.GetFifoState(out numPending, out numReady, out isBusy);if (numReady > 0) // 判断相机是否准备好{CogAcqInfo info = new CogAcqInfo();// 创建acqinfo对象ICogImage image = acq.CompleteAcquireEx(info); //获取图像cogRecordDisplay1.Image = image as CogImage8Grey;//显示图片myImage = image as CogImage8Grey;// 赋值全局图片tb1.Inputs["OutputImage"].Value = image;tb1.Run();tb2.Inputs["OutputImage"].Value = image;tb2.Run();ICogRecord record = tb2.CreateLastRunRecord().SubRecords[0];cogRecordDisplay1.Record = record;cogRecordDisplay1.Fit();}}catch (Exception){throw new Exception("采集信息失败");}}private void button1_Click(object sender, EventArgs e){if (openFileDialog1.ShowDialog() == DialogResult.OK){// 读取图像文件为位图对象Bitmap bitmap = new Bitmap(openFileDialog1.FileName);// 将位图对象转换为VisionPro的图像对象image = new CogImage8Grey(bitmap);// 设置ToolBlock的输入图像为当前选中的图像tb1.Inputs["OutputImage"].Value = image;// 将图像显示到图像控件cogRecordDisplay1.Image = image;cogRecordDisplay1.Fit();//ICogRecord record = tb2.CreateLastRunRecord().SubRecords[0];//cogRecordDisplay1.Record = record;}}private void Form1_FormClosing(object sender, FormClosingEventArgs e){CogSerializer.SaveObjectToFile(tb1,@"未校正的坐标.vpp");CogSerializer.SaveObjectToFile(tb2,@"已校正的坐标.vpp");CogSerializer .SaveObjectToFile(acqFifoTool, @"相机配置.vpp");acq?.FrameGrabber?.Disconnect(false); //false就是不创建新的}private void button2_Click(object sender, EventArgs e){if (cogRecordDisplay1.Image == null && openFileDialog1.ShowDialog() == DialogResult.OK){// 读取图像文件为位图对象Bitmap bitmap = new Bitmap(openFileDialog1.FileName);// 将位图对象转换为VisionPro的图像对象image = new CogImage8Grey(bitmap);// 设置ToolBlock的输入图像为当前选中的图像}tb2.Inputs["OutputImage"].Value = image;ICogRecord record = tb2.CreateLastRunRecord().SubRecords[2];cogRecordDisplay1.Record = record;cogRecordDisplay1.Fit();}private void button3_Click(object sender, EventArgs e){acq.StartAcquire();cogRecordDisplay1.Fit();acq.Complete += Acq_Complete;}private void 相机配置ToolStripMenuItem_Click(object sender, EventArgs e){Acq acq = new Acq(acqFifoTool);acq .Show();}}
}
未校正窗体 也就是像素坐标
已校正坐标也就是使用了九点标定工具 如下 这样得出的图片是基于物理坐标的 已经通过图像像素坐标转换和物理坐标转换
代码展示
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Cognex.VisionPro.CalibFix;
using Cognex.VisionPro.ToolBlock;namespace 九点标定_像素坐标转换为物理坐标
{public partial class Corrected : Form{public Corrected(){InitializeComponent();}public CogToolBlock toolBlock;double x1;double y2;public Corrected(CogToolBlock cogToolBlock,double x,double y):this(){this.toolBlock = cogToolBlock;x1 = x;y2 = y;cogToolBlockEditV21.Subject=cogToolBlock;}private void button1_Click(object sender, EventArgs e){CogCalibNPointToNPointTool nPointTool = toolBlock.Tools["CogCalibNPointToNPointTool1"] as CogCalibNPointToNPointTool;// double outX = Convert.ToDouble(toolBlock.Inputs["X"].Value);//double outY = Convert.ToDouble(toolBlock.Inputs["Y"].Value);nPointTool.Calibration.AddPointPair(x1, y2, 0,0);}private void button2_Click(object sender, EventArgs e){CogCalibNPointToNPointTool nPointTool = toolBlock.Tools["CogCalibNPointToNPointTool1"] as CogCalibNPointToNPointTool;try{// 计算矫正nPointTool.Calibration.Calibrate();MessageBox.Show("计算完成,RMS误差:" + nPointTool.Calibration.ComputedRMSError);}catch (Exception ex){MessageBox.Show("计算失败:" + ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error);}}}
}