Project tutorial
Omni-Directional People Tracking Friendly Robot

Omni-Directional People Tracking Friendly Robot © CC BY-NC

This omni-directional robot will detect your face, track you and then close in on his prey. Autonomously.

  • 5,479 views
  • 9 comments
  • 29 respects

Components and supplies

L298N Dual Stepper Motor Driver Controller Board Module
Dual motor driver capable of up to 2A o drive the motors
×2
LM2596 DC DC Switching Adjustable Step Down Voltage Regulator
To step down the 12V from the battery to a 5V Rail
×1
Onboard Lipo Alarm Battery Checker Low Voltage Detector RC Plane Quadcopter
Used to sound when the lipo is getting low on voltage to ensure it is not broken during use.
×1
24RPM DC 12V 4mm Shaft Geared Motor
Three for three wheels
×3
Ard nano
Arduino Nano R3
Motor controller
×1
Microsoft LifeCam HD-3000 Web camera
For detecting peoples faces, you can use a cheaper camera but this is the one I had in the office.
×1
11026 02
Jumper wires (generic)
Lots of jumper wires for power management and signal useage
×10
Creator ci20 specs web
Creator Ci20
How could we forget the brains of the operation, the Ci20!
×1
11.1V 3Cell Lipo Battery
Example to buy from ebay attached.
×1

Necessary tools and machines

3drag
3D Printer (generic)
09507 01
Soldering iron (generic)

Apps and online services

About this project

Initial Submission

My initial submission to the "Hackster Terminate the competition" competition was to create a robot that seeked out humans but unlike those in the terminator universe it did not go around killing people, instead it used it's powers for good.

This omni-directional robot will find you, detect you, home in on you and compliment you!

One requirement that I specified to myself is that I would ensure that it could be made without having to build any of the electronics yourself. Every part inside of this robot can be purchased from eBay, plugged together using freely available tutorials and they should work. The case will then be 3D printable so you can either print it yourself or get it made on 3D Hubs. I think this project meets this goal and I will now guide you through the steps of building your own Hunter Flatterer Robot

How it all works!

So let us start with a system diagram that shows all the parts of the robot and how they plug together. We will be referencing this throughout the build and checking it off as we go along.

Don't be too daunted by this diagram if you have never used motors before. It basically boils down to Four main parts:

  • The left hand side is dealing with taking the battery and making sure the voltages are correct for the system.
  • The clever bit is done on the ci20
  • The Arduino is used to tell the motors what to do

The next four sections will mirror this list and guide you through setting up each part.

Dealing with Electrickery

So you've got yourself a battery, what next?

The above diagram shows how to connect up all the components for power, if you follow the wiring in the schematic this is the real life representation.

Connecting the Arduino to the L298N

I could explain this, but it's better to just follow the tutorial I did: http://www.instructables.com/id/Arduino-Modules-L298N-Dual-H-Bridge-Motor-Controll.

So now you've wired everything up, you have blue lights flashing everywhere and no blue smoke so you're good to go. Let's get on to making this bad boy work.

3D Printing yourself a case

When I designed this I decided I wanted two things to happen, it look cool & it shows off the Ci20 as well as possible. I mean this is the point of the competition right?

I designed this robot to be completely 3D printable and the designs are all available below. Basically at this point go and print yourself the top, bottom & wheels. If you don't have access to a 3D Printer you can check out www.3dhubs.com to get someone to print it for you! You can check out the designs below

Here's a pretty render I did of the design to make sure that the ci20 was going to fit and be in pride of place

Once you've got it all printed you can glue all the parts in as above (or use masking tape if you're not feeling two confident. You will need an M4 bolt to hold in the motors.

Making the CI20 do some magic

The CI20 is the boss, the brains of the operation. Without it the robot will flounder. So what does it do?

Well the ci20 is going to use OpenCV to detect your face and then send appropriate commands over serial to the Arduino to get the motors going in the right direction.

Step 1: Install OpenCV

Like I've said throughout this guide, I wasn't trying to reinvent the apple-cart. I'm trying to build something that you can do at home and extend on with relative ease. So to install OpenCV go and follow this tutorial:

It talks you through step-by-step of getting OpenCV up and running.

Step 2: Run the code

The Face Tracking code will need compiling and running, run this command on the file on the Ci20.

g++ -I/usr/local/include/opencv -I/usr/local/include/opencv2 -L/usr/local/lib/ -g -o binary  main.cpp -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml -lopencv_video -lopencv_features2d -lopencv_calib3d -lopencv_objdetect -lopencv_contrib -lopencv_legacy -lopencv_stitching

