使用Qt+OpenCV+海康相机SDK实现一个图像处理上位机并与汇川PLC进行TCP通讯的Demo

      先前出于工作需要,接触学习了一下视觉上位机的制作,了解到一般都是使用C#+halcon或者OpenCVSharp,本人基本没有接触过C#,对C++和OpenCV倒是有一点认识,于是尝试用Qt实现一下简易的视觉上位机,但碍于CSDN,Github等社区上学习资料杂乱不堪,学习难度大,故本文综合了一下这些资料,分享一下我自己的学习经验,代码不全,主要演示思路

本文主要分为:

1.OpenCV,海康相机SDK环境配置,SDK二次封装

2.UI界面功能板块介绍

2.项目代码,实现逻辑

一.OpenCV与海康相机SDK配置,SDK二次封装

      这部分内容网上已经有很多教程了,如果使用Qt自带的Mingw编译器,就可以下载Cmake来编译一次OpenCV,这个可以移步B站去找教程,如果使用MSVC编译器的话,就直接在项目文件中引入外部库即可,同样也有教程,请读者自行查阅,这里不再赘述

      引入海康相机的SDK需要去官网下载MVS软件,下载后在文件夹内找到的development文件,将includes下的.h文件和lib文件下的.lib文件(WIN32或者WIN64文件下)复制到pro文件同目录下即可完成配置,注意,这时点击构建的时候有可能出现未定义__int64 错误,这是由于在PixelType文件下的__int64数据类型未定义,找到typedef __int64 int64_t;并在其上方写如下,再点击构建即可,编译成功后便完成了海康相机SDK的加载

#define __int64 long long

    引入的SDK直接使用会比较麻烦,不太符合面对对象的编程思想,一般会进行二次封装,在Qt中额外定义一个camera类,封装一下SDK中的打开相机,采集相机,关闭相机等常用的函数,这里做主要二次封装的思路演示,具体重写的函数数量与类型视需求而定,可以通过查阅SDK中MvCameraControl.h与中对各函数的解释与定义即可完成二次封装:

//引入SDK文件

class camera
{
public:
    camera();
    ~camera();
public:
  int open(MV_CC_DEVICE_INFO *deviceinfoPtr);
  int close();
  bool IsDeviceConnected();
  //抓图
  int StartGrabbing();
  //停止抓图
  int StopGrabbing();
  //主动获取一帧的图像数据
  int GetImageBuffer(MV_FRAME_OUT *framePtr,int Sec);
  //释放图像缓存
  int FreeImageBuffer(MV_FRAME_OUT *framePtr);
  ..........

}

二.UI界面功能

       这部分较为简单,本文着重说明如何代码实现,UI根据项目需求自行设计,大体可以分为相机设置,PLC通讯设置,采集图像显示,视觉算法处理,本案例暂不制作该模块,读者自行根据需求加载即可

1.相机设置容器:

     主要包括5个功能,加载相机名,查找子网下的所有GIGE和USB3的海康相机并显示名字到combox里面,打开相机,关闭相机,开始采集与停止采集功能

2.采集图像容器:

    实时显示从相机中获取的图像

3.PLC通讯容器:

      Qt可以通过串口与TCP进行PLC通讯,使用的协议是Modbus协议,本文中QT使用ModbusTCP协议封装数据包(视觉检测结果),PLC端使用FreeTCP(socket)发送视觉检测需求,这样设置的原因是,汇川PLC作为通讯主站时使用FreeTCP比较简单,指令很少,后面细说

三.项目代码

      该部分主要是建立TCP服务端,相机进程,视觉算法封装三个模块

1.建立TCP服务端与实现PLC通讯细节

    该部分我们需要通过IP地址与端口号进行与PLC的通讯建立,IP地址可以在汇川PLC的编程软件AutoShop中的通讯设置模块进行配置,这里假设设置为192.168.10.10,端口号默认为502,那么Qt中就需要通过这两个建立连接,使用Qt中自带的类,信号与槽机制完成,首先在pro文件中引入如下代码:

