当前位置:   article > 正文

基于ROS的语音控制机器人(一):基本功能的实现_基于语音控制的ros小车

基于语音控制的ros小车

文章目录


前言

用来记录一下我的ros学习过程,小车主要用到的有stm32和树莓派。

本文需要一些ros基础,源码分享https://gitee.com/sy_run/myroscar


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

一、ubuntu16.04和树莓派安装ROS-kinetic

关于安装ros,网上有很多教程,这里就省略不讲。

不过ubuntu安装ros时,rosdep init和rosdep update很容易出现问题 ,在这里列出一个有效的 解决方法。

首先使用以下指令:

  1. cd /etc/ros/rosdep/sources.list.d
  2. sudo gedit 20-default.list

此时20-default.list内容如下

打开这些网址,将五个文件下载下来,拷贝到/etc/ros/rosdep文件夹下。然后sudo rosdep update即可成功。

树莓派安装ros,如果是ubuntun mate安装ros,需要一个显示器,安装过程一样;如果是树莓派自己的系统安装ros,这个过程十分麻烦,建议有条件的可以买一个别人的镜像。

安装成功后记得打开终端输入roscore命令测试以下。

二、树莓派和PC机之间的ros通信

二者之间通过网络进行通信,首先需要确定pc和树莓派各自的ip地址,确定PC机和树莓派谁作为ros的master,本文以PC端作为ros的master为例。

1.修改环境变量

pc端和树莓派都输入sudo ~/.bashrc,在最后一行填入 

  1. export ROS_MASTER_URI=https://${你的master主机的ip地址}:11311 //声明master主机ip地址
  2. export ROS_HOSTNAME=${本机ip地址}

此时只需要在master主机启动roscore即可。

2.数据通信

树莓派和pc机之间的通信主要是利用topic,pc端节点向topic发送数据,树莓派订阅该topic即可收到数据。在这里列出发布者和订阅者的C++版通用代码模板。

发布者:

  1. //头文件部分
  2. #include "ros/ros.h"
  3. #include "std_msgs/String.h"
  4. #include <sstream>
  5. int main(int argc,char** argv)
  6. {
  7. //初始化部分,前两个是命令行参数,第三个参数是发布者的节点名称,该名称必须唯一,不能重复
  8. ros::init(argc,argv,"publish_name");
  9. //创建节点句柄,方便对节点管理
  10. ros::NodeHandle n;
  11. //注册一个发布者,并告诉系统该节点将会发布以topic为话题的String类型消息。第二个参数表示消息发布队列大小
  12. ros::Publisher pub = n.advertise<std_msgs::String>("topic",1000);
  13. //设置循环频率单位HZ,调用Rate::sleep()时会根据该频率休眠相应的时间,不需要时可以省略
  14. ros::Rate loop_rate(10);//不必要可省略
  15. //节点正常时则一直循环,发生异常会返回false跳出循环
  16. while(ros::ok())
  17. {
  18. //初始化std_msgs::String消息
  19. std_msgs::String pubmsgs; //发布的消息
  20. std::stringssteram tempmsg;
  21. //注意消息的赋值方式,不能使用std::string tempmsgs直接赋值,会出现错误!
  22. tempmsg << "此处为发布的内容";
  23. pubmsgs.data = tempmsg.str();
  24. ROS_INFO("%s",pubmsgs.data.c_str()); //后面跟string类型数据会出现乱码情况!
  25. //该函数用来处理回调函数,为了功能无误,无论是否用到回调函数默认都加上
  26. ros::spinOnce();
  27. loop_rate.sleep();//不必要可省略
  28. }
  29. return 0;
  30. }

订阅者:

由于C++支持C语言,所以可以选择两种方式编写,任选一种

  1. //C语言风格
  2. #include "ros/ros.h"
  3. #include "std_msgs/String.h"
  4. //回调函数
  5. void SubCallback(const std_msgs::String::ConstPtr& msg)
  6. {
  7. ROS_INFO("Receive: %s",msg->data.c_str());
  8. }
  9. int main(int argc,char** argv)
  10. {
  11. ros::init(argc,argv,"subscribe");
  12. ros::NodeHandle n;
  13. //创建订阅者,订阅topic话题,注册回调函数
  14. ros::Subscriber sub = n.subscribe("topic",1000,SubCallback);
  15. //循环等待回调函数,spinOnce只执行一次,spin循环执行
  16. ros::spin();
  17. return 0;
  18. }
  1. //C++风格
  2. #include "ros/ros.h"
  3. #include "std_msgs/String.h"
  4. Class TopicSub{
  5. private:
  6. ros::NodeHandle n;
  7. ros::Subscriber sub;
  8. public:
  9. //构造函数,完成初始化
  10. TopicSub()
  11. {
  12. sub = n.subscribe("topic",1000,&TopicSub::SubCallback,this);
  13. }
  14. ~TopicSub(){}
  15. //回调函数
  16. void SubCallback(const std_msgs::String::ConstPtr& msg)
  17. {
  18. ROS_INFO("Receive: %s",msg->data.c_str());
  19. }
  20. };
  21. int main(int argc,char** argv)
  22. {
  23. ros::init(argc,argv,"subscribe");
  24. TopicSub subscriber;
  25. ros::spin()
  26. return 0;
  27. }