The binary file to be created: FaceTracking: main.cpp > Source file: FaceTracking.cpp

Easy right?

Ci20 & Arduino Magic

After flashing the Arduino with the code in the attachments below. Plug the Arduino into the USB port on the CI20 and run your recently created FaceTracker. You will see a camera feed pop up (Oh yeah, plug in the webcam) and if you stick your face in the middle the wheels on the robot should start making it drive forward!

Job Done?

Now due to time constraints I didn't quite get it to do anything else, so here's a challenge for anyone that has got this far. There are two things you can add to make this bot super awesome:

Add a set of speakers and some sound files so that when the face is large enough on the screen it plays a sound file to the person

  • If the robot has not detected a face in 1-2 minutes make it spin around on the spot. You can do this by sending the command "R XX" to the Arduino. Replace XX with the amount of time that you want to turn around for.

Code

Arduino Sketch - MainArduino
This Arduino code receives the drive command from the Ci20 over serial and ensures that the motors are driven in the correct fashion to go in the right direction.

I originally wrote this for a Raspberry Pi but it works with the Ci20 aswell
// Motor 1
int dir1PinA = 3;
int dir2PinA = 2;
int speedPinA = 9; // Needs to be a PWM pin to be able to control motor speed

// Motor 2
int dir1PinB = 4;
int dir2PinB = 5;
int speedPinB = 8; // Needs to be a PWM pin to be able to control motor speed

// Motor 3
int dir1PinC = 6;
int dir2PinC = 7;
int speedPinC = 10; // Needs to be a PWM pin to be able to control motor speed

int x = 0;
int y = 0;
int dominantUltrasonic = 0;
bool moveMotor = false;

int startTime = 0;

void setup() {
  Serial.begin(9600);
  pinMode(dir1PinA,OUTPUT);
  pinMode(dir2PinA,OUTPUT);
  pinMode(speedPinA,OUTPUT);
  pinMode(dir1PinB,OUTPUT);
  pinMode(dir2PinB,OUTPUT);
  pinMode(speedPinB,OUTPUT);
  pinMode(dir1PinC,OUTPUT);
  pinMode(dir2PinC,OUTPUT);
  pinMode(speedPinC,OUTPUT);
  startTime = millis();
}

String rpiString;

void loop() {
    
  readDataFromRPi();
  

  // This tests whether we have received a value from the RPi in the last second.
  // It effectively acts as buffer so that it doesn't keep stopping & starting as the move command is not continuous
  // It also allows stoppage if nothign is coming in from the RPi
//  int  elapsedTime =   millis() - startTime; 
//  if (elapsedTime > 1000)
//  {
//    x = 0;
//    y = 0;
//    dominantUltrasonic = 0;
//    moveMotor = false;
//    startTime = millis();
//    
//  }

  // Send the X & Y to the motors
  //if(moveMotor == true && (x != 0 && y != 0))
  //{
    //Serial.println("MovingMotor");
   // driveInDirection(x,y);  
  //}
  //if(x == 0 && y == 0)
  //{
    //Serial.println("ZeroMMotor");
   // driveInDirection(x,y);    
  //} 
  
         
  // This might just be left over from the ultrasonics - hesitant to remove
  delay(30);
}


void readDataFromRPi()
{
   

 // Read from Rpi
  while (Serial.available()) 
  {
    delay(3);  //delay to allow buffer to fill
    if (Serial.available() >0) 
    {
      char c = Serial.read();  //gets one byte from serial buffer
      rpiString += c; //makes the string readString
      if(c == 'n')
      {
        break;
      }
    }     
  } // ENDWHILE

  
  // If something has been read from the RPi then put it into x,y & domniantUltrasonic
  if (rpiString.length() >0) 
  {
      Serial.println(rpiString); //see what was received

      String isRotate = getValue(rpiString, ' ',0);
     

      String xval = getValue(rpiString, ' ', 1);
      String yval = getValue(rpiString, ' ', 2);

      x = xval.toInt();
      y = yval.toInt();
      startTime = millis();

       if (isRotate == "r")
      {
          rotate(x);
      }
      else
      {
        driveInDirection(x,y);  
      }

    
      
      
      
      rpiString="";
  } //ENDIF
}



 String getValue(String data, char separator, int index)
{
 int found = 0;
  int strIndex[] = {0, -1  };
  int maxIndex = data.length()-1;
  for(int i=0; i<=maxIndex && found<=index; i++){
  if(data.charAt(i)==separator || i==maxIndex){
  found++;
  strIndex[0] = strIndex[1]+1;
  strIndex[1] = (i == maxIndex) ? i+1 : i;
  }
 }
  return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}
