赞
踩
目录
本文在基本功能实现的基础上,基于PyQT5编写了一个用来控制ros机器人的上位机
源码分享:https://gitee.com/sy_run/myroscar
提示:以下是本篇文章正文内容,下面案例可供参考
本文选择的python环境为pycharm2021,安装教程在这里不再多说
所需要的核心库有PyQt5,rospy和opencv
由于换了新电脑,安装了ubuntu20.04的版本,因此ros的版本更换为了noetic,但具体程序并没有太大变化。
安装QT designer可以方便我们更好的设计界面,具体安装流程不在多说
打开QT designer,选择mainwindow,然后通过想要的控件,设计一个自己喜欢的界面即可,本文
设计的界面如下所示:
具有8个按钮和一个QLabel控件,点击保存即可生成ui文件。
通过pyqt5自带的pyuic能够将ui文件转换成py文件,具体方式如下
(1)将ui文件导入至pycharm之后,然后点击文件->设置
选择工具->外部工具
(2)名称输入pyuic,然后程序输入python3,因为ros-noetic是通过python3运行python程序,如果是ros-kinetic版本的话,这里可能需要输入python。
参数这一栏输入下面内容
-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
工作目录输入$FileDirs即可,也就是生成的py文件和ui文件在同一目录下
(3)点击确定,在左侧项目目录中找到ui文件,右键点击ui文件,选择External Tools,点击pyuic
此时当前目录会生成一个py文件
返回上一级目录,新建文件夹scripts,将生成的py文件移动到该目录下,具体的py文件内容如下,没有QT Designer的朋友可以直接复制下面这个代码。
- # -*- coding: utf-8 -*-
-
- # Form implementation generated from reading ui file 'MyRobotApp.ui'
- #
- # Created by: PyQt5 UI code generator 5.14.1
- #
- # WARNING! All changes made in this file will be lost!
-
-
- from PyQt5 import QtCore, QtGui, QtWidgets
-
- class Ui_MainWindow(object):
- def setupUi(self, MainWindow):
- MainWindow.setObjectName("MainWindow")
- MainWindow.resize(689, 529)
- MainWindow.setToolTipDuration(1)
- self.centralwidget = QtWidgets.QWidget(MainWindow)
- self.centralwidget.setObjectName("centralwidget")
- self.ButtonGo = QtWidgets.QPushButton(self.centralwidget)
- self.ButtonGo.setGeometry(QtCore.QRect(210, 350, 100, 50))
- self.ButtonGo.setObjectName("ButtonGo")
- self.ButtonLeft = QtWidgets.QPushButton(self.centralwidget)
- self.ButtonLeft.setGeometry(QtCore.QRect(110, 400, 100, 50))
- self.ButtonLeft.setObjectName("ButtonLeft")
- self.ButtonRight = QtWidgets.QPushButton(self.centralwidget)
- self.ButtonRight.setGeometry(QtCore.QRect(310, 400, 100, 50))
- self.ButtonRight.setObjectName("ButtonRight")
- self.ButtonBack = QtWidgets.QPushButton(self.centralwidget)
- self.ButtonBack.setGeometry(QtCore.QRect(210, 450, 100, 50))
- self.ButtonBack.setObjectName("ButtonBack")
- self.OpenFace = QtWidgets.QPushButton(self.centralwidget)
- self.OpenFace.setGeometry(QtCore.QRect(550, 100, 100, 50))
- self.OpenFace.setObjectName("OpenFace")
- self.OpenVoice = QtWidgets.QPushButton(self.centralwidget)
- self.OpenVoice.setGeometry(QtCore.QRect(550, 180, 100, 50))
- self.OpenVoice.setObjectName("OpenVoice")
- self.OpenKey = QtWidgets.QPushButton(self.centralwidget)
- self.OpenKey.setGeometry(QtCore.QRect(550, 260, 100, 50))
- self.OpenKey.setObjectName("OpenKey")
- self.OpenCamera = QtWidgets.QPushButton(self.centralwidget)
- self.OpenCamera.setGeometry(QtCore.QRect(550, 20, 100, 50))
- self.OpenCamera.setObjectName("OpenCamera")
- self.VeidoLabel = QtWidgets.QLabel(self.centralwidget)
- self.VeidoLabel.setGeometry(QtCore.QRect(20, 20, 480, 320))
- self.VeidoLabel.setText("摄像头未打开!")
- self.VeidoLabel.setWordWrap(False)
- self.VeidoLabel.setObjectName("VeidoLabel")
- MainWindow.setCentralWidget(self.centralwidget)
- self.statusbar = QtWidgets.QStatusBar(MainWindow)
- self.statusbar.setObjectName("statusbar")
- MainWindow.setStatusBar(self.statusbar)
- QtCore.QMetaObject.connectSlotsByName(MainWindow)
- self.retranslateUi(MainWindow)
-
-
- def retranslateUi(self, MainWindow):
- _translate = QtCore.QCoreApplication.translate
- MainWindow.setWindowTitle(_translate("MainWindow", "My RobotCar"))
- self.ButtonGo.setText(_translate("MainWindow", "前进"))
- self.ButtonLeft.setText(_translate("MainWindow", "左转"))
- self.ButtonRight.setText(_translate("MainWindow", "右转"))
- self.ButtonBack.setText(_translate("MainWindow", "后退"))
- self.OpenFace.setText(_translate("MainWindow", "打开人脸识别"))
- self.OpenVoice.setText(_translate("MainWindow", "打开语音控制"))
- self.OpenKey.setText(_translate("MainWindow", "打开键盘控制"))
- self.OpenCamera.setText(_translate("MainWindow", "打开摄像头"))
新建app.py文件,准备开始编写上位机。
上一篇内容提到过,pc和树莓派之间的通讯是通过发布和订阅主题实现的,因此我们可以通过按下按钮发送clicked信号,在相关联的槽去实现命令的发布即可通过上位机实现通信。
而想要通过上位机启动按键控制和语音控制,在程序里编写是十分麻烦的,我们可以利用已经编写好的程序,利用python中os.fork()函数创建子进程,然后利用os.execl()去调用相关可执行文件,即可实现功能。
- #!/usr/bin/python3
- import signal
- import sys
- import os
- import rospy
- import cv2
- from cv_bridge import CvBridge
- from sensor_msgs.msg import CompressedImage
- from PyQt5 import QtCore, QtGui, QtWidgets
- from std_msgs.msg import String
- from MyRobotApp import Ui_MainWindow
-
- class MyCallback(QtWidgets.QMainWindow, Ui_MainWindow):
- def __init__(self, parent=None):
- super(MyCallback, self).__init__(parent)
-
- rospy.init_node("qtcmd") # 初始化结点
- self.qtkeypub = rospy.Publisher("keycmd", String, queue_size=1000) # 键盘指令发布者
- self.bridge = CvBridge()
-
- # 订阅压缩图像主题,提高帧率
- self.comimgsub = rospy.Subscriber("image_compressed/compressed", CompressedImage, self.compressedimagecallback)
- self.msg = String("") # 发送的消息
-
- self.setupUi(self) # 设置ui
- # 人脸模型
- self.face_cascade = cv2.CascadeClassifier(r'/usr/share/opencv4/haarcascades/haarcascade_frontalface_alt.xml')
- # 槽
- self.OpenCamera.clicked.connect(self.OpenCameraCallback)
- self.OpenFace.clicked.connect(self.OpenFaceCallback)
- self.OpenKey.clicked.connect(self.OpenKeyCallback)
- self.OpenVoice.clicked.connect(self.OpenVoiceCallback)
- self.ButtonGo.clicked.connect(self.ButtonGoCallback)
- self.ButtonLeft.clicked.connect(self.ButtonLeftCallback)
- self.ButtonBack.clicked.connect(self.ButtonBackCallback)
- self.ButtonRight.clicked.connect(self.ButtonRightCallback)
- # 声明
- self._translate = QtCore.QCoreApplication.translate
- self.compressed_image = None # 压缩图像
- self.opencameraflag = False # 摄像头开启标志
- self.openfaceflag = False # 人脸识别开启标志
- self.openkeyflag = False # 键盘开启标志
- self.openvoiceflag = False # 语音开启标志
- self.keypid = -1 # 键盘进程号
- self.voicepubpid = -1 # 语音上报进程
- self.voicesubpid = -1 # 语音识别进程
- self.camerapid = -1 # 摄像头进程
-
- # 人脸识别函数
- def facedetect(self, data):
- gray = cv2.cvtColor(data, cv2.COLOR_BGR2GRAY)
- faces = self.face_cascade.detectMultiScale(
- gray,
- scaleFactor=1.15,
- minNeighbors=5,
- minSize=(5, 5),
- flags=cv2.CASCADE_SCALE_IMAGE)
-
- for face in faces:
- x,y,w,h = face
- # 画矩形
- cv2.rectangle(data, (x, y), (x+w, y+h), (50, 255, 50), 2)
-
- # QT显示图片函数
- def showimg(self, data):
- pixmap = QtGui.QImage(data, 480, 320, QtGui.QImage.Format_RGB888)
- pixmap = QtGui.QPixmap.fromImage(pixmap)
- self.VeidoLabel.setPixmap(pixmap)
- self.show()
-
- # ros图片主题回调函数,参数data为图片数据
- def compressedimagecallback(self, data):
- bridge = CvBridge()
- self.compressed_image = bridge.compressed_imgmsg_to_cv2(data, "bgr8")
- if self.openfaceflag:
- self.facedetect(self.compressed_image)
- self.compressed_image = cv2.cvtColor(self.compressed_image, cv2.COLOR_BGR2RGB)
- self.showimg(self.compressed_image)
-
- # 打开摄像头
- def OpenCameraCallback(self): # 摄像头
- if not self.opencameraflag: # 如果摄像头未打开
- self.opencameraflag = True # 标志为打开
- self.OpenCamera.setText(self._translate("MainWindow", "关闭摄像头")) # 修改按键文本
- self.msg = String("opencam")
- self.qtkeypub.publish(self.msg) # 发布打开命令
-
- else: # 如果已经打开
- self.opencameraflag = False # 标志关闭
- self.OpenCamera.setText(self._translate("MainWindow", "打开摄像头")) # 修改文本
- self.msg = String("closecam")
- self.qtkeypub.publish(self.msg) # 发布关闭命令
- rospy.sleep(0.5) # 延时0.5s,否则会出现无法clear
- self.VeidoLabel.clear() # 清屏
- self.VeidoLabel.setText("摄像头未打开!") # 设置Qlabel文本
- self.openfaceflag = False # 关闭人脸识别,无论是否打开
- self.OpenFace.setText(self._translate("MainWindow", "打开人脸识别")) # 修改文本
- rospy.loginfo("send %s", self.msg) # 发布调试信息
-
- # 人脸识别函数
- def OpenFaceCallback(self):
- if not self.openfaceflag: # 如果没有打开人脸识别
- self.openfaceflag = True # 打开
- self.OpenFace.setText(self._translate("MainWindow", "关闭人脸识别")) # 修改文本
- if not self.opencameraflag: # 如果没有打开摄像头
- self.opencameraflag = True # 打开摄像头
- self.OpenCamera.setText(self._translate("MainWindow", "关闭摄像头")) # 修改文本
- self.msg = String("opencam")
- self.qtkeypub.publish(self.msg) # 发布打开命令
- rospy.loginfo("send %s", self.msg) # 调试信息
- else: # 如果已经打开
- self.openfaceflag = False # 关闭人脸识别
- self.OpenFace.setText(self._translate("MainWindow", "打开人脸识别")) # 修改文本
-
- # 键盘打开
- def OpenKeyCallback(self): # 按键控制
- if self.openkeyflag: # 如果键盘是打开的,则关闭键盘
- self.openkeyflag = False
- self.OpenKey.setText(self._translate("MainWindow", "打开键盘控制"))
- self.msg = String("openkey")
- if self.keypid > 0: # 键盘控制父进程
- os.kill(self.keypid, signal.SIGKILL) # 杀死子进程
- os.wait()
- else:
- self.openkeyflag = True
- self.OpenKey.setText(self._translate("MainWindow", "关闭键盘控制"))
- self.msg = String("closekey")
- self.keypid = os.fork() # 创建子进程
- if self.keypid == 0: # 键盘控制子进程
- os.execl('/opt/ros/noetic/bin/rosrun', 'rosrun', 'myrobot', 'keycontrol')
- rospy.loginfo("send %s", self.msg)
-
- # 语音控制
- def OpenVoiceCallback(self):
- if self.openvoiceflag: # 如果语音已经打开,则关闭
- self.openvoiceflag = False
- self.OpenVoice.setText(self._translate("MainWindow", "打开语音控制"))
- self.msg = String("openvoice")
-
- if self.voicepubpid > 0: # 父进程
- os.kill(self.voicepubpid, signal.SIGKILL)
- os.wait()
-
- if self.voicesubpid > 0: # 父进程
- os.kill(self.voicesubpid, signal.SIGKILL)
- os.wait()
- else:
- self.openvoiceflag = True
- self.OpenVoice.setText(self._translate("MainWindow", "关闭语音控制"))
- self.msg = String("closevoice")
- self.voicepubpid = os.fork() # 创建子进程发布语音命令
- self.voicesubpid = os.fork() # 创建子进程接受语音命令
- if self.voicepubpid == 0:
- os.execl('/opt/ros/noetic/bin/rosrun', 'rosrun', 'myrobot', 'voicepub') # 打开语音识别
-
- if self.voicesubpid == 0:
- os.execl('/opt/ros/noetic/bin/rosrun', 'rosrun', 'myrobot', 'voicesub') # 打开命令接收
-
- rospy.loginfo("send %s", self.msg)
-
- # 前进
- def ButtonGoCallback(self):
- self.msg = String("go")
- self.qtkeypub.publish(self.msg)
- rospy.loginfo("send %s", self.msg)
-
- # 后退
- def ButtonBackCallback(self):
- self.msg = String("back")
- self.qtkeypub.publish(self.msg)
- rospy.loginfo("send %s", self.msg)
-
- # 左转
- def ButtonLeftCallback(self):
- self.msg = String("left")
- self.qtkeypub.publish(self.msg)
- rospy.loginfo("send %s", self.msg)
-
- # 右转
- def ButtonRightCallback(self):
- self.msg = String("right")
- self.qtkeypub.publish(self.msg)
- rospy.loginfo("send %s", self.msg)
-
-
- if __name__ == '__main__':
- app = QtWidgets.QApplication(sys.argv)
- ui = MyCallback()
- ui.show()
- sys.exit(app.exec_())
QT上位机在接收图片时,产生了花屏现象,在请教了大佬之后,得到问题的根源,我得到的图片是640x480的,而我设置QtGui.QImage的大小确为480x320,小于我的图片。解决方法就是显示的大小得到的图片大小应当和QT设置显示的大小保持一致。
在程序中加入rospy.spin()函数会导致QT界面黑屏,推测可能是一直进入回调函数中,无法显示qt界面,删除该句即可。
qt的学习还是比较有意思的,也是我第一次设计上位机,这必须得记录下来。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。