三、科大讯飞sdk下载

1.登录科大讯飞官网www.xfyun.com,注册登录后创建应用。

2.点击左上角sdk下载,选择应用,平台和功能

3.点击sdk下载并复制到虚拟机下

4.进入samples目录下,选择32位或64位的脚本运行即可编译

5.cd ../../bin,执行可执行文件即可看到运行结果

四、树莓派和STM32串口通信

关于树莓派串口的配置,网上有许多教程资源,在这里就略过不写;

树莓派和stm32之间通过串口进行通信,树莓派发送指令给串口,stm32接收后则执行命令。

由于命令长度不同,因此使用串口空闲中断进行接收,以下是串口配置代码

  1. //usart1.c
  2. #include "usart.h"
  3. #include "string.h"
  4. #include "analyse.h"
  5. void Usart1_Init(u32 baud)
  6. {
  7. GPIO_InitTypeDef GPIO_InitStruct; /* 定义GPIO模块结构体类型变量 */
  8. USART_InitTypeDef USART_InitStruct; /* 定义USART模块结构体类型变量 */
  9. NVIC_InitTypeDef NVIC_InitStructure; /* 定义NVIC中断结构体类型变量 */
  10. // 设置UASART模块功能管脚
  11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); /* 使能GPIOA端口模块时钟 */
  12. // USART1_RX(PA10)浮空输入
  13. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
  14. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  15. GPIO_Init(GPIOA,&GPIO_InitStruct);
  16. // USART1_TX(PA9)复用推挽输出
  17. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
  18. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
  19. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  20. GPIO_Init(GPIOA,&GPIO_InitStruct);
  21. // 设置USART模块工作模式
  22. RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
  23. USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能USART1模块发送和接收
  24. USART_InitStruct.USART_BaudRate = baud; // 设置USART1模块波特率
  25. USART_InitStruct.USART_WordLength = USART_WordLength_8b; // USART1模块8位数据长度
  26. USART_InitStruct.USART_Parity = USART_Parity_No; // USART1模块禁止奇偶校验
  27. USART_InitStruct.USART_StopBits = USART_StopBits_1; // USART1模块1位停止位
  28. USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 禁止硬件流
  29. USART_Init(USART1,&USART_InitStruct); // 参数初始化USART_3模块
  30. // USART模块NVIC 配置
  31. NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  32. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ; // 抢占优先级等级为1
  33. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 响应优先级等级为3
  34. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断源NVIC中断
  35. NVIC_Init(&NVIC_InitStructure); // 使用NVIC_InitStructure 参数初始化NVIC控制器
  36. USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收中断
  37. USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 开启串口接受中断
  38. USART_Cmd(USART1, ENABLE); // 使能串口1
  39. }
  40. //串口发送
  41. void Usart1_Send_Str(u8 *Data)
  42. {
  43. while( *Data != '\0')
  44. {
  45. while( !USART_GetFlagStatus(USART1,USART_FLAG_TC))
  46. {
  47. } //发送完成
  48. USART_SendData(USART1,*Data);
  49. Data++;
  50. }
  51. }
  52. //中断服务函数
  53. u8 tempbuff[128];//串口缓冲数组
  54. u8 u1count = 0;
  55. u8 rxflag = 0;//接收完成标志
  56. void USART1_IRQHandler(void)
  57. {
  58. int a;
  59. if(rxflag == 0 && USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断
  60. {
  61. tempbuff[u1count++] = USART_ReceiveData(USART1);
  62. }
  63. if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)//空闲中断
  64. {
  65. a = USART1->DR;
  66. a = USART1->SR;
  67. rxflag = 1;//接受标志置1
  68. memset(command_buff,0,sizeof(command_buff));
  69. memcpy(command_buff,tempbuff,u1count);
  70. memset(tempbuff,0,sizeof(tempbuff));
  71. u1count = 0;
  72. }
  73. }
  1. //usart1.h
  2. #ifndef __USART_H_
  3. #define __USART_H_
  4. #include "sys.h"
  5. extern u8 rxflag;
  6. void Usart1_Init(u32 baud);
  7. void Usart1_Send_Str(u8 *Data);
  8. #endif