Arduino Sketch - MotorControlArduino
This goes with the main ino file and needs including in the same project file
/* 
 *  Motor control code
 *  
 *  This class will include code to make the robot go in any direction 
 *  and rotate around the center point.
 */

void driveInDirection(float newX, float newY)
{
  delay(20);
  
  float x = newX;
  float y = newY;
  
  float theta = atan2(y,x);
  float mag = sqrt((x*x) + (y*y));
  float vx = mag * cos(theta);
  float vy = mag * sin(theta);
  
  float w1 = -vx;
  float w2 = 0.5 * vx - sqrt(3)/2 * vy;
  float w3 = 0.5 * vx + sqrt(3)/2 * vy;
  
  // Get largest w value
  float wSet[] = {w1, w2, w3};
  float largestValue = 0.0;
  
  for (int i = 0; i < 3; i++)
  {
    if(abs(wSet[i]) > largestValue)
    {
        largestValue = abs(wSet[i]);
    }
  }
  
  float speedCoef = (float)147.0 / largestValue;
  
  w1 = w1 * speedCoef;
  w2 = w2 * speedCoef;
  w3 = w3 * speedCoef;   

  if (x ==0 && y == 0)
  {
      w1 = 0;
      w2 = 0;
      w3 = 0;
  }
  
  Serial.println(w1);
  Serial.println(w2);
  Serial.println(w3);
  
  w1 = constrain(w1, -150, 150);
  w2 = constrain(w2, -150, 150);
  w3 = constrain(w3, -150, 150);
  
  boolean w1_ccw = w1 < 0 ? true : false;
  boolean w2_ccw = w2 < 0 ? true : false;
  boolean w3_ccw = w3 < 0 ? true : false;
  
  byte w1_speed = (byte) map(abs(w1), 0, 150, 0, 255);
  byte w2_speed = (byte) map(abs(w2), 0, 150, 0, 255);
  byte w3_speed = (byte) map(abs(w3), 0, 150, 0, 255);
  
  printMotorSpeed(w1_speed, 1);
  printMotorSpeed(w2_speed, 2);
  printMotorSpeed(w3_speed, 3);
  
  analogWrite(speedPinA, w1_speed);//Sets speed variable via PWM 
  analogWrite(speedPinB, w2_speed);
  analogWrite(speedPinC, w3_speed);//Sets speed variable via PWM 
  
  digitalWrite(dir1PinA, !w1_ccw);
  digitalWrite(dir2PinA, w1_ccw);
    
  digitalWrite(dir1PinB, !w2_ccw);
  digitalWrite(dir2PinB, w2_ccw);
  
  digitalWrite(dir1PinC, w3_ccw);
  digitalWrite(dir2PinC, !w3_ccw);
}

void rotate(float milliseconds)
{
 float  w1 = 255;
  float w2 = 255;
  float w3 = 255;
  
  boolean w1_ccw = w1 < 0 ? true : false;
  boolean w2_ccw = w2 < 0 ? true : false;
  boolean w3_ccw = w3 < 0 ? true : false;
  
  byte w1_speed = (byte) map(abs(w1), 0, 150, 0, 255);
  byte w2_speed = (byte) map(abs(w2), 0, 150, 0, 255);
  byte w3_speed = (byte) map(abs(w3), 0, 150, 0, 255);
  
  printMotorSpeed(w1_speed, 1);
  printMotorSpeed(w2_speed, 2);
  printMotorSpeed(w3_speed, 3);
  
  analogWrite(speedPinA, w1_speed);//Sets speed variable via PWM 
  analogWrite(speedPinB, w2_speed);
  analogWrite(speedPinC, w3_speed);//Sets speed variable via PWM 
  
  digitalWrite(dir1PinA, !w1_ccw);
  digitalWrite(dir2PinA, w1_ccw);
    
  digitalWrite(dir1PinB, !w2_ccw);
  digitalWrite(dir2PinB, w2_ccw);
  
  digitalWrite(dir1PinC, w3_ccw);
  digitalWrite(dir2PinC, !w3_ccw);
  
  delay(milliseconds);
  
  analogWrite(speedPinA, 0);//Sets speed variable via PWM 
  analogWrite(speedPinB, 0);
  analogWrite(speedPinC, 0);//Sets speed variable via PWM 

}

void printMotorSpeed(byte motorSpeed, int motor)
{
    Serial.print("Motor");
    Serial.print(motor);
    Serial.print(": ");
    Serial.println(motorSpeed); 
}
FaceTracking C++ For Ci20C/C++
Follow guide in Story
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

