前面几个章节完成了opencv、OPCUA、Qt在VS2015中的编程环境配置及测试,下面进行这几个工具的联合编程,最终实现一个可执行的用户程序,应用场景是利用工业相机采图进行图像处理,具备以下功能:
- 读写现场PLC变量功能
- 根据PLC变量值从海康威视工业相机采图
- 利用opencv对图像进行处理,并与PLC进行通讯交互
- 用户界面可以显示当前处理的图像
按照之前章节的步骤,首先测试Qt程序框架中opencv是否可用。
双击项目Form files中的.ui文件,打开Qt designer设计窗口
在左侧控件库中托一个graphview控件到中间的窗口上,这个控件可以作为图片显示的容器。
再添加一个pushbutton按钮,用于触发执行程序,需要添加槽函数start。在.h文件中添加槽函数声明,在.cpp文件中添加槽函数实现。后续的采图通讯、图像处理、界面显示都是在槽函数中实现。
Qt添加的控件对应的头文件也需要添加到代码中,如用到了graphicView控件显示图片,就需要添加#include<QGraphicsView> 头文件,否则读取不到控件名称。
添加控件后在VS中右键项目点重新扫描解决方案,否则添加的控件类在代码中找不到(个人理解就是添加控件后应用一下,控件才会添加到VS中的UI头文件中)
添加pushbutton控件与槽函数的关联,功能是运行程序时,点击开始按钮,触发clicked事件调用槽函数on_start_clicked()执行其中的代码
槽函数实现及解读:
void Qt_test01::on_start_clicked()
{
QGraphicsScene *scene = new QGraphicsScene; //定义显示场景控件,用于将图片显示到主窗口,场景控件作为容器,用于向图片控件传递图片,场景控件在后台中,不会显示到用户界面
img = cv::imread("..\\pic_test.jpg"); //从硬盘读入一张图片
h_imgorg = (int)ui.graphicsView->height() - 5; //设置图片尺寸,比UI界面的图片容器小一点
w_imgorg = (int)ui.graphicsView->width() - 5;
cv::resize(img, img, Size(w_imgorg, h_imgorg)); //更改图片尺寸,以适应图片显示控件大小
Qimg_org = QImage((const unsigned char*)(img.data), img.cols, img.rows, img.step, QImage::Format_RGB888); //Mat格式图像转化为Qt格式图像
scene->clear(); //场景空间及label空间显示图片或文字前先清空,否则一直覆盖导致内存占用越来越多
scene->addPixmap(QPixmap::fromImage(Qimg_org)); //场景控件添加图片
ui.graphicsView->setScene(scene); //场景控件容器中的图片添加到图片控件
ui.graphicsView->show(); //图片控件显示图片
}
按Ctrl+F5调试,点击开始按钮,可以显示图片,说明opencv没问题,可以使用
继续实验,读取海康威视工业相机,采图并将图片显示到用户窗口中,将读取摄像头代码添加到on_start_clicked()槽函数中。
void Qt_test01::on_start_clicked()
{
capture = VideoCapture(0); //打开摄像头
capture.set(CAP_PROP_FRAME_WIDTH, 2448); // 设置图像宽度
capture.set(CAP_PROP_FRAME_HEIGHT, 2048); // 设置图像高度
capture.set(CAP_PROP_FPS, 5); // 设置帧率
if (!capture.isOpened())
{
QMessageBox::information(this, "information", "Open camera failed!");
capture.release();
return ;
}
capture >> img; //读取视频流中的一帧,写入img
QGraphicsScene *scene = new QGraphicsScene; //定义显示场景控件,用于将图片显示到主窗口,场景控件作为容器,用于向图片控件传递图片,场景控件在后台中,不会显示到用户界面
//img = cv::imread("..\\pic_test.jpg"); //从硬盘读入一张图片
h_imgorg = (int)ui.graphicsView->height() - 5; //设置图片尺寸,比UI界面的图片容器小一点
w_imgorg = (int)ui.graphicsView->width() - 5;
cv::resize(img, img, Size(w_imgorg, h_imgorg)); //更改图片尺寸,以适应图片显示控件大小
Qimg_org = QImage((const unsigned char*)(img.data), img.cols, img.rows, img.step, QImage::Format_BGR888); //Mat格式图像转化为Qt格式图像
scene->clear(); //场景空间及label空间显示图片或文字前先清空,否则一直覆盖导致内存占用越来越多
scene->addPixmap(QPixmap::fromImage(Qimg_org)); //场景控件添加图片
ui.graphicsView->setScene(scene); //场景控件容器中的图片添加到图片控件
ui.graphicsView->show(); //图片控件显示图片
}
按Ctrl+F5调试运行,点击开始按钮,graphicView控件显示相机当前图片成功
继续测试OPC UA读取PLC变量,并根据变量值变化进行采图,这里用之前实验的“test.test.OPC_test”点,等于1时采图并显示,等于0时从硬盘读取图片并显示。再添加两个label控件,实时显示读取的OPC_test数值。
程序添加do循环,循环读取OPC变量值;需要添加closeevent槽函数,当点击关闭应用程序时退出do循环并释放OPC接口和释放相机,否则do循环无法退出程序进入死循环。
两个槽函数修改如下:
void Qt_test01::on_start_clicked()
{
QGraphicsScene *scene = new QGraphicsScene; //定义显示场景控件,用于将图片显示到主窗口,场景控件作为容器,用于向图片控件传递图片,场景控件在后台中,不会显示到用户界面
client = UA_Client_new(); //创建opcua客户端
UA_ClientConfig_setDefault(UA_Client_getConfig(client)); //设置客户端为默认配置
status = UA_Client_connect(client, "opc.tcp://127.0.0.1:49320"); //连接服务器,此处连接KEPServer,URL可以通过任务栏KEPServer软件右键,“OPC UA配置”查看
//判断opcua客户端连接状态
if (status != UA_STATUSCODE_GOOD)
{
QMessageBox::information(this, "information", "Connect OPC UA Sever Failed");
return;
}
else
{
QMessageBox::information(this, "information", "Connect OPC UA Sever Successful");
}
UA_Variant value; //opcua变量类型
UA_Variant_init(&value); //初始化,注意初始化后不能直接读取value.data的值,会导致内存指向错误,必须通过opcua读值或赋值后才能读取value.data
capture = VideoCapture(0); //打开摄像头
capture.set(CAP_PROP_FRAME_WIDTH, 2448); // 设置图像宽度
capture.set(CAP_PROP_FRAME_HEIGHT, 2048); // 设置图像高度
capture.set(CAP_PROP_FPS, 5); // 设置帧率
if (!capture.isOpened())
{
QMessageBox::information(this, "information", "Open camera failed!");
capture.release();
return ;
}
do
{
status = UA_Client_readValueAttribute(client, UA_NODEID_STRING(2, "test.test.OPC_test"), &value);
if (status != UA_STATUSCODE_GOOD)
{
QMessageBox::information(this, "information", "OPC UA read Failed");
return;
}
OPC_test = *(UA_UInt32*)value.data;
if (OPC_test == 1)
{
capture >> img; //读取视频流中的一帧,写入img
h_imgorg = (int)ui.graphicsView->height() - 5; //设置图片尺寸,比UI界面的图片容器小一点
w_imgorg = (int)ui.graphicsView->width() - 5;
cv::resize(img, img, Size(w_imgorg, h_imgorg)); //更改图片尺寸,以适应图片显示控件大小
Qimg_org = QImage((const unsigned char*)(img.data), img.cols, img.rows, img.step, QImage::Format_BGR888); //Mat格式图像转化为Qt格式图像
scene->clear(); //场景空间及label空间显示图片或文字前先清空,否则一直覆盖导致内存占用越来越多
scene->addPixmap(QPixmap::fromImage(Qimg_org)); //场景控件添加图片
ui.graphicsView->setScene(scene); //场景控件容器中的图片添加到图片控件
ui.graphicsView->show(); //图片控件显示图片
}
else if(OPC_test == 0)
{
img = cv::imread("..\\pic_test.jpg"); //从硬盘读入一张图片
h_imgorg = (int)ui.graphicsView->height() - 5; //设置图片尺寸,比UI界面的图片容器小一点
w_imgorg = (int)ui.graphicsView->width() - 5;
cv::resize(img, img, Size(w_imgorg, h_imgorg)); //更改图片尺寸,以适应图片显示控件大小
Qimg_org = QImage((const unsigned char*)(img.data), img.cols, img.rows, img.step, QImage::Format_BGR888); //Mat格式图像转化为Qt格式图像
scene->clear(); //场景空间及label空间显示图片或文字前先清空,否则一直覆盖导致内存占用越来越多
scene->addPixmap(QPixmap::fromImage(Qimg_org)); //场景控件添加图片
ui.graphicsView->setScene(scene); //场景控件容器中的图片添加到图片控件
ui.graphicsView->show(); //图片控件显示图片
}
ui.label_2->setText(QString::number(OPC_test));
cv::waitKey(100); //waitKey()等待时间,opencv显示图片必须加此函数,否则显示不出来
} while (Flag);
UA_Client_delete(client); //释放opcua client
capture.release(); //释放摄像头
return;
}
void Qt_test01::closeEvent(QCloseEvent * event)
{
Flag = 0;
}
按Ctrl+F5调试
点击开始(OPC_test初始值为0,显示从硬盘读取的图片)
利用Kepsever自带的OPC Quick Client工具修改OPC_test值为1。此时用户界面图片自动显示相机当前图片,并且当前相机图像是实时的动态视频。
总结:
- Qt框架开发windows应用程序可以前端图形化开发用户界面,后台编写槽函数,实验中是事件触发型模式,即点击按钮后执行对应的槽函数
- 实验中验证了通过OPC UA读写PLC变量(这里用Kepsever的模拟通道,没有直接连接实际的PLC);这里同时也验证了之前文章实验中提到的用open62541 (用到ws2_3lib)验证OPC UA读写时只能在32位(x86)平台下,切换到64位时会报错的问题。利用Qt框架选择64位平台也可以进行OPC UA读写,因为Qt是跨平台的
- 实验中用到了Kepsever软件做服务器,需要购买授权,实验中使用试用版有时间限制
- Opencv和open62541库是开源免费的
- Qt后续开发可用的友好的界面需要继续学习,如界面布局,菜单栏,工具栏,状态栏等功能
- 后续利用opencv库对相机采图进行图像处理及算法,需要对opencv进行深入学习
- 特别注意各种变量类型转换,如opencv图像为Mat类型,用Qt界面显示需要转换为QImage类型;OPCUA数据为UA_Variant类型,C++数据为long类型,赋值时需要转化
- 特别注意图像通道顺序,opencv默认BGR格式,海康相机输出格式可以通过MVS软件进行选择设置,利用海康SDK开发时需要转化格式,利用opencv的VideoCapture则不需要转化格式