QT+= serialbus
QT+= network

     构建通过后,在.h文件中引入如下文件,并分别定义ModbusClient,QTcpSocket,QTcpServer类指针,并在主线程(UI)启动时,在构造函数中进行初始化,在析构中完成资源释放

#include<QModbusClient>
#include<QModbusDataUnit>
#include<QModbusReply>
#include<QTcpSocket>
#include<QTcpServer>
......

private:

  QModbusClient *myclient=nullptr;
  QTcpSocket *mySocket=nullptr;
  QTcpServer *myServer=nullptr;

  QList<QString>RWMod;//combox显示内容
...
Wiget::Widget(QWidget *parent):QWidget(parent)
,ui(new Ui::Widget)
{
   ui->setupUi(this);

   //初始化指针,记得释放
   myclient=new QModbusClient();
   mySocket=new QTcpSocket();
   myServet=new QTcpServer();
   
   //初始化选择读取寄存器类型,
   //按需求添加K,C,T,X,Y等寄存器
    RWMod.push_back("M寄存器");
    RWMod.push_back("D寄存器");
    for(int i=0;i<RWMod.size();i++) 
    {
      ui->CBPLCdata->addItem(RWMod.at(i));
    }

   ui->PBdisConnect->setEnable(false);
}

Widget::~widget()
{
    connect(socket,&QTcpSocket::disconnected,this,[=]()
    {
            qDebug()<<"断开客户端连接";
            //close and deletelater
    });

    if(myclient!=null)
    {
        delete myclient;
    }
    if(mySocket!=null) 
    {
        delete mySocket;
    }
    if(myServer!=null)
    {
       delete myServer;
    }

    delete ui;
}

     下面在cpp文件中实现TCP服务端建立,使用listen方法监听本机的7960端口,监听成功后,server对象作为发送者,发送建立连接的信号,主线程UI接受信号,并执行表达式内的方法由socket完成建立连接,socket发送读取端口数据的信号,并在表达式内解包读取到的TCP报文

//在构造函数中建立连接或者在槽函数中建立
    bool result =server->listen(QHostAddress::Any,7960);//监听本机7960端口
    if(result)
    {
        qDebug()<<"服务器启动成功";
    }
    else{
        qDebug()<<"服务器启动失败";
        QMessageBox::warning(this,"警告","未能连接上PLC");
        return;
    }
    qDebug()<<ret<<server->errorString();
    connect(server,&QTcpServer::newConnection,this,[=]()
    {
        socket=server->nextPendingConnection();
        if(socket!=nullptr)
        {
           qDebug()<<"已成功与客户端建立连接";
        }
             connect(socket,&QTcpSocket::readyRead,this,[=]()
            {
                 QByteArray data=socket->readAll();
                 ui->plainTextEdit_15->insertPlainText(data);
                 qDebug()<<data;
                //执行视觉检测
                //上位机会接受到一个TCP报文,数据类型是QByteArray,根据报文选择不同的处理方法
                QByteArray byte("0x00000");
                 if(data==byte)
                 {
                     qDebug()<<"检测到PLC视觉检测需求,开始检测";
                      //实现视觉检测并传递检测结果到PLC
                 }
            });
    });

       通过上述的代码,我们就可以实现一个简易的TCP服务端,读者只需根据实际需求,设置switch语句对PLC发来的不同TCP报文内容进行选择执行,这里不再演示。

      下面开始建立与PLC的连接,通过ip与端口号进行连接,这里必须确保电脑的IP地址与PLC在同一个子网下,如果连接失败可以先去检查电脑的网络配置和PLC是否接入同一个路由器,这里内定了PLC的IP和端口,实际使用过程可以通过遍历当前网络下的所用IP来进行选择连接

