文章目录
前言
用来记录一下我的ros学习过程,小车主要用到的有stm32和树莓派。
本文需要一些ros基础
提示:以下是本篇文章正文内容,下面案例可供参考
一、ubuntu16.04和树莓派安装ROS-kinetic
关于安装ros,网上有很多教程,这里就省略不讲。
不过ubuntu安装ros时,rosdep init和rosdep update很容易出现问题 ,在这里列出一个有效的 解决方法。
首先使用以下指令:
cd /etc/ros/rosdep/sources.list.d
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,在最后一行填入?
export ROS_MASTER_URI=https://${你的master主机的ip地址}:11311 //声明master主机ip地址
export ROS_HOSTNAME=${本机ip地址}
此时只需要在master主机启动roscore即可。
2.数据通信
树莓派和pc机之间的通信主要是利用topic,pc端节点向topic发送数据,树莓派订阅该topic即可收到数据。在这里列出发布者和订阅者的C++版通用代码模板。
发布者:
//头文件部分
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
int main(int argc,char** argv)
{
//初始化部分,前两个是命令行参数,第三个参数是发布者的节点名称,该名称必须唯一,不能重复
ros::init(argc,argv,"publish_name");
//创建节点句柄,方便对节点管理
ros::NodeHandle n;
//注册一个发布者,并告诉系统该节点将会发布以topic为话题的String类型消息。第二个参数表示消息发布队列大小
ros::Publisher pub = n.advertise<std_msgs::String>("topic",1000);
//设置循环频率单位HZ,调用Rate::sleep()时会根据该频率休眠相应的时间,不需要时可以省略
ros::Rate loop_rate(10);//不必要可省略
//节点正常时则一直循环,发生异常会返回false跳出循环
while(ros::ok())
{
//初始化std_msgs::String消息
std_msgs::String pubmsgs; //发布的消息
std::stringssteram tempmsg;
//注意消息的赋值方式,不能使用std::string tempmsgs直接赋值,会出现错误!
tempmsg << "此处为发布的内容";
pubmsgs.data = tempmsg.str();
ROS_INFO("%s",pubmsgs.data.c_str()); //后面跟string类型数据会出现乱码情况!
//该函数用来处理回调函数,为了功能无误,无论是否用到回调函数默认都加上
ros::spinOnce();
loop_rate.sleep();//不必要可省略
}
return 0;
}
订阅者:
由于C++支持C语言,所以可以选择两种方式编写,任选一种
//C语言风格
#include "ros/ros.h"
#include "std_msgs/String.h"
//回调函数
void SubCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("Receive: %s",msg->data.c_str());
}
int main(int argc,char** argv)
{
ros::init(argc,argv,"subscribe");
ros::NodeHandle n;
//创建订阅者,订阅topic话题,注册回调函数
ros::Subscriber sub = n.subscribe("topic",1000,SubCallback);
//循环等待回调函数,spinOnce只执行一次,spin循环执行
ros::spin();
return 0;
}
//C++风格
#include "ros/ros.h"
#include "std_msgs/String.h"
Class TopicSub{
private:
ros::NodeHandle n;
ros::Subscriber sub;
public:
//构造函数,完成初始化
TopicSub()
{
sub = n.subscribe("topic",1000,&TopicSub::SubCallback,this);
}
~TopicSub(){}
//回调函数
void SubCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("Receive: %s",msg->data.c_str());
}
};
int main(int argc,char** argv)
{
ros::init(argc,argv,"subscribe");
TopicSub subscriber;
ros::spin()
return 0;
}
三、科大讯飞sdk下载
1.登录科大讯飞官网www.xfyun.com,注册登录后创建应用。
2.点击左上角sdk下载,选择应用,平台和功能
3.点击sdk下载并复制到虚拟机下
4.进入samples目录下,选择32位或64位的脚本运行即可编译
5.cd ../../bin,执行可执行文件即可看到运行结果
四、树莓派和STM32串口通信
关于树莓派串口的配置,网上有许多教程资源,在这里就略过不写;
树莓派和stm32之间通过串口进行通信,树莓派发送指令给串口,stm32接收后则执行命令。
由于命令长度不同,因此使用串口空闲中断进行接收,以下是串口配置代码
//usart1.c
#include "usart.h"
#include "string.h"
#include "analyse.h"
void Usart1_Init(u32 baud)
{
GPIO_InitTypeDef GPIO_InitStruct; /* 定义GPIO模块结构体类型变量 */
USART_InitTypeDef USART_InitStruct; /* 定义USART模块结构体类型变量 */
NVIC_InitTypeDef NVIC_InitStructure; /* 定义NVIC中断结构体类型变量 */
// 设置UASART模块功能管脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); /* 使能GPIOA端口模块时钟 */
// USART1_RX(PA10)浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStruct);
// USART1_TX(PA9)复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
// 设置USART模块工作模式
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能USART1模块发送和接收
USART_InitStruct.USART_BaudRate = baud; // 设置USART1模块波特率
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // USART1模块8位数据长度
USART_InitStruct.USART_Parity = USART_Parity_No; // USART1模块禁止奇偶校验
USART_InitStruct.USART_StopBits = USART_StopBits_1; // USART1模块1位停止位
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 禁止硬件流
USART_Init(USART1,&USART_InitStruct); // 参数初始化USART_3模块
// USART模块NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ; // 抢占优先级等级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 响应优先级等级为3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断源NVIC中断
NVIC_Init(&NVIC_InitStructure); // 使用NVIC_InitStructure 参数初始化NVIC控制器
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 开启串口接受中断
USART_Cmd(USART1, ENABLE); // 使能串口1
}
//串口发送
void Usart1_Send_Str(u8 *Data)
{
while( *Data != '\0')
{
while( !USART_GetFlagStatus(USART1,USART_FLAG_TC))
{
} //发送完成
USART_SendData(USART1,*Data);
Data++;
}
}
//中断服务函数
u8 tempbuff[128];//串口缓冲数组
u8 u1count = 0;
u8 rxflag = 0;//接收完成标志
void USART1_IRQHandler(void)
{
int a;
if(rxflag == 0 && USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断
{
tempbuff[u1count++] = USART_ReceiveData(USART1);
}
if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)//空闲中断
{
a = USART1->DR;
a = USART1->SR;
rxflag = 1;//接受标志置1
memset(command_buff,0,sizeof(command_buff));
memcpy(command_buff,tempbuff,u1count);
memset(tempbuff,0,sizeof(tempbuff));
u1count = 0;
}
}
//usart1.h
#ifndef __USART_H_
#define __USART_H_
#include "sys.h"
extern u8 rxflag;
void Usart1_Init(u32 baud);
void Usart1_Send_Str(u8 *Data);
#endif
stm32命令解析代码:
//analyse.c
#include "analyse.h"
#include "usart.h"
#include "string.h"
#include "motor.h"
u8 command_buff[128] = {0};
PidObject car_left;
PidObject car_right;
int Robot_Command(void)//command_buff命令解析函数
{
if( (sizeof(command_buff) != 0) && (rxflag == 1) ) //如果接收到数据
{
rxflag = 0;
if(strcmp((const char*)command_buff,"go") == 0) return GO;
else if(strcmp((const char*)command_buff,"back") == 0) return BACK;
else if(strcmp((const char*)command_buff,"left") == 0) return LEFT;
else if(strcmp((const char*)command_buff,"right") == 0) return RIGHT;
else if(strcmp((const char*)command_buff,"stop") == 0) return STOP;
}
return STOP;
}
void Robot_Work(int command)
{
switch(command)
{
case STOP:Motor_Stop();break;
case GO:Motor_Forward(); break;
case BACK:Motor_Back(); break;
case LEFT:Motor_Left(); break;
case RIGHT:Motor_Right(); break;
default:break;
}
}
#ifndef __ANALYSE_H_
#define __ANALYSE_H_
#include "sys.h"
#include "pid.h"
#define STOP 0 //制动
#define GO 1 //前进
#define BACK 2 //后退
#define LEFT 3 //左转
#define RIGHT 4 //右转
extern u8 command_buff[128];
extern PidObject car_left;
extern PidObject car_right;
int Robot_Command(void);//command_buff命令解析函数
void Robot_Work(int command);
#endif
五、opencv实现人脸识别
在PC端和树莓派分别创建ros工作空间,用来存放代码
1.pc端接收图像topic数据
#include <ros/ros.h>
#include <image_transport/image_transport.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <cv_bridge/cv_bridge.h>
#include <iostream>
#include <string>
using namespace cv;
using namespace std;
CascadeClassifier face_detector;
string filepath = "/opt/ros/kinetic/share/OpenCV-3.3.1-dev/haarcascades/haarcascade_frontalface_alt.xml";
class ImageShow{
private:
ros::NodeHandle nh; //定义ros句柄
image_transport::ImageTransport it; //
image_transport::Subscriber FaceShow; //定义订阅者
cv_bridge::CvImagePtr cv_ptr;//定义一个cvimage指针实例
public:
ImageShow():it(nh)
{
FaceShow = it.subscribe("image_compressed",1,&ImageShow::imageCallback,this,image_transport::TransportHints("compressed"));//选择图像压缩,否则帧数会过低
cv::namedWindow("pi_image");
}
~ImageShow()
{
cv::destroyWindow("pi_image");
}
void imageCallback(const sensor_msgs::ImageConstPtr& msg)
{
try
{
cv_ptr = cv_bridge::toCvCopy(msg,sensor_msgs::image_encodings::BGR8);
}
catch(cv_bridge::Exception& e)
{
ROS_ERROR("exception:%s",e.what());
}
if(!face_detector.load(filepath))
{
cout<<"could not load"<<endl;
}
Mat gray_src;
cvtColor(cv_ptr->image,gray_src,COLOR_BGR2GRAY);
equalizeHist(gray_src,gray_src);
vector<Rect> faces;
face_detector.detectMultiScale(gray_src,faces,1.1,3,0,Size(30,30));
for(size_t t=0;t<faces.size();t++)
{
rectangle(cv_ptr->image,faces[t],Scalar(255,255,0),2,8,0);
}
image_show(cv_ptr->image);
}
void image_show(cv::Mat img)
{
cv::imshow("pi_image",img);
cv::waitKey(1);
}
};
int main(int argc,char** argv)
{
ros::init(argc,argv,"imageSub_node");
ImageShow test;
ros::spin();
}
2.树莓派端发布图像topic
#include <iostream>
#include <ros/ros.h>
#include <cv_bridge/cv_bridge.h> // 将ROS下的sensor_msgs/Image消息类型转化为cv::Mat数据类型
#include <sensor_msgs/image_encodings.h> // ROS下对图像进行处理
#include <image_transport/image_transport.h> // 用来发布和订阅图像信息
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/videoio.hpp>
int main(int argc, char** argv)
{
ros::init(argc, argv, "imageGet_node"); // ros初始化,定义节点名为imageGet_node
ros::NodeHandle nh; // 定义ros句柄
image_transport::ImageTransport it(nh); // 类似ROS句柄
image_transport::Publisher image_pub = it.advertise("image_compressed", 1); // 发布话题名/cameraImage
ros::Rate loop_rate(50); // 设置刷新频率,Hz
cv::Mat imageRaw; // 原始图像保存
cv::VideoCapture capture(0); // 创建摄像头捕获,并打开摄像头0(一般是0,2....)
if(capture.isOpened() == 0) // 如果摄像头没有打开
{
std::cout << "Read camera failed!" << std::endl;
return -1;
}
while(nh.ok())
{
capture.read(imageRaw); // 读取当前图像到imageRaw
cv::Size dsize = cv::Size(imageRaw.cols*0.5, imageRaw.rows*0.5);
cv::Mat img2 = cv::Mat(dsize, CV_32S);
cv::resize(imageRaw,img2,dsize);
//cv::imshow("veiwer", imageRaw); // 将图像输出到窗口
sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", img2).toImageMsg(); // 图像格式转换
image_pub.publish(msg); // 发布图像信息
ros::spinOnce(); // 保证完整
loop_rate.sleep(); // 照应上面设置的频率
if(cv::waitKey(1) >= 0) // 延时ms,按下任何键退出(必须要有waitKey,不然是看不到图像的)
break;
}
}
六、键盘控制小车
1.PC端键盘控制发布
#include <termios.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/poll.h>
#include <boost/thread/thread.hpp>
#include <ros/ros.h>
#include <std_msgs/String.h>
#include <sstream>
#define KEY_W 0X77 //w键
#define KEY_A 0X61 //a键
#define KEY_S 0X73 //s
#define KEY_D 0X64 //d
#define KEY_P 0X70 //p
struct termios cooked,raw;
int fd = 0;
class KeyControlNode{
private:
std_msgs::String msg;
ros::NodeHandle n;
ros::Publisher pub;
public:
KeyControlNode()
{
pub = n.advertise<std_msgs::String>("keycmd",1000);//向“keycmd”主题发布消息
}
~KeyControlNode(){}
void keyboardloop()
{
char key;
bool dirty = false;
tcgetattr(fd,&cooked);
memcpy(&raw,&cooked,sizeof(struct termios));
raw.c_lflag &= ~(ICANON|ECHO);
raw.c_cc[VEOL] = 1;
raw.c_cc[VEOF] = 2;
tcsetattr(fd,TCSANOW,&raw);
puts("WASD 控制移动,P停止\n");
struct pollfd ufd;
ufd.fd = fd;
ufd.events = POLLIN;
while(1)
{
boost::this_thread::interruption_point();
int num;
std::stringstream ss;
//利用boost库创建线程
if( (num = poll(&ufd,1,250)) < 0)
{
perror("poll():");
exit(0);
}
else if(num > 0)
{
if(read(fd,&key,1) < 0)
{
perror("read");
exit(1);
}
}
switch(key)
{
case KEY_W:
ss<<"go";
msg.data = ss.str();
dirty = true;
break;
case KEY_A:
ss<<"left";
msg.data = ss.str();
dirty = true;
break;
case KEY_S:
ss<<"back";
msg.data = ss.str();
dirty = true;
break;
case KEY_D:
ss<<"right";
msg.data = ss.str();
dirty = true;
break;
case KEY_P:
ss<<"stop";
msg.data = ss.str();
dirty = true;
break;
default:
ss<<"";
msg.data = ss.str();
dirty = true;
break;
}
key = 0;
ROS_INFO("%s",msg.data.c_str());
pub.publish(msg);//消息发布
}
}
};
int main(int argc,char** argv)
{
ros::init(argc,argv,"key",ros::init_options::AnonymousName|ros::init_options::NoSigintHandler);
KeyControlNode tbk;
//线程
boost::thread t = boost::thread(boost::bind(&KeyControlNode::keyboardloop,&tbk));
ros::spin();
t.interrupt();
t.join();
tcsetattr(fd,TCSANOW,&cooked);
}
2.树莓派订阅主题获取命令
#include <stdio.h>
#include "wiringPi.h"
#include <stdlib.h>
#include "wiringSerial.h"//wiringPi库
#include <ros/ros.h>
#include <std_msgs/String.h>
#include <string>
class SerialKeyboard{
private:
int fd;
ros::NodeHandle n;
ros::Subscriber sub;
std::string oldmsg;
public:
SerialKeyboard(int baund,const char* dev_name)//构造函数初始化波特率和设备号
{
fd = serialOpen(dev_name,baund);
if(wiringPiSetup()<0)
{
printf("Initialize fail!\r\n");
}
if(fd < 0)
{
printf("open serial fail!\r\n");
}
//订阅“keycmd”话题接收命令
sub = n.subscribe("keycmd",1000,&SerialKeyboard::SerialkeyboardCallback,this);
oldmsg = " ";
}
~SerialKeyboard()
{
serialClose(fd);
}
void SerialkeyboardCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("pc send:%s",msg->data.c_str());
if(msg->data.c_str() != oldmsg)
{
oldmsg = msg->data.c_str();
serialPuts(fd,msg->data.c_str()); //串口发送给stm32
}
}
};
int main(int argc,char** argv)
{
ros::init(argc,argv,"keycmd");
SerialKeyboard key(115200,"/dev/ttyAMA0");//波特率115200,设备号ttyAMA0
ros::spin();
return 0;
}
六、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模板,使其作为发布者发布语音识别的结果
/*
* 语音听写(iFly Auto Transform)技术能够实时地将语音转换成对应的文字。
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "ros_image/qisr.h"
#include "ros_image/msp_cmn.h"
#include "ros_image/msp_errors.h"
#include "ros_image/speech_recognizer.h"
#include "ros/ros.h"
#include "std_msgs/String.h"
#define FRAME_LEN 640
#define BUFFER_SIZE 4096
static void show_result(char *string, char is_over)//显示识别结果
{
printf("\rResult: [ %s ]", string);
if(is_over)
putchar('\n');
}
static char *g_result = NULL;
static unsigned int g_buffersize = BUFFER_SIZE;
std_msgs::String msgs;//定义消息全局变量
void on_result(const char *result, char is_last)
{
if (result) {
size_t left = g_buffersize - 1 - strlen(g_result);
size_t size = strlen(result);
if (left < size) {
g_result = (char*)realloc(g_result, g_buffersize + BUFFER_SIZE);
if (g_result)
g_buffersize += BUFFER_SIZE;
else {
ROS_INFO("mem alloc failed\n");
return;
}
}
strncat(g_result, result, size);
show_result(g_result, is_last);
if(g_result != "")
msgs.data = g_result;
}
}
void on_speech_begin()
{
if (g_result)
{
free(g_result);
}
g_result = (char*)malloc(BUFFER_SIZE);
g_buffersize = BUFFER_SIZE;
memset(g_result, 0, g_buffersize);
ROS_INFO("Start Listening...\n");
}
void on_speech_end(int reason)
{
if (reason == END_REASON_VAD_DETECT)
ROS_INFO("\nSpeaking done \n");
else
ROS_INFO("\nRecognizer error %d\n", reason);
}
/* demo recognize the audio from microphone */
static void demo_mic(const char* session_begin_params)
{
int errcode;
int i = 0;
struct speech_rec iat;
struct speech_rec_notifier recnotifier = {
on_result,
on_speech_begin,
on_speech_end
};
errcode = sr_init(&iat, session_begin_params, SR_MIC, &recnotifier);
if (errcode) {
ROS_INFO("speech recognizer init failed\n");
return;
}
errcode = sr_start_listening(&iat);
if (errcode) {
ROS_INFO("start listen failed %d\n", errcode);
}
/* demo 15 seconds recording */
while(i++ < 5)
sleep(1);
errcode = sr_stop_listening(&iat);
if (errcode) {
ROS_INFO("stop listening failed %d\n", errcode);
}
sr_uninit(&iat);
}
/* main thread: start/stop record ; query the result of recgonization.
* record thread: record callback(data write)
* helper thread: ui(keystroke detection)
*/
int main(int argc, char** argv)
{
int ret = MSP_SUCCESS;
/* login params, please do keep the appid correct */
const char* login_params = "appid = ${你自己的ID}, work_dir = .";
int aud_src = 0; /* from mic or file */
/*
* See "iFlytek MSC Reference Manual"
*/
const char* session_begin_params =
"sub = iat, domain = iat, language = zh_cn, "
"accent = mandarin, sample_rate = 16000, "
"result_type = plain, result_encoding = utf8";
/* Login first. the 1st arg is username, the 2nd arg is password
* just set them as NULL. the 3rd arg is login paramertes
* */
ret = MSPLogin(NULL, NULL, login_params);
if (MSP_SUCCESS != ret) {
ROS_INFO("MSPLogin failed , Error code %d.\n",ret);
MSPLogout(); // Logout...
}
ros::init(argc,argv,"VoiceRecognize");
ros::NodeHandle n;
//发布消息到“voicewords”主题
ros::Publisher voicepub = n.advertise<std_msgs::String>("voicewords",1000);
while(ros::ok())
{
ROS_INFO("Demo recognizing the speech from microphone\n");
ROS_INFO("Speak in 5 seconds\n");
demo_mic(session_begin_params);
ROS_INFO("5 sec passed\n");
voicepub.publish(msgs);//发布数据
}
ros::spin();
MSPLogout();//退出讯飞云登录
return 0;
}
新建voicecmd.cpp使其作为订阅者接收语音识别数据,并处理,代码如下图
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "stdlib.h"
#include "stdio.h"
#include "unistd.h"
#include "signal.h"
#include "sys/types.h"
#include "sys/wait.h"
class VoicecmdSub{
private:
ros::NodeHandle n;
std_msgs::String cmd;
ros::Subscriber voicesub; //定义订阅着
ros::Publisher cmdpub; //定义发布者
std::string oldmsg;
bool opencam; //摄像头打开标志
bool openface; //人脸识别打开标志
bool openkeyboard; //键盘控制打开标志
pid_t pid_cam; //摄像头子进程号
pid_t pid_face; //人脸识别子进程号
pid_t pid_keyboard; //键盘控制子进程号
public:
VoicecmdSub()
{
cmdpub = n.advertise<std_msgs::String>("keycmd",1000);//发布到“keycmd”上
voicesub = n.subscribe("voicewords",1000,&VoicecmdSub::voicecmdCallback,this);//订阅“voicewords”主题
oldmsg = "";
opencam = false;
openface = false;
openkeyboard = false;
pid_cam = -1;
pid_face = -1;
pid_keyboard = -1;
}
~VoicecmdSub(){}
void voicecmdCallback(const std_msgs::String::ConstPtr& msg)
{
std::string msgs = msg->data.c_str();//这里语音转发后使用std_msgs::String会出现乱码情况,所以采取std::string
std::stringstream ss;
ss<<"";
if(msgs != oldmsg)
{
oldmsg = msgs;
std::cout<<"I Heard:"<<msgs<<std::endl;
//通过识别的命令执行对应的操作,利用system函数创建进程播放合成的语音
if(msgs == "好兄弟在吗?")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/hxdwz.wav");
}
else if(msgs == "你能做什么?")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/allcmd.wav");
}
else if(msgs == "前进。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/cmdrcv.wav");
ss<<"go";
}
else if(msgs == "后退。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/cmdrcv.wav");
ss<<"back";
}
else if(msgs == "左转。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/cmdrcv.wav");
ss<<"left";
}
else if(msgs == "右转。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/cmdrcv.wav");
ss<<"right";
}
else if(msgs == "打开摄像头。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/opencam.wav");
ss<<"opencam";
if(!opencam)
{
opencam = true;
pid_cam = fork();//创建子进程执行操作
if(pid_cam == 0)
{
//利用execl函数,使用rosrun指令 execl("/opt/ros/kinetic/bin/rosrun","rosrun","ros_image","imageSub",NULL);
}
}
}
else if(msgs == "关闭摄像头。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/closecam.wav");
ss<<"closecam";
if(opencam)
{
opencam = false;
if(pid_cam > 0)
{
kill(pid_cam,SIGKILL);//杀死子进程
wait(NULL);
}
}
}
else if(msgs == "打开人脸识别。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/faceopen.wav");
ss<<"opencam";
if(!openface)
{
openface = true;
pid_face = fork();
if(pid_face == 0)
{
execl("/opt/ros/kinetic/bin/rosrun","rosrun", "ros_image", "face",NULL);
}
}
}
else if(msgs == "关闭人脸识别。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/faceclose.wav");
ss<<"closecam";
if(openface)
{
openface = false;
if(pid_face > 0)
{
kill(pid_face,SIGKILL);
wait(NULL);
}
}
}
else if(msgs == "打开键盘控制。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/keyopen.wav");
if(!openkeyboard)
{
openkeyboard = true;
pid_keyboard = fork();
if(pid_keyboard == 0)
{
execl("/opt/ros/kinetic/bin/rosrun","rosrun","ros_image","key_control",NULL);
}
}
}
else if(msgs == "关闭键盘控制。")
{
system("mplayer /home/mzq/catkin_ws/src/ros_image/music/keyclose.wav");
if(openkeyboard)
{
openkeyboard = false;
if(pid_keyboard > 0)
{
kill(pid_keyboard,SIGKILL);
wait(NULL);
}
}
}
cmd.data = ss.str();
cmdpub.publish(cmd);//发布消息
}
}
};
int main(int argc,char** argv)
{
ros::init(argc,argv,"voicecmdpub");
VoicecmdSub it;
ros::spin();
return 0;
}
2.树莓派端接收指令
利用上文的按键控制订阅的代码,修改一下
#include <stdio.h>
#include "wiringPi.h"
#include <stdlib.h>
#include "wiringSerial.h"
#include <ros/ros.h>
#include <std_msgs/String.h>
#include <string>
#include "sys/types.h"
#include "unistd.h"
#include "sys/wait.h"
#include "signal.h"
class SerialKeyboard{
private:
int fd;
ros::NodeHandle n;
ros::Subscriber sub;
std::string oldmsg;
bool opencam;
pid_t pid_cam;
public:
SerialKeyboard(int baund,const char* dev_name)
{
fd = serialOpen(dev_name,baund);
if(wiringPiSetup()<0)
{
printf("Initialize fail!\r\n");
}
if(fd < 0)
{
printf("open serial fail!\r\n");
}
sub = n.subscribe("keycmd",1000,&SerialKeyboard::SerialkeyboardCallback,this);
oldmsg = " ";
opencam = false;
pid_cam = -1;
}
~SerialKeyboard()
{
serialClose(fd);
}
void SerialkeyboardCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("pc send:%s",msg->data.c_str());
if(msg->data.c_str() != oldmsg)
{
oldmsg = msg->data.c_str();
if(oldmsg == "opencam")//打开摄像头,打开人脸识别都是这个命令
{
if(!opencam)
{
opencam = true;
pid_cam = fork();//创建进程
if(pid_cam == 0)
{
execl("/opt/ros/kinetic/bin/rosrun","rosrun", "mycamera","image_Get",NULL);
}
}
}
else if(oldmsg == "closecam")//关闭摄像头或者关闭人脸识别
{
if(opencam)
{
//pid_cam = -1;
opencam = false;
if(pid_cam > 0)
{
kill(pid_cam,SIGKILL);//杀死子进程
wait(NULL);
}
}
}
else
serialPuts(fd,msg->data.c_str());
}
}
};
int main(int argc,char** argv)
{
ros::init(argc,argv,"keycmd");
SerialKeyboard key(115200,"/dev/ttyAMA0");
ros::spin();
return 0;
}
该处使用的url网络请求的数据。
3.修改工作空间的CMakeList
添加下列语句
(1)PC端
add_compile_options(-std=c++11)
find_package(catkin REQUIRED COMPONENTS
? cv_bridge
? image_transport
? roscpp
? rospy
? sensor_msgs
? std_msgs
)
find_package(OpenCV REQUIRED)
find_package(Boost)
catkin_package(
? INCLUDE_DIRS include
? #LIBRARIES ros_image
? CATKIN_DEPENDS ${catkin_deps}
# ?DEPENDS system_lib
)
include_directories(
?include
? ${catkin_INCLUDE_DIRS}
)
include_directories(
# include
? ${OpenCV_INCLUDE_DIRS}
)
#生成可执行文件
add_executable(imageSub src/show.cpp)
add_executable(face src/face.cpp)
add_executable(key_control src/keycontrol.cpp)
add_executable(voicepub src/voice.cpp src/speech_recognizer.c src/linuxrec.c)
add_executable(voicesub src/voicecmd.cpp)
#链接
target_link_libraries(imageSub ${catkin_LIBRARIES} ${OpenCV_LIBS})
target_link_libraries(face ${catkin_LIBRARIES} ${OpenCV_LIBS})
target_link_libraries(key_control ${catkin_LIBRARIES} ${Boost_LIBRARIES})
target_link_libraries(voicepub ${catkin_LIBRARIES} libmsc.so -ldl -lpthread -lm -lrt -lasound)
target_link_libraries(voicesub ${catkin_LIBRARIES})
(2)树莓派端
find_package(catkin REQUIRED COMPONENTS
cv_bridge
image_transport
roscpp
rospy
sensor_msgs
std_msgs
)
find_package(OpenCV REQUIRED)
set(wiringPi_include "/usr/include")
include_directories(
# include
${catkin_INCLUDE_DIRS}
${wiringPi_include}
LINK_DIRECTORIES("usr/lib")
)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(image_Get src/pub.cpp)
add_executable(imageSub src/camera.cpp)
add_executable(keycmd src/keycmd.cpp)
target_link_libraries(image_Get ${catkin_LIBRARIES} ${OpenCV_LIBS})
target_link_libraries(imageSub ${catkin_LIBRARIES} ${OpenCV_LIBS})
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.结果
总结
小车还是很好玩的,小车底盘的程序大家可以自己写,我的不好我就不贴出来了哈哈哈
|