stm32命令解析代码:

  1. //analyse.c
  2. #include "analyse.h"
  3. #include "usart.h"
  4. #include "string.h"
  5. #include "motor.h"
  6. u8 command_buff[128] = {0};
  7. PidObject car_left;
  8. PidObject car_right;
  9. int Robot_Command(void)//command_buff命令解析函数
  10. {
  11. if( (sizeof(command_buff) != 0) && (rxflag == 1) ) //如果接收到数据
  12. {
  13. rxflag = 0;
  14. if(strcmp((const char*)command_buff,"go") == 0) return GO;
  15. else if(strcmp((const char*)command_buff,"back") == 0) return BACK;
  16. else if(strcmp((const char*)command_buff,"left") == 0) return LEFT;
  17. else if(strcmp((const char*)command_buff,"right") == 0) return RIGHT;
  18. else if(strcmp((const char*)command_buff,"stop") == 0) return STOP;
  19. }
  20. return STOP;
  21. }
  22. void Robot_Work(int command)
  23. {
  24. switch(command)
  25. {
  26. case STOP:Motor_Stop();break;
  27. case GO:Motor_Forward(); break;
  28. case BACK:Motor_Back(); break;
  29. case LEFT:Motor_Left(); break;
  30. case RIGHT:Motor_Right(); break;
  31. default:break;
  32. }
  33. }
  1. #ifndef __ANALYSE_H_
  2. #define __ANALYSE_H_
  3. #include "sys.h"
  4. #include "pid.h"
  5. #define STOP 0 //制动
  6. #define GO 1 //前进
  7. #define BACK 2 //后退
  8. #define LEFT 3 //左转
  9. #define RIGHT 4 //右转
  10. extern u8 command_buff[128];
  11. extern PidObject car_left;
  12. extern PidObject car_right;
  13. int Robot_Command(void);//command_buff命令解析函数
  14. void Robot_Work(int command);
  15. #endif

五、opencv实现人脸识别

在PC端和树莓派分别创建ros工作空间,用来存放代码

1.pc端接收图像topic数据

  1. #include <ros/ros.h>
  2. #include <image_transport/image_transport.h>
  3. #include <opencv2/highgui/highgui.hpp>
  4. #include <opencv2/opencv.hpp>
  5. #include <cv_bridge/cv_bridge.h>
  6. #include <iostream>
  7. #include <string>
  8. using namespace cv;
  9. using namespace std;
  10. CascadeClassifier face_detector;
  11. string filepath = "/opt/ros/kinetic/share/OpenCV-3.3.1-dev/haarcascades/haarcascade_frontalface_alt.xml";
  12. class ImageShow{
  13. private:
  14. ros::NodeHandle nh; //定义ros句柄
  15. image_transport::ImageTransport it; //
  16. image_transport::Subscriber FaceShow; //定义订阅者
  17. cv_bridge::CvImagePtr cv_ptr;//定义一个cvimage指针实例
  18. public:
  19. ImageShow():it(nh)
  20. {
  21. FaceShow = it.subscribe("image_compressed",1,&ImageShow::imageCallback,this,image_transport::TransportHints("compressed"));//选择图像压缩,否则帧数会过低
  22. cv::namedWindow("pi_image");
  23. }
  24. ~ImageShow()
  25. {
  26. cv::destroyWindow("pi_image");
  27. }
  28. void imageCallback(const sensor_msgs::ImageConstPtr& msg)
  29. {
  30. try
  31. {
  32. cv_ptr = cv_bridge::toCvCopy(msg,sensor_msgs::image_encodings::BGR8);
  33. }
  34. catch(cv_bridge::Exception& e)
  35. {
  36. ROS_ERROR("exception:%s",e.what());
  37. }
  38. if(!face_detector.load(filepath))
  39. {
  40. cout<<"could not load"<<endl;
  41. }
  42. Mat gray_src;
  43. cvtColor(cv_ptr->image,gray_src,COLOR_BGR2GRAY);
  44. equalizeHist(gray_src,gray_src);
  45. vector<Rect> faces;
  46. face_detector.detectMultiScale(gray_src,faces,1.1,3,0,Size(30,30));
  47. for(size_t t=0;t<faces.size();t++)
  48. {
  49. rectangle(cv_ptr->image,faces[t],Scalar(255,255,0),2,8,0);
  50. }
  51. image_show(cv_ptr->image);
  52. }
  53. void image_show(cv::Mat img)
  54. {
  55. cv::imshow("pi_image",img);
  56. cv::waitKey(1);
  57. }
  58. };
  59. int main(int argc,char** argv)
  60. {
  61. ros::init(argc,argv,"imageSub_node");
  62. ImageShow test;
  63. ros::spin();
  64. }

