草庐IT

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

努力&&奋斗 2023-07-09 原文

文章目录

目录

文章目录

前言

一、准备工作

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的学习还是比较有意思的,也是我第一次设计上位机,这必须得记录下来。

有关基于ROS的语音控制机器人(二):上位机的实现的更多相关文章

  1. Ruby Readline 在向上箭头上使控制台崩溃 - 2

    当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby​​安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少

  2. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  3. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  6. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  7. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  10. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

随机推荐