Halcon与C#联合编程(一):使用S7协议实现机器视觉上位机与西门子PLC联动

项目概述

笔者利用下班时间琢磨出一个上位机软件,用以实现较康耐视智能相机(二手康耐视智能相机花费了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码检测

尾言

上传大部分代码及测试效果供读者学习。由于代码全部为笔者在下班时间编写,完整源码可直接服务于生产,为避免侵权嫌疑,暂不提供完整源码。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值