void MainWindow::on_PBconnect_clicked()
{
   if(myclient==NULL)
   {
      return ;
      //添加异常处理
   }   
   if(myclient->state()==QModbusDevice::ConnectedState)
   {
      myclient->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);
myclient->setConnectionParameter(QModbusDevice::NetworkAddresstParameter,"192.168.10.10");
      myclient->setTimeout(3000);
      myclient->setNumberRetties(3);
      if(myclient->connectDevice())
      {
            QMessageBox::information(this," ","connected!");
            ui->PBdisconnect->setEnable(true);
            //连接后才可点击断开,在ui构造函数中初始设置为false
      }
   }

}

void Widget::on_PBdisconnect_clicked()
{
    if(myclient->state()==QModbusTcpClient::ConnectedState)
    {
        myclient->disconnectDevice();
        QMessageBox::information(this,"information","已断开连接");
       
    }
        ui->PBconnect->setEnable(true);
}

    接着是构建发送读写请求的报文,我们使用ModbusTCP协议,主要使用QModbusDataUnit封装报文,通过信号与槽机制来完成,这里演示D位寄存器的读取与写入,修改unit的构造即可:

//读取
void Widget::readMethod(int readAddress,quint16 data)
{
    //HoldingRegisters:保持寄存器
    //Coils:线圈
    
    QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters,readAddress,data);
    QModbusReply *reply=client->sendReadRequest(unit,1);
    if(reply)
    {
        if(!reply->isFinished())
        {
            connect(reply,QModbusReply::finished,this,replyData);
            return;
        }
        reply->deleteLater();
    }
}
void Widget::replyData()
{
    QModbusReply *reply =(QModbusReply*)(sender());
    QModbusDataUnit unit =reply->result();
    reply->deleteLater();
    if(unit.valueCount()>0)
    {
        ui->plainTextEdit->insertPlainText(QString::number(unit.value(0)));
    }
     readunit=unit.value(0);
     //qDebug()<<readunit;
}
void Widget::on_pushButton_clicked()
{
   readMethod(ui->plainTextEdit_4->toPlainText().toInt());
   //传入窗口的值
}

//写入
void Widget::writeMethod(int writeAddress,quint16 data)
{
    QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters,writeAddress,data);
    unit.setValue(0,data);

    QModbusReply *reply=client->sendWriteRequest(unit,1);
    if(!reply->isFinished())
    {
        connect(reply,&QModbusReply::finished,this,[this,reply]()
        {
            if(reply->error()!=QModbusDevice::NoError)
                reply->deleteLater(); 
        });
    }
    else{
            if(reply->error()!=QModbusDevice::NoError)
                reply->deleteLater();
        }
}

       通过上述代码,我们就实现了Qt上位机连接PLC并读取与写入到保持寄存器(D类)的功能,只需要在按钮的clicked()函数中使用这两个方法就可以完成读写操作。

       至此我们基本完成了上位机通讯模块的设置,下位机也需要对应的通讯设置,在汇川PLC的编程软件中写入如下代码:

M8000 TCPCON K192 K168 K10 K80 K7960
      TCPSTA K192 K168 K10 K80 K7960 D1

X1 DMOV K2 D2
   DMOV H30 D3
   SET M0

//M8000为常开,X1为上升沿触发
//D1表示通讯状态,-1表示断开连接
//在AutoShop左侧工具栏中的通讯设置找到以太网设置,右键点击生成以太网配置,在其中配置如下:
//从站ip地址:192.168.10.80(假定主机地址)
//通讯方式:触发
//功能:读寄存器(03)
//触发条件:M0
//从站寄存器地址:D2(实际发送下一个地址的寄存器的数据,即D3)
//数据长度:50
//主站缓冲区地址:D500(填写不用的寄存器即可)
//端口号:7960
//站号:默认255
//协议:FreeTCP

        至此就完成了该项目demo的全部制作,谢谢观看

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值