背景

这也是之前做的一个项目。主要目的是一个粗的电线里面有三种颜色的线在一起,需要通过机器视觉检测出来相互之间的位置,保证黄色线在最上面。他们有个专门的电机旋转电线,需要给到控制电机的PLC需要旋转的角度。我们主要负责做识别检测部分和上位机操作软件。

检测的线材如图:

系统组成


硬件主要为摄像头,工控机,串口转MODBUS。PLC控制不是我们负责的,我们只需要通过MODBUS把需要旋转的角度发送过去即可。只知道他们那边的PLC用的是中研五轴十轴模块。
摄像头采用了映美精的GigE彩色工业相机。工控机采用i5处理器的小型工控机。

软件初始时的设计思路如图所示:

这是最初根据需求设计的思路,后面开始野蛮生长。。

下面是初始用GUI design设计的基本GUI界面,后面也完全变了样:

后面又加入了许多新的功能和界面,最终界面最后再展示吧。

我们开发采用界面与算法分开的方式。我主要负责界面和逻辑的编写,几乎就是除了算法的所有部分,而我的同学专心测试算法。上位机软件部分用QT编写,算法的话是opencv,他为了方便先用python的库写好大概,测试没问题后,在对着人工转成C++。

用到的QT版本是5.9.3,opencv是4.1。

学习到的技术

通过这个项目确实学习到了很多关于QT和C++的实操。根据代码里面的各个模块值得讲的来讲学习到的东西吧。

首先是一张实际代码的结构图:

  • 首先是QT的项目文件.pro

因为用到了QT的串口功能,所以要在.pro里加入:

QT       += serialbus serialport widgets

还有要用到的管理员权限,后面开机自启,还有关闭能顺便关机都会用到:

#权限  需要以管理员运行creator
QMAKE_LFLAGS += /MANIFESTUAC:\"level=\'requireAdministrator\' uiAccess=\'false\'\" #以管理员运行
QMAKE_LFLAGS += /SUBSYSTEM:WINDOWS,\"5.01\" #VS2013 在XP运行

然后在QT里加入新的模块记得直接项目右击添加新的文件,才会自动导入到.pro里。

还有引入库的方法,因为后面要用到opencv的库。现在本机上装上opencv的包,这里用的4.1版。然后项目右击,添加库,外部库,选择库的路径。完了它会在.pro下自动添加相应代码。但之前也遇到了问题,添加之后还是无法正常编译,于是又查了写方法,手动在.pro里添加了如下代码:

INCLUDEPATH += D:\OPENCV\opencv4.1\build\include
INCLUDEPATH += D:\OPENCV\opencv4.1\build\include\opencv2CONFIG(debug, debug|release): {
LIBS += -LD:\OPENCV\opencv4.1\build\x64\vc14\lib \
#-lopencv_world410 \
-lopencv_world410d \
} else:CONFIG(release, debug|release): {
LIBS += -LD:\OPENCV\opencv4.1\build\x64\vc14\lib \
-lopencv_world410 \
#-lopencv_world410d \
}

注意release的和debug的一定要分开,不要涂省事都包进去,之前就是这点没区分,调试的时候好好的,想发布一输出release版就报错。

引入的映美精库和modbus库:

LIBS += $$quote(D:\QT\wire\Library\TIS_UDSHL11_x64.lib)
LIBS += $$quote(D:\QT\wire\Library\TIS_UDSHL11d_x64.lib)win32: LIBS += -L$$PWD/Library/ -llibmodbus-5

改软件的图标,把图标放在代码目录里,如1.icon,加入:

RC_ICONS = 1.ico
  • main

主要是自启相关的功能,跟系统相关的交互。之前调试的时候一不小心,同时测了开机自启和自动关机…结果开机就启动了程序然后立马关机,成了一个病毒一样的东西。后来终于找到方法结束罪恶的循环,开机之前进入安全模式,就会没有自启,然后进到软件文件夹里把软件删了。