2.树莓派端发布图像topic

  1. #include <iostream>
  2. #include <ros/ros.h>
  3. #include <cv_bridge/cv_bridge.h> // 将ROS下的sensor_msgs/Image消息类型转化为cv::Mat数据类型
  4. #include <sensor_msgs/image_encodings.h> // ROS下对图像进行处理
  5. #include <image_transport/image_transport.h> // 用来发布和订阅图像信息
  6. #include <opencv2/core/core.hpp>
  7. #include <opencv2/highgui/highgui.hpp>
  8. #include <opencv2/imgproc/imgproc.hpp>
  9. #include <opencv2/videoio.hpp>
  10. int main(int argc, char** argv)
  11. {
  12. ros::init(argc, argv, "imageGet_node"); // ros初始化,定义节点名为imageGet_node
  13. ros::NodeHandle nh; // 定义ros句柄
  14. image_transport::ImageTransport it(nh); // 类似ROS句柄
  15. image_transport::Publisher image_pub = it.advertise("image_compressed", 1); // 发布话题名/cameraImage
  16. ros::Rate loop_rate(50); // 设置刷新频率,Hz
  17. cv::Mat imageRaw; // 原始图像保存
  18. cv::VideoCapture capture(0); // 创建摄像头捕获,并打开摄像头0(一般是0,2....)
  19. if(capture.isOpened() == 0) // 如果摄像头没有打开
  20. {
  21. std::cout << "Read camera failed!" << std::endl;
  22. return -1;
  23. }
  24. while(nh.ok())
  25. {
  26. capture.read(imageRaw); // 读取当前图像到imageRaw
  27. cv::Size dsize = cv::Size(imageRaw.cols*0.5, imageRaw.rows*0.5);
  28. cv::Mat img2 = cv::Mat(dsize, CV_32S);
  29. cv::resize(imageRaw,img2,dsize);
  30. //cv::imshow("veiwer", imageRaw); // 将图像输出到窗口
  31. sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", img2).toImageMsg(); // 图像格式转换
  32. image_pub.publish(msg); // 发布图像信息
  33. ros::spinOnce(); // 保证完整
  34. loop_rate.sleep(); // 照应上面设置的频率
  35. if(cv::waitKey(1) >= 0) // 延时ms,按下任何键退出(必须要有waitKey,不然是看不到图像的)
  36. break;
  37. }
  38. }

六、键盘控制小车

1.PC端键盘控制发布

  1. #include <termios.h>
  2. #include <signal.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <sys/poll.h>
  6. #include <boost/thread/thread.hpp>
  7. #include <ros/ros.h>
  8. #include <std_msgs/String.h>
  9. #include <sstream>
  10. #define KEY_W 0X77 //w键
  11. #define KEY_A 0X61 //a键
  12. #define KEY_S 0X73 //s
  13. #define KEY_D 0X64 //d
  14. #define KEY_P 0X70 //p
  15. struct termios cooked,raw;
  16. int fd = 0;
  17. class KeyControlNode{
  18. private:
  19. std_msgs::String msg;
  20. ros::NodeHandle n;
  21. ros::Publisher pub;
  22. public:
  23. KeyControlNode()
  24. {
  25. pub = n.advertise<std_msgs::String>("keycmd",1000);//向“keycmd”主题发布消息
  26. }
  27. ~KeyControlNode(){}
  28. void keyboardloop()
  29. {
  30. char key;
  31. bool dirty = false;
  32. tcgetattr(fd,&cooked);
  33. memcpy(&raw,&cooked,sizeof(struct termios));
  34. raw.c_lflag &= ~(ICANON|ECHO);
  35. raw.c_cc[VEOL] = 1;
  36. raw.c_cc[VEOF] = 2;
  37. tcsetattr(fd,TCSANOW,&raw);
  38. puts("WASD 控制移动,P停止\n");
  39. struct pollfd ufd;
  40. ufd.fd = fd;
  41. ufd.events = POLLIN;
  42. while(1)
  43. {
  44. boost::this_thread::interruption_point();
  45. int num;
  46. std::stringstream ss;
  47. //利用boost库创建线程
  48. if( (num = poll(&ufd,1,250)) < 0)
  49. {
  50. perror("poll():");
  51. exit(0);
  52. }
  53. else if(num > 0)
  54. {
  55. if(read(fd,&key,1) < 0)
  56. {
  57. perror("read");
  58. exit(1);
  59. }
  60. }
  61. switch(key)
  62. {
  63. case KEY_W:
  64. ss<<"go";
  65. msg.data = ss.str();
  66. dirty = true;
  67. break;
  68. case KEY_A:
  69. ss<<"left";
  70. msg.data = ss.str();
  71. dirty = true;
  72. break;
  73. case KEY_S:
  74. ss<<"back";
  75. msg.data = ss.str();
  76. dirty = true;
  77. break;
  78. case KEY_D:
  79. ss<<"right";
  80. msg.data = ss.str();
  81. dirty = true;
  82. break;
  83. case KEY_P:
  84. ss<<"stop";
  85. msg.data = ss.str();
  86. dirty = true;
  87. break;
  88. default:
  89. ss<<"";
  90. msg.data = ss.str();
  91. dirty = true;
  92. break;
  93. }
  94. key = 0;
  95. ROS_INFO("%s",msg.data.c_str());
  96. pub.publish(msg);//消息发布
  97. }
  98. }
  99. };
  100. int main(int argc,char** argv)
  101. {
  102. ros::init(argc,argv,"key",ros::init_options::AnonymousName|ros::init_options::NoSigintHandler);
  103. KeyControlNode tbk;
  104. //线程
  105. boost::thread t = boost::thread(boost::bind(&KeyControlNode::keyboardloop,&tbk));
  106. ros::spin();
  107. t.interrupt();
  108. t.join();
  109. tcsetattr(fd,TCSANOW,&cooked);
  110. }

