目录
文章目录
前言
一、准备工作
1.python工作环境
2.ros环境
3.QT designer
二、界面程序设计
1.界面设计
2.ui文件转py文件
三、上位机程序编写
1.具体思路
2.具体实现
3.遇到的问题
(1)花屏问题
(2)rospy.spin()问题
四、运行结果
1.打开摄像头
2.打开人脸识别
3.打开语音控制
4.打开键盘控制
5.上位机控制
结语
本文在基本功能实现的基础上,基于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的学习还是比较有意思的,也是我第一次设计上位机,这必须得记录下来。