项目概述
笔者利用下班时间琢磨出一个上位机软件,用以实现较康耐视智能相机(二手康耐视智能相机花费了11600)更低成本的机器视觉解决方案。系虎彩全厂首个源码可控的机器视觉与西门子PLC联动的上位机软件。
功能
将摄像头放置于合适位置后,利用形状检测,通过判断IQOO手机盒的‘5G’标记个数/QR码是否相同来判断天盖的正反并解决混款问题。下位机使用西门子PLC即可。
上位机原理解析
机器视觉算法
形状检测算法实现
在Halcon中调出算法,并导出为C#源码。此时得到的源码并不能直接使用,需手动修改。
public void action2()
{
// Local iconic variables
bool result = false;
if (key == "")
{
System.Windows.Forms.MessageBox.Show("请选择连接字符串");
return;
}
//此处开始为Halcon检测5G标识
// Local iconic variables
HObject ho_Image, ho_ModelRegion, ho_TemplateImage;
HObject ho_ModelContours, ho_TransContours;
// Local control variables
HTuple hv_AcqHandle = new HTuple(), hv_ModelID = new HTuple();
HTuple hv_ModelRegionArea = new HTuple(), hv_RefRow = new HTuple();
HTuple hv_RefColumn = new HTuple(), hv_HomMat2D = new HTuple();
HTuple hv_Row = new HTuple(), hv_Column = new HTuple();
HTuple hv_Angle = new HTuple(), hv_Score = new HTuple();
HTuple hv_I = new HTuple();
//counter1 = int.Parse(hv_I.ToString());
// Initialize local and output iconic variables
HOperatorSet.GenEmptyObj(out ho_Image);
HOperatorSet.GenEmptyObj(out ho_ModelRegion);
HOperatorSet.GenEmptyObj(out ho_TemplateImage);
HOperatorSet.GenEmptyObj(out ho_ModelContours);
HOperatorSet.GenEmptyObj(out ho_TransContours);
//
//Matching 01: ************************************************
//Matching 01: BEGIN of generated code for model initialization
//Matching 01: ************************************************
HOperatorSet.SetSystem("border_shape_models", "false");
//
//Matching 01: Initialize acquisition
hv_AcqHandle.Dispose();
HOperatorSet.OpenFramegrabber("DirectShow", 1, 1, 0, 0, 0, 0, "default", 8, "rgb",
-1, "false", "default", key, 0, -1, out hv_AcqHandle);
//
//Matching 01: Obtain the model image
ho_Image.Dispose();
HOperatorSet.ReadImage(out ho_Image, "E:/工作文件夹/2020.12.16/WIN_20201217_13_55_05_Pro.jpg");
//
//Matching 01: Build the ROI from basic regions
ho_ModelRegion.Dispose();
HOperatorSet.GenRectangle1(out ho_ModelRegion, 1215.17, 1236.49, 1288, 1427.91);
//
//Matching 01: Reduce the model template
ho_TemplateImage.Dispose();
HOperatorSet.ReduceDomain(ho_Image, ho_ModelRegion, out ho_TemplateImage);
//
//Matching 01: Create the shape model
using (HDevDisposeHelper dh = new HDevDisposeHelper())
{
HOperatorSet.CreateShapeModel(ho_TemplateImage, 4, (new HTuple(0)).TupleRad()
, (new HTuple(360)).TupleRad(), (new HTuple(1.5553)).TupleRad(), (new HTuple("none")).TupleConcat(
"no_pregeneration"), "use_polarity", ((new HTuple(10)).TupleConcat(26)).TupleConcat(
6), 4, out hv_ModelID);
}
//
//Matching 01: Get the model contour for transforming it later into the image
ho_ModelContours.Dispose();
HOperatorSet.GetShapeModelContours(out ho_ModelContours, hv_ModelID, 1);
//
//Matching 01: Get the reference position
hv_ModelRegionArea.Dispose(); hv_RefRow.Dispose(); hv_RefColumn.Dispose();
HOperatorSet.AreaCenter(ho_ModelRegion, out hv_ModelRegionArea, out hv_RefRow,
out hv_RefColumn);
hv_HomMat2D.Dispose();
HOperatorSet.VectorAngleToRigid(0, 0, 0, hv_RefRow, hv_RefColumn, 0, out hv_HomMat2D);
ho_TransContours.Dispose();
HOperatorSet.AffineTransContourXld(ho_ModelContours, out ho_TransContours, hv_HomMat2D);
//
//Matching 01: Display the model contours
if (HDevWindowStack.IsOpen())
{
HOperatorSet.DispObj(ho_Image, HDevWindowStack.GetActive());
}
if (HDevWindowStack.IsOpen())
{
HOperatorSet.SetColor(HDevWindowStack.GetActive(), "green");
}
if (HDevWindowStack.IsOpen())
{
HOperatorSet.SetDraw(HDevWindowStack.GetActive(), "margin");
}
if (HDevWindowStack.IsOpen())
{
HOperatorSet.DispObj(ho_ModelRegion, HDevWindowStack.GetActive());
}
if (HDevWindowStack.IsOpen())
{
HOperatorSet.DispObj(ho_TransContours, HDevWindowStack.GetActive());
}
// stop(...); only in hdevelop
//
//Matching 01: END of generated code for model initialization
//Matching 01: * * * * * * * * * * * * * * * * * * * * * * *
//Matching 01: BEGIN of generated code for model application
//
while (true)
{
//
//Matching 01: Obtain the image
ho_Image.Dispose();
HOperatorSet.GrabImage(out ho_Image, hv_AcqHandle);
ho_Image1=ho_Image;
ChangeImage(ho_Image1);
//
//Matching 01: Find the model
using (HDevDisposeHelper dh = new HDevDisposeHelper())
{
hv_Row.Dispose(); hv_Column.Dispose(); hv_Angle.Dispose(); hv_Score.Dispose();
HOperatorSet.FindShapeModel(ho_Image, hv_ModelID, (new HTuple(0)).TupleRad()
, (new HTuple(360)).TupleRad(), 0.78, 0, 0.5, (new HTuple("least_squares")).TupleConcat(
"max_deformation 32"), (new HTuple(4)).TupleConcat(1), 0.75, out hv_Row,
out hv_Column, out hv_Angle, out hv_Score);
}
//
//Matching 01: Transform the model contours into the detected positions
if (HDevWindowStack.IsOpen())
{
HOperatorSet.DispObj(ho_Image, HDevWindowStack.GetActive());
}
for (hv_I = 0; (int)hv_I <= (int)((new HTuple(hv_Score.TupleLength())) - 1); hv_I = (int)hv_I + 1)
{
hv_HomMat2D.Dispose();
HOperatorSet.HomMat2dIdentity(out hv_HomMat2D);
using (HDevDisposeHelper dh = new HDevDisposeHelper())
{
HTuple ExpTmpOutVar_0;
HOperatorSet.HomMat2dRotate(hv_HomMat2D, hv_Angle.TupleSelect(hv_I), 0, 0,
out ExpTmpOutVar_0);
hv_HomMat2D.Dispose();
hv_HomMat2D = ExpTmpOutVar_0;
}
using (HDevDisposeHelper dh = new HDevDisposeHelper())
{
HTuple ExpTmpOutVar_0;
HOperatorSet.HomMat2dTranslate(hv_HomMat2D, hv_Row.TupleSelect(hv_I), hv_Column.TupleSelect(
hv_I), out ExpTmpOutVar_0);
hv_HomMat2D.Dispose();
hv_HomMat2D = ExpTmpOutVar_0;
}
ho_TransContours.Dispose();
HOperatorSet.AffineTransContourXld(ho_ModelContours, out ho_TransContours,
hv_HomMat2D);
if (HDevWindowStack.IsOpen())
{
HOperatorSet.SetColor(HDevWindowStack.GetActive(), "green");
}
if (HDevWindowStack.IsOpen())
{
HOperatorSet.DispObj(ho_TransContours, HDevWindowStack.GetActive());
}
//stop ()
}
counter1 = int.Parse(hv_I.ToString());
//GC.Collect();
}
//
//Matching 01: *******************************************
//Matching 01: END of generated code for model application
//Matching 01: *******************************************
//
}
QR码算法实现
public void action3()
{
// Local iconic variables
bool result = false;
if (key == "")
{
System.Windows.Forms.MessageBox.Show("请选择连接字符串");
return;
}
//此处开始为Halcon检测QR码
// Local iconic variables
HObject ho_Image = null, ho_SymbolXLDs = null;
// Local control variables
HTuple hv_AcqHandle = new HTuple(), hv_DataCodeHandle = new HTuple();
HTuple hv_ResultHandles = new HTuple(), hv_DecodedDataStrings = new HTuple();
// Initialize local and output iconic variables
HOperatorSet.GenEmptyObj(out ho_Image);
HOperatorSet.GenEmptyObj(out ho_SymbolXLDs);
//Image Acquisition 01: Code generated by Image Acquisition 01
hv_AcqHandle.Dispose();
HOperatorSet.OpenFramegrabber("DirectShow", 1, 1, 0, 0, 0, 0, "default", 8, "rgb",
-1, "false", "default", "[0] WEB CAM", 0, -1, out hv_AcqHandle);
HOperatorSet.GrabImageStart(hv_AcqHandle, -1);
while (true)
{
ho_Image1 = ho_Image;
ChangeImage(ho_Image1);
ho_Image.Dispose();
HOperatorSet.GrabImageAsync(out ho_Image, hv_AcqHandle, -1);
ho_Image1 = ho_Image;
ChangeImage(ho_Image1);
//Image Acquisition 01: Do something
hv_DataCodeHandle.Dispose();
HOperatorSet.CreateDataCode2dModel("QR Code", new HTuple(), new HTuple(), out hv_DataCodeHandle);
ho_SymbolXLDs.Dispose(); hv_ResultHandles.Dispose(); hv_DecodedDataStrings.Dispose();
HOperatorSet.FindDataCode2d(ho_Image, out ho_SymbolXLDs, hv_DataCodeHandle,
new HTuple(), new HTuple(), out hv_ResultHandles, out hv_DecodedDataStrings);
StringQR = hv_DecodedDataStrings.ToString();
//ch1 = StringQR.ToCharArray();
}
HOperatorSet.CloseFramegrabber(hv_AcqHandle);
}
大部分代码为Halcon导出。
下位机控制
使用西门子的S7.dll即可实现PLC的连接。首先初始化连接。
Plc plc = new Plc(CpuType.S71200 , "192.168.1.100", 0, 1);//设置默认PLC连接
private void button3_Click(object sender, EventArgs e)
{
if ((comboBox2.Text != "") && (textBox2.Text != "") )//判断PLC类型、IP地址、插槽 机架信息
{
string ip = textBox2.Text;//PLC IP地址
short jijiahao = 0;// Convert.ToInt16(textBox2.Text);//PLC机架号
short chacaohao =1;//Convert.ToInt16(textBox3.Text);//PLC插槽号
switch (comboBox2.Text)
{
case "Siemens S7-200SMART":
plc = new Plc(CpuType.S71200 , ip, jijiahao, chacaohao);
break;
default: break;
}
try
{
plc.Open();
if (plc.IsConnected)//判断PLC链接是否成功
{
timer2.Enabled = true;
MessageBox.Show("已连接到PLC!");
}
else
{
MessageBox.Show("PLC连接不成功,请检查PLC类型、IP地址是否正确!!!");
}
}
catch
{
MessageBox.Show("PLC连接不成功,请检查PLC类型、IP地址是否正确!!!");
}
}
}
断开连接使用如下方法。
private void button4_Click(object sender, EventArgs e)
{
plc.Close();
if (!plc.IsConnected)
{
timer2.Enabled = false;
MessageBox.Show("已断开连接!!");
}
else
{
MessageBox.Show("PLC断开连接失败!!!");
}
}
手动功能部分。
private void button5_MouseDown(object sender, MouseEventArgs e)
{
Image Image1;
Image Image2;
Image1 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-绿.png");//绿色代表低电平
Image2 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-红.png");//红色代表高电平
pictureBox1.Image = Image2;
if (plc.IsConnected)
plc.Write("M10.0", 1);
}
private void button5_MouseUp(object sender, MouseEventArgs e)
{
Image Image1;
Image Image2;
Image1 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-绿.png");//绿色代表低电平
Image2 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-红.png");//红色代表高电平
pictureBox1.Image = Image1;
if (plc.IsConnected)
plc.Write("M10.0", 0);
}
private void button6_MouseUp(object sender, MouseEventArgs e)
{
Image Image1;
Image Image2;
Image1 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-绿.png");//绿色代表低电平
Image2 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-红.png");//红色代表高电平
pictureBox2.Image = Image1;
if (plc.IsConnected)
plc.Write("M10.1", 0);
}
private void button6_MouseDown(object sender, MouseEventArgs e)
{
Image Image1;
Image Image2;
Image1 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-绿.png");//绿色代表低电平
Image2 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-红.png");//红色代表高电平
pictureBox2.Image = Image2;
if (plc.IsConnected)
plc.Write("M10.1", 1);
}
手动/自动模式切换。
private void button7_Click(object sender, EventArgs e)
{
if (!PlcAutoMode)
{
button7.Text = "自动模式";
MessageBox.Show("PLC启用自动模式");
PlcAutoMode = true;
button5.Enabled = false;
button6.Enabled = false;
timer2.Enabled = true;
}
else
{
button7.Text = "手动模式";
MessageBox.Show("PLC启用手动模式");
timer2.Enabled = false;
PlcAutoMode = false;
button5.Enabled = true;
button6.Enabled = true;
}
}
控制下位机,这里我选用M10.0与M10.1这2个中间继电器。
private void timer2_Tick(object sender, EventArgs e)
{
Image Image1;
Image Image2;
Image1 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-绿.png");//绿色代表低电平
Image2 = new Bitmap(@"E:\work\技术攻关\C#运动控制卡\练习程序\2020.11.15\运动控制卡~练习\Resources\灯-红.png");//红色代表高电平
if (plc.IsConnected)
label8.Text = "已连接";
else
label8.Text = "未连接";
if (textBox1.Text == "0")
{
pictureBox1.Image = Image2;
pictureBox2.Image = Image1;
if (plc.IsConnected)
{
plc.Write("M10.0", 1);
plc.Write("M10.1", 0);
}
MessageBox.Show("正扣");
}
else if (textBox1.Text == "1")
{
pictureBox1.Image = Image1;
pictureBox2.Image = Image2;
if (plc.IsConnected)
{
plc.Write("M10.0", 0);
plc.Write("M10.1", 1);
}
MessageBox.Show("反扣");
}
else
{
pictureBox1.Image = Image1;
pictureBox2.Image = Image1;
if (plc.IsConnected)
{
plc.Write("M10.0", 0);
plc.Write("M10.1", 0);
}
}
}
上位机其它功能
窗口图像缩放功能实现
private void hView_HMouseWheel(object sender, HMouseEventArgs e) //滚轮上下实现缩放
{
HTuple WindowID;
WindowID = hView.HalconWindow;
HTuple Zoom, Row, Col, Button;
HTuple Row0, Column0, Row00, Column00, Ht, Wt, r1, c1, r2, c2;
if (e.Delta > 0)
{
Zoom = 1.5;
MessageBox.Show("放大显示");
}
else
{
Zoom = 0.5;
MessageBox.Show("缩小显示");
}
HOperatorSet.GetMposition(WindowID, out Row, out Col, out Button);
HOperatorSet.GetPart(WindowID, out Row0, out Column0, out Row00, out Column00);
Ht = Row00 - Row0;
Wt = Column00 - Column0;
if (Ht * Wt < 32000 * 32000 || Zoom == 1.5)//普通版halcon能处理的图像最大尺寸是32K*32K。如果无限缩小原图像,导致显示的图像超出限制,则会造成程序崩溃
{
r1 = (Row0 + ((1 - (1.0 / Zoom)) * (Row - Row0)));
c1 = (Column0 + ((1 - (1.0 / Zoom)) * (Col - Column0)));
r2 = r1 + (Ht / Zoom);
c2 = c1 + (Wt / Zoom);
HOperatorSet.SetPart(WindowID, r1, c1, r2, c2);
HOperatorSet.ClearWindow(WindowID);
// HOperatorSet.DispObj(ho_Image, WindowID);
}
}
功能调用
private void Halcon_ChangeImage(HalconDotNet.HObject obj)
{
HOperatorSet.DispObj(obj, hView.HalconWindow);
}
private void btnGetCamera_Click(object sender, EventArgs e)
{
comboBox1.Items.Clear();
string[] camera = halcon.GetCamera();
if (camera != null)
comboBox1.Items.AddRange(camera);
}
private void button2_Click(object sender, EventArgs e) //检测5G标识
{
if (comboBox1.Text == "")
return;
halcon.Close();
System.Threading.Thread.Sleep(200);
halcon.key = comboBox1.Text;
Task task = new Task(halcon.action2);
timer1.Enabled = true;
task.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
textBox1.Text =halcon.counter1.ToString();
textBox4.Text =halcon.StringQR;
if (textBox4.Text==textBox3.Text)
label11.Text = "相等";
else
label11.Text = "不相等";
}
下位机及执行机构
下位机使用西门子PLC,S7-200SMART ST20进行实验,通讯功能正常。
使用PLC带24V继电器即可控制气缸及SCARA驱控一体机,使得执行元件做出对应的动作。
DEMO效果展示
Halcon测试5G标识
Halcon QR码检测
尾言
上传大部分代码及测试效果供读者学习。由于代码全部为笔者在下班时间编写,完整源码可直接服务于生产,为避免侵权嫌疑,暂不提供完整源码。