2.树莓派订阅主题获取命令

  1. #include <stdio.h>
  2. #include "wiringPi.h"
  3. #include <stdlib.h>
  4. #include "wiringSerial.h"//wiringPi库
  5. #include <ros/ros.h>
  6. #include <std_msgs/String.h>
  7. #include <string>
  8. class SerialKeyboard{
  9. private:
  10. int fd;
  11. ros::NodeHandle n;
  12. ros::Subscriber sub;
  13. std::string oldmsg;
  14. public:
  15. SerialKeyboard(int baund,const char* dev_name)//构造函数初始化波特率和设备号
  16. {
  17. fd = serialOpen(dev_name,baund);
  18. if(wiringPiSetup()<0)
  19. {
  20. printf("Initialize fail!\r\n");
  21. }
  22. if(fd < 0)
  23. {
  24. printf("open serial fail!\r\n");
  25. }
  26. //订阅“keycmd”话题接收命令
  27. sub = n.subscribe("keycmd",1000,&SerialKeyboard::SerialkeyboardCallback,this);
  28. oldmsg = " ";
  29. }
  30. ~SerialKeyboard()
  31. {
  32. serialClose(fd);
  33. }
  34. void SerialkeyboardCallback(const std_msgs::String::ConstPtr& msg)
  35. {
  36. ROS_INFO("pc send:%s",msg->data.c_str());
  37. if(msg->data.c_str() != oldmsg)
  38. {
  39. oldmsg = msg->data.c_str();
  40. serialPuts(fd,msg->data.c_str()); //串口发送给stm32
  41. }
  42. }
  43. };
  44. int main(int argc,char** argv)
  45. {
  46. ros::init(argc,argv,"keycmd");
  47. SerialKeyboard key(115200,"/dev/ttyAMA0");//波特率115200,设备号ttyAMA0
  48. ros::spin();
  49. return 0;
  50. }

六、PC语音控制树莓派

最后就是整合,通过语音去控制上述功能

1.PC端语音识别发送命令

将语音合成的语音放在工作空间新建的music目录下。

语音合成功能通过运行sdk的samples中的tts_online_sample中的.sh脚本编译,然后在bin目录执行可执行文件即可生成wav文件,其语音内容和文件名在tts_online_sample.c文件第151行进行修改

修改完成后,就能在bin目录获得wav文件,拷贝到工作空间的music目录下即可