加入开机自启本来想通过命令行加入参数启动时才开启自启(通过 int main(int argc, char *argv[])),可是试了以下好像没能实现,具体原因也不知道,最后就做成了默认自启。
开机自启(要先#include “windows.h”):

void appAutoRun(bool bAutoRun)
{QString appName = QApplication::applicationName();//程序名称QString appPath = QApplication::applicationFilePath();// 程序路径appPath = appPath.replace("/","\\");//HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunQSettings  *reg=new QSettings("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",QSettings::NativeFormat);if (bAutoRun){QString val = reg->value(appName).toString();// 如果此键不存在,则返回的是空字符串if(val != appPath)reg->setValue(appName,appPath);}else{reg->remove(appName);}reg->deleteLater();
}

防止程序重复启动:

bool checkOnly()
{//  创建互斥量HANDLE m_hMutex  =  CreateMutex(NULL, FALSE,  L"fortest_abc123" );//  检查错误代码if  (GetLastError()  ==  ERROR_ALREADY_EXISTS)  {//  如果已有互斥量存在则释放句柄并复位互斥量CloseHandle(m_hMutex);m_hMutex  =  NULL;//  程序退出return  false;}elsereturn true;
}

在main函数的最前面加入程序重复的检测,如果返回false,则直接不再执行后面的,return 0。

顺便补习下这部分知识
CreateMutex只是创建了一把锁,作用是找出当前系统是否已经存在指定进程的实例。如果没有则创建一个互斥体。

HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针
BOOL bInitialOwner, // 初始化互斥对象的所有者
LPCTSTR lpName // 指向互斥对象名的指针
);