CascadeClassifier face_cascade, eyes_cascade;
String window_name = "Face Detection";

#include <termios.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>


int sendSerial(char* message)
{
int fd = open("/dev/ttyUSB0", O_RDWR);
if (fd == -1)
{
    perror("dev/ttyUSB0");
    return 1;
}

struct termios tios;
tcgetattr(fd, &tios);

tios.c_iflag = IGNBRK | IGNPAR;
tios.c_oflag = 0;
tios.c_lflag = 0;
cfsetspeed(&tios, B9600);
tcsetattr(fd, TCSAFLUSH,&tios);

usleep(1000);

//char msg[] = "50 50";
write(fd, message, strlen(message));

return  0;
}


/**
 * Detects faces and draws an ellipse around them
 */

void detectFaces(Mat frame) {

  std::vector<Rect> faces;
  Mat frame_gray;

  // Convert to gray scale
  cvtColor(frame, frame_gray, COLOR_BGR2GRAY);

  // Equalize histogram
  equalizeHist(frame_gray, frame_gray);

  // Detect faces
  face_cascade.detectMultiScale(frame_gray, faces, 1.1, 3,
				0|CASCADE_SCALE_IMAGE, Size(30,30));

  // Iterate over all of the faces
  for(size_t i = 0; i < faces.size(); i++) {

    // Find center of faces
    Point center(faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2);

    Mat face = frame_gray(faces[i]);
    std::vector<Rect> eyes;

    // Try to detect eyes, inside each face
   // eyes_cascade.detectMultiScale(face, eyes, 1.1, 2,
	//			  0 |CASCADE_SCALE_IMAGE, Size(50, 50) );

 //   if(eyes.size() > 0)
      // Draw ellipse around face
      ellipse(frame, center, Size(faces[i].width/2, faces[i].height/2),
	      0, 0, 360, Scalar( 255, 0, 255 ), 4, 8, 0 );
		  
	if(center.x > frame.cols/3 && center.x < frame.cols/3*2)
	{
		char msg[] = "F 50 50";
		sendSerial(msg);
	}
	else
	{
		char msg[] = "0 0 ";
		sendSerial(msg);
	}
		
	
  }

  // Display frame
  imshow( window_name, frame );
}


int main() {
	
	char msg[] = "0 0 ";
	sendSerial(msg);
	

  VideoCapture cap(0); // Open default camera
  cap.set(CV_CAP_PROP_FRAME_WIDTH, 480);
  cap.set(CV_CAP_PROP_FRAME_HEIGHT, 240);
  Mat frame;

  if(!face_cascade.load("haarcascade_frontalface_default.xml"))
  {
	std::cout << "classifier";
	return -1;
  } // load faces
 // eyes_cascade.load("haarcascade_eye_tree_eyeglasses.xml"); // load eyes

  while(cap.read(frame)) {
    detectFaces(frame); // Call function to detect faces
    if( waitKey(30) >= 0)    // pause
      break;
  }
  return 0;
}

Custom parts and enclosures

Base of Robot
This is the base of the omnidirectional robot, designed by me to fit all the parts in and have a lid attached
Lid of Robot
This is the lid which shows off the Ci20 in all it's glory.
Omni Directional Wheels
This is the original design of the omni-directional wheels. I used one half of this with the rims and then edited it to include the motor shaft
Wheel with shaft
A remixed wheel from the thingiverse link

Schematics

Schematic and "Wiring Diagram"
See better description in story section
Untitled%20diagram

Comments

Similar projects you might like

Smart Face Tracking Robot Car

Project tutorial by Sebastian Hernandez

  • 5,967 views
  • 5 comments
  • 36 respects

Otto DIY+ Arduino Bluetooth Robot Easy to 3D Print

Project tutorial by Team Otto builders

  • 48,231 views
  • 117 comments
  • 162 respects

Ball Tracking Robot

Project showcase by Rohan Juneja

  • 38,444 views
  • 34 comments
  • 86 respects

OttoDIY Build Your Own Robot in One Hour!

Project tutorial by Camilo Parra Palacio

  • 104,231 views
  • 128 comments
  • 296 respects

3D Printed and Expandable Robot for Arduino

Project showcase by Matthew Hallberg

  • 2,340 views
  • 3 comments
  • 10 respects

MeArm Robot Arm - Your Robot - V1.0

Project tutorial by Benjamin Gray

  • 19,302 views
  • 3 comments
  • 34 respects
Add projectSign up / Login