将讯飞sdk的语音听写代码复制到工作空间里,.h头文件放在工作空间的include目录下,.c文件放在src目录下,修改iat_online_record_sample.c文件名为voice.cpp(可以任取)

 修改相关代码,添加ros模板,使其作为发布者发布语音识别的结果

  1. /*
  2. * 语音听写(iFly Auto Transform)技术能够实时地将语音转换成对应的文字。
  3. */
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. #include <unistd.h>
  8. #include "ros_image/qisr.h"
  9. #include "ros_image/msp_cmn.h"
  10. #include "ros_image/msp_errors.h"
  11. #include "ros_image/speech_recognizer.h"
  12. #include "ros/ros.h"
  13. #include "std_msgs/String.h"
  14. #define FRAME_LEN 640
  15. #define BUFFER_SIZE 4096
  16. static void show_result(char *string, char is_over)//显示识别结果
  17. {
  18. printf("\rResult: [ %s ]", string);
  19. if(is_over)
  20. putchar('\n');
  21. }
  22. static char *g_result = NULL;
  23. static unsigned int g_buffersize = BUFFER_SIZE;
  24. std_msgs::String msgs;//定义消息全局变量
  25. void on_result(const char *result, char is_last)
  26. {
  27. if (result) {
  28. size_t left = g_buffersize - 1 - strlen(g_result);
  29. size_t size = strlen(result);
  30. if (left < size) {
  31. g_result = (char*)realloc(g_result, g_buffersize + BUFFER_SIZE);
  32. if (g_result)
  33. g_buffersize += BUFFER_SIZE;
  34. else {
  35. ROS_INFO("mem alloc failed\n");
  36. return;
  37. }
  38. }
  39. strncat(g_result, result, size);
  40. show_result(g_result, is_last);
  41. if(g_result != "")
  42. msgs.data = g_result;
  43. }
  44. }
  45. void on_speech_begin()
  46. {
  47. if (g_result)
  48. {
  49. free(g_result);
  50. }
  51. g_result = (char*)malloc(BUFFER_SIZE);
  52. g_buffersize = BUFFER_SIZE;
  53. memset(g_result, 0, g_buffersize);
  54. ROS_INFO("Start Listening...\n");
  55. }
  56. void on_speech_end(int reason)
  57. {
  58. if (reason == END_REASON_VAD_DETECT)
  59. ROS_INFO("\nSpeaking done \n");
  60. else
  61. ROS_INFO("\nRecognizer error %d\n", reason);
  62. }
  63. /* demo recognize the audio from microphone */
  64. static void demo_mic(const char* session_begin_params)
  65. {
  66. int errcode;
  67. int i = 0;
  68. struct speech_rec iat;
  69. struct speech_rec_notifier recnotifier = {
  70. on_result,
  71. on_speech_begin,
  72. on_speech_end
  73. };
  74. errcode = sr_init(&iat, session_begin_params, SR_MIC, &recnotifier);
  75. if (errcode) {
  76. ROS_INFO("speech recognizer init failed\n");
  77. return;
  78. }
  79. errcode = sr_start_listening(&iat);
  80. if (errcode) {
  81. ROS_INFO("start listen failed %d\n", errcode);
  82. }
  83. /* demo 15 seconds recording */
  84. while(i++ < 5)
  85. sleep(1);
  86. errcode = sr_stop_listening(&iat);
  87. if (errcode) {
  88. ROS_INFO("stop listening failed %d\n", errcode);
  89. }
  90. sr_uninit(&iat);
  91. }
  92. /* main thread: start/stop record ; query the result of recgonization.
  93. * record thread: record callback(data write)
  94. * helper thread: ui(keystroke detection)
  95. */
  96. int main(int argc, char** argv)
  97. {
  98. int ret = MSP_SUCCESS;
  99. /* login params, please do keep the appid correct */
  100. const char* login_params = "appid = ${你自己的ID}, work_dir = .";
  101. int aud_src = 0; /* from mic or file */
  102. /*
  103. * See "iFlytek MSC Reference Manual"
  104. */
  105. const char* session_begin_params =
  106. "sub = iat, domain = iat, language = zh_cn, "
  107. "accent = mandarin, sample_rate = 16000, "
  108. "result_type = plain, result_encoding = utf8";
  109. /* Login first. the 1st arg is username, the 2nd arg is password
  110. * just set them as NULL. the 3rd arg is login paramertes
  111. * */
  112. ret = MSPLogin(NULL, NULL, login_params);
  113. if (MSP_SUCCESS != ret) {
  114. ROS_INFO("MSPLogin failed , Error code %d.\n",ret);
  115. MSPLogout(); // Logout...
  116. }
  117. ros::init(argc,argv,"VoiceRecognize");
  118. ros::NodeHandle n;
  119. //发布消息到“voicewords”主题
  120. ros::Publisher voicepub = n.advertise<std_msgs::String>("voicewords",1000);
  121. while(ros::ok())
  122. {
  123. ROS_INFO("Demo recognizing the speech from microphone\n");
  124. ROS_INFO("Speak in 5 seconds\n");
  125. demo_mic(session_begin_params);
  126. ROS_INFO("5 sec passed\n");
  127. voicepub.publish(msgs);//发布数据
  128. }
  129. ros::spin();
  130. MSPLogout();//退出讯飞云登录
  131. return 0;
  132. }

