基于ROS的语音控制机器人(二):上位机的实现

文章目录

目录

文章目录

前言

一、准备工作

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


提示:以下是本篇文章正文内容,下面案例可供参考

一、准备工作

1.python工作环境

本文选择的python环境为pycharm2021,安装教程在这里不再多说

所需要的核心库有PyQt5,rospy和opencv

2.ros环境

由于换了新电脑,安装了ubuntu20.04的版本,因此ros的版本更换为了noetic,但具体程序并没有太大变化。

3.QT designer

安装QT designer可以方便我们更好的设计界面,具体安装流程不在多说

二、界面程序设计

1.界面设计

打开QT designer,选择mainwindow,然后通过想要的控件,设计一个自己喜欢的界面即可,本文

设计的界面如下所示:

具有8个按钮和一个QLabel控件,点击保存即可生成ui文件。

2.ui文件转py文件

通过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文件,准备开始编写上位机。

1.具体思路

        上一篇内容提到过,pc和树莓派之间的通讯是通过发布和订阅主题实现的,因此我们可以通过按下按钮发送clicked信号,在相关联的槽去实现命令的发布即可通过上位机实现通信。

        而想要通过上位机启动按键控制和语音控制,在程序里编写是十分麻烦的,我们可以利用已经编写好的程序,利用python中os.fork()函数创建子进程,然后利用os.execl()去调用相关可执行文件,即可实现功能。

2.具体实现

#!/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_())

3.遇到的问题

(1)花屏问题

QT上位机在接收图片时,产生了花屏现象,在请教了大佬之后,得到问题的根源,我得到的图片是640x480的,而我设置QtGui.QImage的大小确为480x320,小于我的图片。解决方法就是显示的大小得到的图片大小应当和QT设置显示的大小保持一致。

(2)rospy.spin()问题

在程序中加入rospy.spin()函数会导致QT界面黑屏,推测可能是一直进入回调函数中,无法显示qt界面,删除该句即可。

 四、运行结果

 1.打开摄像头

2.打开人脸识别 

 3.打开语音控制

4.打开键盘控制

 5.上位机控制


结语

qt的学习还是比较有意思的,也是我第一次设计上位机,这必须得记录下来。

Guess you like

Origin blog.csdn.net/deku_desi/article/details/118878715