修改IP(需要#include ):

void IpConfig()
{QProcess *process = new QProcess();//网卡名称QString name = "\"以太网\" ";QString cmd = "netsh interface ipv4 set address name = " + name + "source = static address = 218.192.162.1 ";qDebug()<<"cmd = "<<cmd;process->start(cmd);   //执行dos命令process->waitForFinished(); //等待执行完成delete process;
}

通过QT里的向命令行输入指令实现。自动关机也是同样的原理。

  • maindialog

主逻辑界面如图:
这里要先说个教训,之前没什么经验,有些类实例化命名的方式太不规范,导致后面分不清谁是谁,无形中增加了之后工作的难度。所以以后项目中一定要吸取教训,命名能让人看懂大致意思,还有写好注释,面得过段时间再看到时根本想不起是什么。

因为工控机只使用这个软件不作他用,所以界面全屏始终在最上方,并且加入电子时钟。
不再列代码了,后面会上传到资源列表和github,主要列一下实现的功能和有用的技术点。

1. 窗口全屏且置顶
2. 相机未打开时那块区域黑色背景(QPalette实现)
3. 电子时钟(QTimer、QLCDNumber实现)
4. 不断跳转的检测数量和历史检测数(通过读写本地配置文件和检测算法的信号槽实现)

最关键的技术点还是信号槽吧,也算是QT的精髓了
这里主要讲两个吧,第一个是范例,在ui界面的定义的按钮,点击打开对应窗口的:

connect(ui->pushButton_3, SIGNAL(clicked(bool)), this, SLOT(debug()));

参数1是ui里的按钮对象,参数2信号点击,参数3本界面的对象,slot槽函数,在.h里的public slots或private slots要提前定义,后面当成本类的成员方法函数写出来。

第二个是在别的窗口或类里抛出了信号量(注意,信号量要定义在抛出信号量的类里 如

public:
signals:void finish(int);void no_roi();)需要发出信号量时,在对应地方:emit finish(int);如果有参数的,信号槽要这样写:connect(Listener1::Instance(), SIGNAL(finish(int)), this, SLOT(show1(int)));;

在本类里接收做出反应:

connect(&w3, SIGNAL(grab_signal()), this, SLOT(grab1()));

参数1是要在本类.h里声明发出信号的对应类的对象,这个类至少要继承于QObject,并在类里定义signals: void grab_signal(); 把信号定义在里面。参数2是发出信号的名称,参数3本界面的对象,参数4对应的槽函数。

  • historyimage

主要是实现读取本地图片形成列表,通过存图时命名显示出旋转角度和哪个相机所拍等信息。

实现功能:
1. 列表(通过ui->qTableWidget、QTableWidgetItem实现,qTableWidget真的很方便)
2. 排序(QHeaderView实现)
3. 列表上双击打开和右键菜单(doubleClicked(QModelIndex) 信号槽 和 customContextMenuRequested(QPoint) 信号槽)
4. 读文件(QImage load实现)
5. 列表下一页(verticalScrollBar)

  • sysdata

专门存储的数据类,读写数据到本地。关键点是QSettings,这点列代码吧。
写:

 QSettings ini(filepath, QSettings::IniFormat);ini.beginGroup(QString("System_Data"));QString key;key.sprintf("password");ini.setValue(key, p);key.sprintf("deflection");ini.setValue(key, deflection1);ini.endGroup();

读:

QString key;
QSettings ini(filepath, QSettings::IniFormat);
ini.beginGroup(QString("System_Data"));
key.sprintf("password");
password = ini.value(key).toString();
key.sprintf("deflection");
deflection = ini.value(key).toString();
ini.endGroup();
  • keyboard

虚拟键盘,有些地方值得写一下:

改变焦点信号槽

 //绑定全局改变焦点信号槽connect(qApp, SIGNAL(focusChanged(QWidget *, QWidget *)),this, SLOT(focusChanged(QWidget *, QWidget *)));

QT的提供了很多全局的信号,在程序的任何位置都可以接收处理
当系统焦点发生改变的时候,就会发出focusChanged信号,系统内其他程序都可以接收这个信号

定义槽函数,如

void Keyboard::FocusChanged(QWidget *, QWidget *nowWidget)
{if (nowWidget->inherits("QLineEdit")){... ...}
}

可以在槽函数里判断,过滤想要的控件。

通过全局改变焦点信号槽识别出当前指向的是否为输入栏。

事件过滤器

这里有两篇应用参考:
https://jingyan.baidu.com/article/a378c960c9003bb32928304b.html
https://blog.csdn.net/wang13342322203/article/details/81532207

在监测的代码里执行需要的行为. 这可以用event Filter来达到. 设置一个event filter有两个步骤:

  1. 在目标对象上调用installEventFilter(),将监测对象注册到目标对象上.
  2. 在监测对象的eventFilter()方法里处理目标对象的事件.

通过事件过滤器识别出鼠标的按下而释放从而在输入栏旁边弹出虚拟键盘,还有捕捉输入栏不是焦点窗口时关闭虚拟键盘。

还用一个语法foreach,用于遍历按键是按钮列表里的哪一个,如:

QList<QPushButton *> btn = this->findChildren<QPushButton *>();
foreach (QPushButton * b, btn) {connect(b, SIGNAL(clicked()), this, SLOT(btn_clicked()));
}

foreach (varItem , Items) // foreach(variable ,container)
类似于C++11里的 for(i:range)

其中btn_clicked()还用到了一个关键字sender(),指信号槽发送者。是QT自带的方法。

QPushButton *btn = (QPushButton *)sender();

调试界面:

  • TIS_Camera.cpp

相机类,通过映美精官方相机的SDK编写,用于操作相机的相关功能。由于都是根据官方开发的,官方那部分就不再细讲,主要讲一下自己开发的部分几个点:
1. 单例模式
因为相机只有一个,不同对象间数据应该共享同一个,不应该不同地方创建不同对象所指的相机数据不同。所以应使用单例模式,保证对象始终是一个。后面才发现可以直接不创建对象,在别的地方对类进行操作。
在public里直接以static创建类名:
//实现单例模式,即使不创建对象仍能够使用 保证了不同类之间使用了同一个对象,便利于信号与槽的传递,与主界面逻辑的沟通

static TIS_Camera *Instance(){if (!_instance) {_instance = new TIS_Camera;}return _instance;
}

在private里定义:

 static TIS_Camera *_instance;       //实例对象

重要的,在cpp里开头也要定义,不然会报错:

TIS_Camera *TIS_Camera::_instance = 0;

2. 显示相机信号用套接字而不是不停截图显示
之前不知道怎么把相机的内容实时显示在界面上,用了个方法,不断的抓图然后显示,估计一秒钟三十下吧,结果自己的电脑运行起来都非常卡,只能另寻他法。刚巧在网上找到了用HWND窗口句柄的方法。
在Windows中,句柄是一个系统内部数据结构的引用。例如当你操作一个窗口,或说是一个Delphi窗体时,系统会给你一个该窗口的句柄,系统会通知你:你正在操作142号窗口,就此你的应用程序就能要求系统对142号窗口进行操作——移动窗口、改变窗口大小、把窗口最小化等等。
换句话说,句柄是一种内部代码,通过它能引用受系统控制的特殊元素,如窗口、位图、图标、内存块、光标、字体、菜单等。
定义时,将要显示相机内容的QWidget传入,将本身相机的句柄赋予QT里布局好的句柄:

void TIS_Camera::Camera(QWidget *win){HWND appwnd;appwnd = (HWND)win->winId();// Set the window that should display the live video.Grab1.setHWND(appwnd);//
}

使用时:

cam.Camera(ui->widget_2);

3. CVmat转QImage
用来把opencv的图片格式转换为QT的图片格式:

 QImage TIS_Camera::cvMat2QImage(const cv::Mat& mat, bool clone, bool rb_swap)
{const uchar *pSrc = (const uchar*)mat.data;// 8-bits unsigned, NO. OF CHANNELS = 1if (mat.type() == CV_8UC1){//QImage image(mat.cols, mat.rows, QImage::Format_Grayscale8);QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8);if (clone) return image.copy();return image;}// 8-bits unsigned, NO. OF CHANNELS = 3else if (mat.type() == CV_8UC3){// Create QImage with same dimensions as input MatQImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);if (clone){if (rb_swap) return image.rgbSwapped();return image.copy();}else{if (rb_swap){cv::cvtColor(mat, mat, CV_BGR2RGB);}return image;}}else if (mat.type() == CV_8UC4){qDebug() << "CV_8UC4";QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32);if (clone) return image.copy();return image;}else{qDebug() << "ERROR: Mat could not be converted to QImage.";return QImage();}
}

注意opencv图片也有格式区别,之前不知道,图片一只显示不出来,后来才发现是CV_8UC4的问题。
openCV的类型可以阅读以下内容:CV_8U:无符号整型8位,C4:四个信道。
4. 相机回调里的try…catch
之前算法老出问题,一旦出问题就会导致主界面崩溃,调试的时候都直接崩溃。这显然不是我们想看到的,所以引入了try.catch异常检测机制,一方面能在出错的时候弹出对话框,增加程序的稳定性,另一方面也能方便排查到底是哪里出了问题。
使用方法是在调用每一个可能出现问题的函数前先try把他们包裹进去,再在后面catch写出可能出现问题的类型,然后throw。或者某些判断之后直接throw。
接着在外边调用这个类的大方法前也加入try,catch()里写需要捕捉的错误代码,然后{}里处理。用这个方法结合信号槽和对话框确实有效解决了程序崩溃的问题。

其他部分涉及的技术点基本都有些重复,不再展开写了,或许后续想到后再补充。

补充:还有个非常好用的一键把程序所需要的dll打包拉去的脚本:

for /r "%cd%" %%i in (*.exe) do (   D:\QT\Qt5.9.3\5.9.3\msvc2015_64\bin\windeployqt.exe "%%~nxi")pause:: 以::表示注释 该批处理语句 云鬟查找当前目录下exe文件名 执行 windeployqt 复制dll 自行修改相应目录

总结

本次项目基本掌握了QT程序的开发流程,熟悉了C++的语法,以及一些协作开发工具的使用,比如git(主要用的TortoiseGit,真是超级好用),腾讯工蜂。也仔细思考了多人协作开发的相关流程,如何分别拉去创建分支和合并,如何协调开发和修BUG等等。算是收获颇丰,也意识到一个人的精力是有限的,一直编一会儿代码都会很容易疲惫,需要协调好码代码和休息的时间。还有深刻理解了那句话,程序应该百分之八十的时间用来设计,百分之二十的时间把设计转化为代码。而不是一头扎进去就是写,写之前把一切想清楚,设计好,才是真正有效率的编程方式。

资源地址:
github地址:https://github.com/ypg666/Wire_Rotate

线材检测项目(基于QT)相关推荐

  1. dgen模拟器 linux,game_box: GameBox 是一款游戏家用机模拟器,本项目基于Qt,可在windous\mac\linux等多平台使用。...

    game box 概述 GameBox是一款游戏家用机模拟器,本项目基于Qt,可在windous\mac\linux等多平台使用.由于本项目基于多种开源代码开发而成,强烈提醒注意license说明. ...

  2. 野牛NBIOT 环境监测项目---基于QT 5.9 接入华为OceanConnect云平台(六)

    NBIOT-北向开发之PC桌面 基于QT 5.9 北向接入华为OceanConnect云平台 该软件与上一章节提供的的平台profile是对应的,如果profile的关键参数不一致,需要同步修改软件代 ...

  3. 基于Qt的智能车载系统嵌入式项目(正点原子IMX6ULL开发板)

    基于正点原子的IMX6ULL开发板的智能车载系统(Qt) 提示:该项目借鉴了不少大佬的代码,我没有自己造轮子(代码在文章末尾,同时附上参考链接 ) 本人其他项目链接基于linux的智能仓储项目 基于Q ...

  4. QT5/C++项目:基于QT的跨平台网络对战象棋(一)(推荐★★★★)

    QT5/C++项目:基于QT的跨平台网络对战象棋(一)(推荐★★★★) 文章目录 QT5/C++项目:基于QT的跨平台网络对战象棋(一)(推荐★★★★) 本篇副标题: 本篇博客讲了什么or解决了什么问 ...

  5. 基于QT天气预报项目,推荐初学者练手

    文章目录 前言 本项目基于QT平台开发的一款天气预报,一个适合小白练手的项目.使用到了技术有HTTP编程.定位API.天气预报API.JSON解析,unicode转化为汉字.分享给大家一起讨论学习: ...

  6. QT5/C++项目:基于QT的跨平台网络对战象棋(三)(推荐★★★★)

    QT5/C++项目:基于QT的跨平台网络对战象棋(三)(推荐★★★★) 文章目录 QT5/C++项目:基于QT的跨平台网络对战象棋(三)(推荐★★★★) 本篇副标题: 本篇博客讲了什么or解决了什么问 ...

  7. 目标检测—基于Yolov3的目标检测项目实战(学习笔记)

    最近在学习tensorflow,尝试运行学习了github上基于yolov3的一个目标检测项目,此算法可对视频.图片.摄像头实时进行检测,本文主要讲述了,在windows电脑上,复现这一目标检测项目的 ...

  8. 基于QT的【第一个项目】设计+所有组件配合使用+网络编程局域网通信+文件IO操作+登录界面和头像+多界面跳转+JSON数据解析+表情包制作

    基于QT的第一个项目+所有组件配合使用+网络编程局域网通信+文件IO操作+登录界面和头像+多界面跳转+JSON数据解析+表情包制作 第一阶段 网络编程局域网TCP/IP聊天QT实现 main.c ma ...

  9. 嵌入式项目实战——基于QT的视频监控系统设计(二)

    嵌入式项目实战--基于QT的视频监控系统设计(二) 昨天我分享了关于QT的基本使用方法,掌握了这些基本的方法就可以设计一个简单的视频监控界面.下面我们开始分享完成这个嵌入式项目同样重要的知识点--UD ...

  10. 嵌入式项目实战——基于QT的视频监控系统设计(三)

    嵌入式项目实战--基于QT的视频监控系统设计(三) 进入到五一假期第三天,继续我们的项目.本来五一假期还是想好好休息一下的,因为最近学习的状态不太好,刷题都没有思路了,但是身边的同学太卷了,不过我还是 ...

最新文章

  1. 深度:应用安全是信息安全防护的短板
  2. 【C#】C#抽象类及其方法
  3. [云炬创业基础笔记] 第四章测试12
  4. 一直在构建工作空间_基于用户场景构建的建筑工程弱电设计工作设想
  5. rhel6上使用udev配置oracle asm,Red Hat Enterprise Linux 6使用udev配置Oracle ASM总结文档
  6. python discuz搜索api_Python + Bottle + 谷歌搜索Api 实现简单搜索引擎
  7. mysql 加1_[MySQL场景系列之三] 加一操作
  8. hadoop rpc客户端初始化和调用过程详解
  9. Visual Studio 2013 (CV版)编译错误【error C4996: 'sprintf': This function or variable may be unsafe. 】的解决方案
  10. python 股票 因子分析_因子分析1.-Python数据科学技术详解与商业项目实战精讲 - Python学习网...
  11. BNU29140 Taikotaiko(概率)
  12. STKX组件技术在星地链路中的仿真模式研究
  13. 人大金仓数据库工程师培训实战教程(同步复制、读写分离、集群高可用)
  14. 虚拟机ip映射到外网
  15. Oracle语句函数
  16. 塔塔露也能学会的算法(1) | dijkstra从入门到放弃
  17. 如何用echarts实现颜色渐变半圆形仪表盘
  18. 微信小程序页面添加背景图,图片全屏显示
  19. UG NX 12抽取体特征
  20. 模块电路选型(1)----电源模块

热门文章

  1. 虚拟偶像养成记:人工智能人格化与IP化打造出完美“爱豆”
  2. 一位销售的几年职业总结
  3. LateX安装下载使用详细教程
  4. 音频和Midi基础支持
  5. Python3迅雷vip账号批量抓取导入excel中
  6. Linux好用的音乐播放器
  7. CHM打不开的解决方法
  8. chm打不开怎么办?
  9. qc中的流程图怎么画_QC流程图
  10. 运维工程师面试题及答案(网络运维工程师面试题)