新建voicecmd.cpp使其作为订阅者接收语音识别数据,并处理,代码如下图

  1. #include "ros/ros.h"
  2. #include "std_msgs/String.h"
  3. #include "stdlib.h"
  4. #include "stdio.h"
  5. #include "unistd.h"
  6. #include "signal.h"
  7. #include "sys/types.h"
  8. #include "sys/wait.h"
  9. class VoicecmdSub{
  10. private:
  11. ros::NodeHandle n;
  12. std_msgs::String cmd;
  13. ros::Subscriber voicesub; //定义订阅着
  14. ros::Publisher cmdpub; //定义发布者
  15. std::string oldmsg;
  16. bool opencam; //摄像头打开标志
  17. bool openface; //人脸识别打开标志
  18. bool openkeyboard; //键盘控制打开标志
  19. pid_t pid_cam; //摄像头子进程号
  20. pid_t pid_face; //人脸识别子进程号
  21. pid_t pid_keyboard; //键盘控制子进程号
  22. public:
  23. VoicecmdSub()
  24. {
  25. cmdpub = n.advertise<std_msgs::String>("keycmd",1000);//发布到“keycmd”上
  26. voicesub = n.subscribe("voicewords",1000,&VoicecmdSub::voicecmdCallback,this);//订阅“voicewords”主题
  27. oldmsg = "";
  28. opencam = false;
  29. openface = false;
  30. openkeyboard = false;
  31. pid_cam = -1;
  32. pid_face = -1;
  33. pid_keyboard = -1;
  34. }
  35. ~VoicecmdSub(){}
  36. void voicecmdCallback(const std_msgs::String::ConstPtr& msg)
  37. {
  38. std::string msgs = msg->data.c_str();//这里语音转发后使用std_msgs::String会出现乱码情况,所以采取std::string
  39. std::stringstream ss;
  40. ss<<"";
  41. if(msgs != oldmsg)
  42. {
  43. oldmsg = msgs;
  44. std::cout<<"I Heard:"<<msgs<<std::endl;
  45. //通过识别的命令执行对应的操作,利用system函数创建进程播放合成的语音
  46. if(msgs == "好兄弟在吗?")
  47. {
  48. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/hxdwz.wav");
  49. }
  50. else if(msgs == "你能做什么?")
  51. {
  52. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/allcmd.wav");
  53. }
  54. else if(msgs == "前进。")
  55. {
  56. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/cmdrcv.wav");
  57. ss<<"go";
  58. }
  59. else if(msgs == "后退。")
  60. {
  61. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/cmdrcv.wav");
  62. ss<<"back";
  63. }
  64. else if(msgs == "左转。")
  65. {
  66. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/cmdrcv.wav");
  67. ss<<"left";
  68. }
  69. else if(msgs == "右转。")
  70. {
  71. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/cmdrcv.wav");
  72. ss<<"right";
  73. }
  74. else if(msgs == "打开摄像头。")
  75. {
  76. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/opencam.wav");
  77. ss<<"opencam";
  78. if(!opencam)
  79. {
  80. opencam = true;
  81. pid_cam = fork();//创建子进程执行操作
  82. if(pid_cam == 0)
  83. {
  84. //利用execl函数,使用rosrun指令 execl("/opt/ros/kinetic/bin/rosrun","rosrun","ros_image","imageSub",NULL);
  85. }
  86. }
  87. }
  88. else if(msgs == "关闭摄像头。")
  89. {
  90. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/closecam.wav");
  91. ss<<"closecam";
  92. if(opencam)
  93. {
  94. opencam = false;
  95. if(pid_cam > 0)
  96. {
  97. kill(pid_cam,SIGKILL);//杀死子进程
  98. wait(NULL);
  99. }
  100. }
  101. }
  102. else if(msgs == "打开人脸识别。")
  103. {
  104. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/faceopen.wav");
  105. ss<<"opencam";
  106. if(!openface)
  107. {
  108. openface = true;
  109. pid_face = fork();
  110. if(pid_face == 0)
  111. {
  112. execl("/opt/ros/kinetic/bin/rosrun","rosrun", "ros_image", "face",NULL);
  113. }
  114. }
  115. }
  116. else if(msgs == "关闭人脸识别。")
  117. {
  118. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/faceclose.wav");
  119. ss<<"closecam";
  120. if(openface)
  121. {
  122. openface = false;
  123. if(pid_face > 0)
  124. {
  125. kill(pid_face,SIGKILL);
  126. wait(NULL);
  127. }
  128. }
  129. }
  130. else if(msgs == "打开键盘控制。")
  131. {
  132. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/keyopen.wav");
  133. if(!openkeyboard)
  134. {
  135. openkeyboard = true;
  136. pid_keyboard = fork();
  137. if(pid_keyboard == 0)
  138. {
  139. execl("/opt/ros/kinetic/bin/rosrun","rosrun","ros_image","key_control",NULL);
  140. }
  141. }
  142. }
  143. else if(msgs == "关闭键盘控制。")
  144. {
  145. system("mplayer /home/mzq/catkin_ws/src/ros_image/music/keyclose.wav");
  146. if(openkeyboard)
  147. {
  148. openkeyboard = false;
  149. if(pid_keyboard > 0)
  150. {
  151. kill(pid_keyboard,SIGKILL);
  152. wait(NULL);
  153. }
  154. }
  155. }
  156. cmd.data = ss.str();
  157. cmdpub.publish(cmd);//发布消息
  158. }
  159. }
  160. };
  161. int main(int argc,char** argv)
  162. {
  163. ros::init(argc,argv,"voicecmdpub");
  164. VoicecmdSub it;
  165. ros::spin();
  166. return 0;
  167. }

2.树莓派端接收指令

