====== Robotino Vision Pickup ======
**This tutorial is part of the [[robotino_operation|Robotino Operation]] series.**
The completed project can be downloaded here {{dylanw:robotino_control.zip}}
{{youtube>29y0zNNldC0?large}}
{{youtube>hGD6ovrpo-4?large}}\\
===== Details of camera mounting =====
It was decided to mount the camera off to one side of the robotino so that the vision would not be obscured by the BHA (the arm). The camera was fixed to the robotino with velcro and stabilized with tape as no mooting holes were available in the desired location.
{{dylanw:cameraright.jpeg }} {{ dylanw:cameraleft.jpeg}}
===== Setup =====
**For this tutorial you will need:**
* an Ubuntu PC
* ROS installed on your pc [[http://wiki.ros.org/ROS/Installation|(installation instructions)]]
* API2 installed on your pc **and** on your robotino
**You will also need to find and install several ROS packages:**
* [[http://wiki.ros.org/vision_opencv|vision_opencv]]
* [[http://wiki.openrobotino.org/index.php?title=ROS|robotino-ros-pkg]]
* the completed ros package for this tutorial can downloaded as well {{dylanw:robotino_control.zip}}
===== Vision node =====
First we need to create a vision node to detect the location of our target object. This package tracks the object by it's color.
The vision node is based on the following tutorial: [[http://opencv-srf.blogspot.ro/2010/09/object-detection-using-color-seperation.html|open cv color tracking]]. It is recommended that you look over this tutorial to understand the details of the vision node code.
#include
#include "std_msgs/String.h"
#include
#include
#include
#include
#include
#include
#include "std_msgs/MultiArrayLayout.h"
#include "std_msgs/MultiArrayDimension.h"
#include "std_msgs/Int32MultiArray.h"
namespace enc = sensor_msgs::image_encodings;
static const char WINDOW[] = "Image window";
class ImageConverter
{
// declare all of our node handelers
ros::NodeHandle nh_;
image_transport::ImageTransport it_;
image_transport::Subscriber image_sub_;
image_transport::Publisher image_pub_;
ros::Publisher pos_pub_;
public:
//declare variables to be use later
IplImage* imgTracking;
int lastX;
int lastY;
ros::NodeHandle n;
ImageConverter()
: it_(nh_)
{
image_pub_ = it_.advertise("/image_processed", 1);
pos_pub_= n.advertise("/obj_pos", 2);
image_sub_ = it_.subscribe("/image_raw", 1, &ImageConverter::imageCb, this);
cv::namedWindow(WINDOW);
}
~ImageConverter()
{
cv::destroyWindow(WINDOW);
}
// same as in linked tutorial
IplImage* GetThresholdedImage(IplImage* imgHSV){
IplImage* imgThresh=cvCreateImage(cvGetSize(imgHSV),IPL_DEPTH_8U, 1);
// below we select the HSV values. Thes are the values for the ball I am using on my robotino's webcam
cvInRangeS(imgHSV, cvScalar(23,60,35), cvScalar(38,2556,256), imgThresh);
return imgThresh;
}
// same as in linked tutorial
void trackObject(IplImage* imgThresh){
// Calculate the moments of 'imgThresh'
CvMoments *moments = (CvMoments*)malloc(sizeof(CvMoments));
cvMoments(imgThresh, moments, 1);
double moment10 = cvGetSpatialMoment(moments, 1, 0);
double moment01 = cvGetSpatialMoment(moments, 0, 1);
double area = cvGetCentralMoment(moments, 0, 0);
// if the area<1000, I consider that the there are no object in the image and it's because of the noise, the area is not zero
if(area>100){
// calculate the position of the ball
int posX = moment10/area;
int posY = moment01/area;
if(lastX>=0 && lastY>=0 && posX>=0 && posY>=0){
// Draw a yellow line from the previous point to the current point
cvLine(imgTracking, cvPoint(posX, posY), cvPoint(lastX, lastY), cvScalar(0,0,255), 4);
}
lastX = posX;
lastY = posY;
std::cout << "X:pos" << posX <image.rows > 60 && cv_ptr->image.cols > 60){
IplImage* frame=new IplImage(cv_ptr->image); // in our case the source of the image is different
// than that of the tutorial so we and to select frame from our cv_pointer we got from our open cv link
imgTracking=cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U, 3);
cvZero(imgTracking); //covert the image, 'imgTracking' to black
frame=cvCloneImage(frame);
cvSmooth(frame, frame, CV_GAUSSIAN,3,3); //smooth the original image using Gaussian kernel
IplImage* imgHSV = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3);
cvCvtColor(frame, imgHSV, CV_BGR2HSV); //Change the color format from BGR to HSV
IplImage* imgThresh = GetThresholdedImage(imgHSV);
cvSmooth(imgThresh, imgThresh, CV_GAUSSIAN,3,3); //smooth the binary image using Gaussian kernel
//track the possition of the ball
trackObject(imgThresh);
// Add the tracking image and the frame
cvAdd(frame, imgTracking, frame);
cvShowImage("Ball", imgThresh);
cvShowImage("Video", frame);
//Clean up used images
cvReleaseImage(&frame); // we created a pointer so we need to clean it up before we
// exit the scope of the if statement
cvReleaseImage(&imgHSV);
cvReleaseImage(&imgThresh);
cvReleaseImage(&frame);
}
cv::imshow(WINDOW, cv_ptr->image);
cv::waitKey(3);
image_pub_.publish(cv_ptr->toImageMsg());
}
};
int main(int argc, char** argv)
{
ros::init(argc, argv, "vision");
ImageConverter ic;
ros::spin();
return 0;
}
===== BHApose =====
BHApose is a simple extension of the poser code found in [[robotino_arm_control|last weeks tutorial]]. The only major difference is that the pose is now determined by a rostopic callback function instead of asking the user for it at startup.
The new callback functions can be seen near the head of the code.
#define _USE_MATH_DEFINES
#include
#include
#include
#include "ros/ros.h"
#include "std_msgs/String.h"
#include
#include
#include "std_msgs/MultiArrayLayout.h"
#include "std_msgs/MultiArrayDimension.h"
#include "std_msgs/Float32MultiArray.h"
#include "rec/robotino/api2/all.h"
using namespace rec::robotino::api2;
float _rect[2]= {0,0};
bool _run = true;
class MyCom : public Com
{
public:
MyCom()
: Com( "rect" )
{
}
void errorEvent( const char* errorString )
{
std::cerr << "Error: " << errorString << std::endl;
}
void connectedEvent()
{
std::cout << "Connected." << std::endl;
}
void connectionClosedEvent()
{
std::cout << "Connection closed." << std::endl;
}
void logEvent( const char* message, int level )
{
std::cout << message << std::endl;
}
void pingEvent( float timeMs )
{
//std::cout << "Ping: " << timeMs << "ms" << std::endl;
}
};
class MyBumper : public Bumper
{
public:
MyBumper()
: bumped( false )
{
}
void bumperEvent( bool hasContact )
{
bumped |= hasContact;
std::cout << "Bumper has " << ( hasContact ? "contact" : "no contact") << std::endl;
}
bool bumped;
};
MyCom com;
OmniDrive omniDrive;
MyBumper bumper;
CompactBHA cbha;
float targetValues[6] = { 0.591398, 0.522972, 0.532747, 0.796676, 0.821114, 0.904203};
bool gripperClosed=false;
// our calback funciton to retrieve the pose
void poseCallback(const std_msgs::Float32MultiArray::ConstPtr& array)
{
int i = 0;
// print all the remaining numbers
for(std::vector::const_iterator it = array->data.begin(); it != array->data.end(); ++it)
{
targetValues[i] = *it;
i++;
std::cout<data.c_str());
gripperClosed=true;
ROS_INFO("gripper Closed");
}
void init( const std::string& hostname )
{
// Initialize the actors
// Connect
std::cout << "Connecting... ";
com.setAddress( hostname.c_str() );
com.connectToServer( true );
if( false == com.isConnected() )
{
std::cout << std::endl << "Could not connect to " << com.address() << std::endl;
rec::robotino::api2::shutdown();
exit( 1 );
}
else
{
std::cout << "success" << std::endl;
cbha.setCompressorsEnabled( true );
}
}
void drive()
{
float pressures[8] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
float values[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
const float maxPressure = 1.5f;
float stepSize=.5;
float lastTime=com.msecsElapsed();
float lowerAvg;
float upperAvg;
float errors[6] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
float tolerance=.02;
bool inTolerance=false;
ros::NodeHandle n;
ros::Subscriber bha_sub = n.subscribe("/bha_pose", 100, poseCallback);
ros::Subscriber sub = n.subscribe("/bha_gripper", 100, gripperCallback);
while( com.isConnected() && false == bumper.value() && _run )
{
// update the string pot values
cbha.stringPots( values );
// move toward target
pressures[0]=pressures[0]+(-values[0]+targetValues[0])*stepSize+(values[1]-targetValues[1])*stepSize+(values[2]-targetValues[2])*stepSize;
pressures[1]=pressures[1]+(values[0]-targetValues[0])*stepSize+(-values[1]+targetValues[1])*stepSize+(values[2]-targetValues[2])*stepSize;
pressures[2]=pressures[2]+(values[0]-targetValues[0])*stepSize+(values[1]-targetValues[1])*stepSize+(-values[2]+targetValues[2])*stepSize;
lowerAvg=(pressures[0]+pressures[1]+pressures[2])/3;
pressures[0]-=lowerAvg;
pressures[1]-=lowerAvg;
pressures[2]-=lowerAvg;
pressures[3]=pressures[3]+(-values[3]+targetValues[3])*stepSize+(values[4]-targetValues[4])*stepSize+(values[5]-targetValues[5])*stepSize;
pressures[4]=pressures[4]+(values[3]-targetValues[3])*stepSize+(-values[4]+targetValues[4])*stepSize+(values[5]-targetValues[5])*stepSize;
pressures[5]=pressures[5]+(values[3]-targetValues[3])*stepSize+(values[4]-targetValues[4])*stepSize+(-values[5]+targetValues[5])*stepSize;
upperAvg=(pressures[3]+pressures[4]+pressures[5])/3;
pressures[3]-=upperAvg;
pressures[4]-=upperAvg;
pressures[5]-=upperAvg;
pressures[7]=10;
cbha.setPressures( pressures );
for( unsigned int i = 0; i < 6; ++i ){
errors[i]=targetValues[i]-values[i];
std::cout<< errors[i] << std::endl;
if(tolerance > std::abs(errors[i]) ){
//std::cout << "in tolerance: " << i << std::endl;
}
else{
break;
}
if(i==5){
inTolerance=true;
//std::cout << "all in tolerance" << std::endl;
}
else inTolerance=false;
}
if(inTolerance) std::cout << "all in tolerance" << std::endl;
else std::cout << "not in tolerance" << std::endl;
if(inTolerance=true && gripperClosed){
cbha.setGripperValve1( true );
cbha.setGripperValve2( true );
}
// list values
/*
std::cout << std::endl;
std::cout << "avg: " << lowerAvg << " bar" << std::endl;
std::cout << std::endl;
for( unsigned int i = 0; i < 8; ++i )
{
std::cout << "B" << i << ": " << pressures[i] << " bar" << std::endl;
}
std::cout << std::endl;
for( unsigned int i = 0; i < 6; ++i )
{
std::cout << "String pot " << i << ": " << values[i] << std::endl;
}
*/
// syncronize and delay
ros::spinOnce();
lastTime=com.msecsElapsed();
com.processEvents();
rec::robotino::api2::msleep( 100 );
}
}
void destroy()
{
com.disconnectFromServer();
}
int main( int argc, char **argv )
{
ros::init(argc, argv, "BHApose");
std::string hostname = "192.168.0.2";
if( argc > 1 )
{
hostname = argv[1];
}
try
{
init( hostname );
drive();
destroy();
}
catch( const rec::robotino::api2::RobotinoException& e )
{
std::cerr << "Com Error: " << e.what() << std::endl;
}
catch( const std::exception& e )
{
std::cerr << "Error: " << e.what() << std::endl;
}
catch( ... )
{
std::cerr << "Unknow Error" << std::endl;
}
rec::robotino::api2::shutdown();
}
===== Talker =====
Talker is the node that controls the other 2 nodes. It first move the vehicle so that the target object is at a predefined location on the cameras vision. then it tells poser to move the arm to a new the pickup location, grab it and hold it up.
#include "ros/ros.h"
#include "std_msgs/String.h"
#include
#include "robotino_msgs/BHAReadings.h"
#include "robotino_msgs/SetBHAPressures.h"
#include
#include
#include
#include "std_msgs/MultiArrayLayout.h"
#include "std_msgs/MultiArrayDimension.h"
#include "std_msgs/Int32MultiArray.h"
#include "std_msgs/Float32MultiArray.h"
// declare global vars
void arrayCallback(const std_msgs::Int32MultiArray::ConstPtr& array);
int Arr[2];
int main(int argc, char **argv)
{
int targetX=135;
int targetY=150;
float posGain=.001;
ros::init(argc, argv, "talker");
ros::NodeHandle n;
ros::Subscriber ball_sub = n.subscribe("/obj_pos", 100, arrayCallback);
ros::Publisher chatter_pub = n.advertise("/cmd_vel", 1, true);
ros::Publisher bha_pub = n.advertise("/bha_pose", 1, true);
ros::Publisher gripper_pub = n.advertise("/bha_gripper", 100);
ros::Rate loop_rate(10);
int count = 0;
// loop one approach the ball
while (ros::ok())
{
std::cout<< "x: " << abs(targetY-Arr[1]) << std::endl;
std::cout<< "y: " << abs(targetX-Arr[0]) << std::endl;
geometry_msgs::Twist cmd_vel_msg_;
cmd_vel_msg_.linear.x = 0;
cmd_vel_msg_.linear.y = 0;
cmd_vel_msg_.angular.z = 0;
// move the vehicle so that the ball is in the target location on the camera screen.
if(Arr[0]>0)
{
if(abs(targetY-Arr[1])>2) cmd_vel_msg_.linear.x = (targetY-Arr[1])*posGain;
if(abs(targetX-Arr[0])>2) cmd_vel_msg_.linear.y = (targetX-Arr[0])*posGain;
std::cout<< "Vel set "<< std::endl;
}
// once we have reached the target location within tolerance break from the loop
if(abs(targetY-Arr[1])<2 && abs(targetX-Arr[0])<5)
{
break;
}
chatter_pub.publish(cmd_vel_msg_);
// publish the pose
float pose[6]= { 0.591398, 0.522972, 0.532747, 0.796676, 0.821114, 0.904203}; // straight arm pose
std_msgs::Float32MultiArray msg;
for (int i = 0; i < 6; i++)
{
msg.data.push_back(pose[i]);
}
bha_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
// seccond step move the arm to pickup location
{
// broadcast new pose
float pose[6]= {0.803812,0.459433,0.498534, 0.982405, 0.694037,0.855327};
std_msgs::Float32MultiArray msg;
for (int i = 0; i < 6; i++)
{
msg.data.push_back(pose[i]);
}
bha_pub.publish(msg);
ros::Duration(5).sleep(); // wait 5 secconds before closing gripper
// close the gripper
std_msgs::String gripmsg;
std::stringstream ss;
ss << "tolclose" << count;
gripmsg.data = ss.str();
gripper_pub.publish(gripmsg);
ros::Duration(10).sleep(); // wait 5 secconds before closing gripper
}
// set arm back to origional position
std::cout<< "loop3------ " << std::endl;
while (ros::ok())
{
float pose[6]= { 0.591398, 0.522972, 0.532747, 0.796676, 0.821114, 0.904203};
std_msgs::Float32MultiArray msg;
for (int i = 0; i < 6; i++)
{
msg.data.push_back(pose[i]);
}
bha_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
//end program
return 0;
}
void arrayCallback(const std_msgs::Int32MultiArray::ConstPtr& array)
{
int i = 0;
// print all the remaining numbers
for(std::vector::const_iterator it = array->data.begin(); it != array->data.end(); ++it)
{
Arr[i] = *it;
i++;
}
return;
}
===== Launching and configuring your system =====
First you will need to find your pose so you will need to launch poser from the [[robotino_arm_control|Robotino Arm Control]] tutorial. In the completed project you will find the code for poser as well.
run poser
rosrun robotino_control poser
Now pose the arm in the position you want it to be in when it picks up the object and accept the pose. Make sure you copy the pose values before you accept them. The arm will now move to the position you set.
Now you need to find the camera position. So launch the robotino_node your vision node
roslaunch robotino_node robotino_node.launch hostname:=
roslaunch robotino_control vision
You should see a terminal and a window similar to this one
{{dylanw:color.png}}
Read off the x and y coordinates from the terminal window. Save these values. If you don't see a red dot on the image or you don't see any x and y values you need to adjust the HSV values to the object you are using in the vision code and recompile.
Now enter your pose values in the second stage of the talker program. and enter your x and y values as target x and target y
===== Launching your project =====
to launch your project
open 4 terminal windows
terminal 1
roslaunch robotino_node robotino_node.launch hostname:=
terminal 2
rosrun robotino_control BHApose
terminal 3
rosrun robotino_control vision
terminal 4
rosrun robotino_control talker
you can see the procedure int the following video.
{{youtube>hGD6ovrpo-4?large}}