系統版本:Ubuntu14.04,ROS indigo
機器人底盤:kobuki
攝像頭:Asus Xtion
程式設計語言:C++
參考書本:ROS by Example
一、功能介紹
使機器人直行1米,接着旋轉180°,再傳回到起始點。
二、實作方法
根據/odom和/base_footprint(或/base_link)坐标之間的轉換來監視機器人的位置和方向。
此方法使用術語“裡程資訊”(“Odometry”)表示内部位置資料,ROS提供一種消息類型來存儲這些資訊,既是“nav_msgs/Odometry”。使用以下指令檢視消息的資料結構,見圖1。
圖1
nav_msgs/Odometry提供了機器人frame_id坐标系到child_id坐标系的相對位置。geometry_msgs/Pose消息提供了機器人的位姿資訊,消息geometry_msgs/Twist提供了速度資訊。線速度x為正時,機器人向前移動,為負時,機器人向後移動。角速度z為正時,機器人向左轉,為負時,機器人向右轉。
因為裡程Odometry其實就是兩個坐标系之間的位移,那麼我們就有必要釋出兩個坐标系之間的坐标變換資訊。一般ROS的裡程測量使用/odom作為父坐标ID(固定坐标),/base_footprint(或/base_link)作為子坐标ID(機器人自身)。這些變換是指機器人相對/odom坐标移動。
三、實驗步驟
1.在robot_move/src/裡建立odom_out_and_back.cpp,并粘貼如下代碼:
#include <ros/ros.h>
#include <signal.h>
#include <geometry_msgs/Twist.h>
#include <tf/transform_listener.h>
#include <nav_msgs/Odometry.h>
#include <string.h>
ros::Publisher cmdVelPub;
void shutdown(int sig)
{
cmdVelPub.publish(geometry_msgs::Twist());
ROS_INFO("odom_out_and_back.cpp ended!");
ros::shutdown();
}
int main(int argc, char** argv)
{
//How fast will we update the robot's movement?
double rate = ;
int count = ;//Loop through the two legs of the trip
ros::init(argc, argv, "go_and_back");
std::string topic = "/cmd_vel";
ros::NodeHandle node;
cmdVelPub = node.advertise<geometry_msgs::Twist>(topic, );
//Set the equivalent ROS rate variable
ros::Rate loopRate(rate);
geometry_msgs::Twist speed; // 控制信号載體 Twist message
signal(SIGINT, shutdown);
ROS_INFO("odom_out_and_back.cpp start...");
//Set the forward linear speed to 0.2 meters per second
float linear_speed = ;
//Set the travel distance to 1.0 meters
float goal_distance = ;
//Set the rotation speed to 0.5 radians per second
float angular_speed = ;
//Set the rotation angle to Pi radians (180 degrees)
double goal_angle = M_PI;
//Set the angular tolerance in degrees converted to radians
double angular_tolerance = *M_PI/; //角度轉換成弧度:deg*PI/180
tf::TransformListener listener;
tf::StampedTransform transform;
//Find out if the robot uses /base_link or /base_footprint
std::string odom_frame = "/odom";
std::string base_frame;
try
{
listener.waitForTransform(odom_frame, "/base_footprint", ros::Time(), ros::Duration() );
base_frame = "/base_footprint";
ROS_INFO("base_frame = /base_footprint");
}
catch (tf::TransformException & ex)
{
try
{
listener.waitForTransform(odom_frame, "/base_link", ros::Time(), ros::Duration() );
base_frame = "/base_link";
ROS_INFO("base_frame = /base_link");
}
catch (tf::TransformException ex)
{
ROS_INFO("Cannot find transform between /odom and /base_link or /base_footprint");
cmdVelPub.publish(geometry_msgs::Twist());
ros::shutdown();
}
}
//Loop once for each leg of the trip
for(int i = ;i < ;i++)
{
ROS_INFO("go straight...!");
speed.linear.x = linear_speed; // 設定線速度,正為前進,負為後退
//Get the starting position values
listener.lookupTransform(odom_frame, base_frame, ros::Time(), transform);
float x_start = transform.getOrigin().x();
float y_start = transform.getOrigin().y();
// Keep track of the distance traveled
float distance = ;
while( (distance < goal_distance) && (ros::ok()) )
{
//Publish the Twist message and sleep 1 cycle
cmdVelPub.publish(speed);
loopRate.sleep();
listener.lookupTransform(odom_frame, base_frame, ros::Time(), transform);
//Get the current position
float x = transform.getOrigin().x();
float y = transform.getOrigin().y();
//Compute the Euclidean distance from the start
distance = sqrt(pow((x - x_start), ) + pow((y - y_start), ));
}
//Stop the robot before the rotation
cmdVelPub.publish(geometry_msgs::Twist());
ros::Duration().sleep(); // sleep for a second
ROS_INFO("rotation...!");
//Now rotate left roughly 180 degrees
speed.linear.x = ;
//Set the angular speed
speed.angular.z = angular_speed; // 設定角速度,正為左轉,負為右轉
//yaw是圍繞Y軸旋轉,也叫偏航角
//Track the last angle measured
double last_angle = fabs(tf::getYaw(transform.getRotation()));
//Track how far we have turned
double turn_angle = ;
while( (fabs(turn_angle + angular_tolerance) < M_PI) && (ros::ok()) )
{
//Publish the Twist message and sleep 1 cycle
cmdVelPub.publish(speed);
loopRate.sleep();
// Get the current rotation
listener.lookupTransform(odom_frame, base_frame, ros::Time(), transform);
//C++: abs()求得是正數的絕對值,fabs()求得是浮點數的絕對值;python:abs(x),參數可以是:負數、正數、浮點數或者長整形
double rotation = fabs(tf::getYaw(transform.getRotation()));
//Compute the amount of rotation since the last loop
double delta_angle = fabs(rotation - last_angle);
//Add to the running total
turn_angle += delta_angle;
last_angle = rotation;
}
//Stop the robot before the rotation
//Set the angular speed
speed.angular.z = ;
cmdVelPub.publish(geometry_msgs::Twist());
ros::Duration().sleep(); // sleep for a second
}
cmdVelPub.publish(geometry_msgs::Twist());
ros::Duration().sleep(); // sleep for a second
ROS_INFO("odom_out_and_back.cpp ended!");
ros::shutdown();
return ;
}
特别說明:代碼中的注釋大部分引用《ros_by_example_indigo_volume_1》這本書的,英文注釋簡單易懂,是以不作翻譯了。
2.修改robot_move目錄下的CMakeLists.txt
在CMakeLists.txt檔案末尾加入幾條語句:
add_executable(odom_out_and_back src/odom_out_and_back.cpp)
target_link_libraries(odom_out_and_back ${catkin_LIBRARIES})
3.編譯程式
在catkin_ws目錄下,進行catkin_make編譯,得到odom_out_and_back執行程式。
4.測試程式
4.1 啟動roscore
roscore
4.2 啟動機器人
4.2.1若是運作仿真機器人
4.2.2若是運作真實的機器人平台
4.3 啟動 rviz 圖形化顯示程式
4.4 啟動odom_out_and_back程式,效果如圖2
rosrun robot_move odom_out_and_back
圖2
四、解釋部分代碼
tf::TransformListener listener;
tf::StampedTransform transform;
//Find out if the robot uses /base_link or /base_footprint
std::string odom_frame = "/odom";
std::string base_frame;
try
{
listener.waitForTransform(odom_frame, "/base_footprint", ros::Time(), ros::Duration() );
base_frame = "/base_footprint";
ROS_INFO("base_frame = /base_footprint");
}
catch (tf::TransformException & ex)
{
try
{
listener.waitForTransform(odom_frame, "/base_link", ros::Time(), ros::Duration() );
base_frame = "/base_link";
ROS_INFO("base_frame = /base_link");
}
catch (tf::TransformException ex)
{
ROS_INFO("Cannot find transform between /odom and /base_link or /base_footprint");
cmdVelPub.publish(geometry_msgs::Twist());
ros::shutdown();
}
}
建立StampedTransform對象來擷取變換資訊,建立TransformListener對象監聽坐标變換。我們需要/odom坐标和/base_footprint坐标或者/base_link坐标之間的變換。首先測試是否存在/base_footprint坐标,如果不存在,再測試/base_link坐标。結果将儲存在base_frame變量,以便之後使用。在本次實驗使用的是/base_footprint坐标。
//Loop once for each leg of the trip
for(int i = ;i < ;i++)
{
ROS_INFO("go straight...!");
speed.linear.x = linear_speed; // 設定線速度,正為前進,負為後退
//Get the starting position values
listener.lookupTransform(odom_frame, base_frame, ros::Time(), transform);
float x_start = transform.getOrigin().x();
float y_start = transform.getOrigin().y();
執行兩次循環,每次循環都是機器人移動直行1米,然後旋轉180°。每次循環一開始,我們都記錄起始點的位置。使用listener對象來檢視odom_frame和base_frame坐标的變換,并記錄在transform。通過transform.getOrigin().x()和transform.getOrigin().y()獲得起始點位置。
float distance = ;
while( (distance < goal_distance) && (ros::ok()) )
{
//Publish the Twist message and sleep 1 cycle
cmdVelPub.publish(speed);
loopRate.sleep();
listener.lookupTransform(odom_frame, base_frame, ros::Time(), transform);
//Get the current position
float x = transform.getOrigin().x();
float y = transform.getOrigin().y();
//Compute the Euclidean distance from the start
distance = sqrt(pow((x - x_start), ) + pow((y - y_start), ));
}
這個循環是使機器人直行1米。
while( (fabs(turn_angle + angular_tolerance) < M_PI) && (ros::ok()) )
{
//Publish the Twist message and sleep 1 cycle
cmdVelPub.publish(speed);
loopRate.sleep();
// Get the current rotation
listener.lookupTransform(odom_frame, base_frame, ros::Time(), transform);
//C++: abs()求得是正數的絕對值,fabs()求得是浮點數的絕對值;python:abs(x),參數可以是:負數、正數、浮點數或者長整形
double rotation = fabs(tf::getYaw(transform.getRotation()));
//Compute the amount of rotation since the last loop
double delta_angle = fabs(rotation - last_angle);
//Add to the running total
turn_angle += delta_angle;
last_angle = rotation;
}
這個循環使機器人在一定angular_tolerance角度誤差内旋轉180°。abs()求得是正數的絕對值,fabs()求得是浮點數的絕對值。tf::getYaw(transform.getRotation())擷取旋轉的角度。
五、使用Odometry行走一個正方形
設定四個導航點,使機器人移動成一個正方形。效果如圖3.
圖3
源代碼位址:https://github.com/KeoChi/robot_move
個人學習筆記,歡迎交流學習。