利用上文的按键控制订阅的代码,修改一下

  1. #include <stdio.h>
  2. #include "wiringPi.h"
  3. #include <stdlib.h>
  4. #include "wiringSerial.h"
  5. #include <ros/ros.h>
  6. #include <std_msgs/String.h>
  7. #include <string>
  8. #include "sys/types.h"
  9. #include "unistd.h"
  10. #include "sys/wait.h"
  11. #include "signal.h"
  12. class SerialKeyboard{
  13. private:
  14. int fd;
  15. ros::NodeHandle n;
  16. ros::Subscriber sub;
  17. std::string oldmsg;
  18. bool opencam;
  19. pid_t pid_cam;
  20. public:
  21. SerialKeyboard(int baund,const char* dev_name)
  22. {
  23. fd = serialOpen(dev_name,baund);
  24. if(wiringPiSetup()<0)
  25. {
  26. printf("Initialize fail!\r\n");
  27. }
  28. if(fd < 0)
  29. {
  30. printf("open serial fail!\r\n");
  31. }
  32. sub = n.subscribe("keycmd",1000,&SerialKeyboard::SerialkeyboardCallback,this);
  33. oldmsg = " ";
  34. opencam = false;
  35. pid_cam = -1;
  36. }
  37. ~SerialKeyboard()
  38. {
  39. serialClose(fd);
  40. }
  41. void SerialkeyboardCallback(const std_msgs::String::ConstPtr& msg)
  42. {
  43. ROS_INFO("pc send:%s",msg->data.c_str());
  44. if(msg->data.c_str() != oldmsg)
  45. {
  46. oldmsg = msg->data.c_str();
  47. if(oldmsg == "opencam")//打开摄像头,打开人脸识别都是这个命令
  48. {
  49. if(!opencam)
  50. {
  51. opencam = true;
  52. pid_cam = fork();//创建进程
  53. if(pid_cam == 0)
  54. {
  55. execl("/opt/ros/kinetic/bin/rosrun","rosrun", "mycamera","image_Get",NULL);
  56. }
  57. }
  58. }
  59. else if(oldmsg == "closecam")//关闭摄像头或者关闭人脸识别
  60. {
  61. if(opencam)
  62. {
  63. //pid_cam = -1;
  64. opencam = false;
  65. if(pid_cam > 0)
  66. {
  67. kill(pid_cam,SIGKILL);//杀死子进程
  68. wait(NULL);
  69. }
  70. }
  71. }
  72. else
  73. serialPuts(fd,msg->data.c_str());
  74. }
  75. }
  76. };
  77. int main(int argc,char** argv)
  78. {
  79. ros::init(argc,argv,"keycmd");
  80. SerialKeyboard key(115200,"/dev/ttyAMA0");
  81. ros::spin();
  82. return 0;
  83. }

该处使用的url网络请求的数据。

3.修改工作空间的CMakeList

添加下列语句

(1)PC端

  1. add_compile_options(-std=c++11)
  2. find_package(catkin REQUIRED COMPONENTS
  3.   cv_bridge
  4.   image_transport
  5.   roscpp
  6.   rospy
  7.   sensor_msgs
  8.   std_msgs
  9. )
  10. find_package(OpenCV REQUIRED)
  11. find_package(Boost)
  12. catkin_package(
  13.   INCLUDE_DIRS include
  14.   #LIBRARIES ros_image
  15.   CATKIN_DEPENDS ${catkin_deps}
  16. #  DEPENDS system_lib
  17. )
  18. include_directories(
  19.  include
  20.   ${catkin_INCLUDE_DIRS}
  21. )
  22. include_directories(
  23. # include
  24.   ${OpenCV_INCLUDE_DIRS}
  25. )
  26. #生成可执行文件
  27. add_executable(imageSub src/show.cpp)
  28. add_executable(face src/face.cpp)
  29. add_executable(key_control src/keycontrol.cpp)
  30. add_executable(voicepub src/voice.cpp src/speech_recognizer.c src/linuxrec.c)
  31. add_executable(voicesub src/voicecmd.cpp)
  32. #链接
  33. target_link_libraries(imageSub ${catkin_LIBRARIES} ${OpenCV_LIBS})
  34. target_link_libraries(face ${catkin_LIBRARIES} ${OpenCV_LIBS})
  35. target_link_libraries(key_control ${catkin_LIBRARIES} ${Boost_LIBRARIES})
  36. target_link_libraries(voicepub ${catkin_LIBRARIES} libmsc.so -ldl -lpthread -lm -lrt -lasound)
  37. target_link_libraries(voicesub ${catkin_LIBRARIES})

(2)树莓派端

  1. find_package(catkin REQUIRED COMPONENTS
  2. cv_bridge
  3. image_transport
  4. roscpp
  5. rospy
  6. sensor_msgs
  7. std_msgs
  8. )
  9. find_package(OpenCV REQUIRED)
  10. set(wiringPi_include "/usr/include")
  11. include_directories(
  12. # include
  13. ${catkin_INCLUDE_DIRS}
  14. ${wiringPi_include}
  15. LINK_DIRECTORIES("usr/lib")
  16. )
  17. include_directories(${OpenCV_INCLUDE_DIRS})
  18. add_executable(image_Get src/pub.cpp)
  19. add_executable(imageSub src/camera.cpp)
  20. add_executable(keycmd src/keycmd.cpp)
  21. target_link_libraries(image_Get ${catkin_LIBRARIES} ${OpenCV_LIBS})
  22. target_link_libraries(imageSub ${catkin_LIBRARIES} ${OpenCV_LIBS})
  23. target_link_libraries(keycmd ${catkin_LIBRARIES} wiringPi)

4.编译

回到pc端和树莓派工作空间根目录,分别执行catkin_make命令,编译成功

5.运行

pc端打开三个终端,分别按顺序输入以下命令

roscore        //启动ros

rosrun ros_image voicepub //启动voicepub节点发布语音识别消息

rosrun ros_image voicesub   //启动voicesub节点订阅语音识别消息并发布命令给树莓派

树莓派打开一个终端输入

rosrun mycamera keycmd        //启动keycmd节点接收命令

6.结果


总结

小车还是很好玩的,小车底盘的程序大家可以自己写,我的不好我就不贴出来了哈哈哈

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号