Object Tracking Camera with Raspberry Pi and TensorFlow Lite

Object tracking camera
Object tracking camera

Introduction

In the previous tutorial, we run the new TensorFlow Lite object detection sample program on Raspberry Pi. In fact, we can modify this example and build our own object tracking camera. Specifically, we can achieve this with the following few steps:

  • attach the camera to a mount that can be moved by a servo motor,
  • use the object detection results from the example program to calculate the position of an object relative to the screen centre,
  • move a servo motor according to the calculation result.

NOTE: Before you proceed, make sure you have installed all the necessary libraries and activate the correct virtual environment. If you haven’t done so, check out this tutorial for how you can do that.

Pan-tilt Camera Mount and Servo Motors

For simplicity, we will attach the camera to the pan-tilt kit from Adafruit. It comes with a camera mount and two servo motors. Therefore, for the hardware side, all we need to do is to connect the servo motors to the Raspberry Pi with a few jumper wires and attach the camera to the camera mount. Moreover, we will demonstrate how the pan motion can be controlled with the object detection program only. The part for the tilt motion is very similar and we leave it to you as an exercise.

Control a Servo Motor with Raspberry Pi

Controlling a servo motor with Raspberry Pi is quite similar to controlling it with an Arduino. First, we connect the servo motor to the GPIOs of the Raspberry Pi as follows.

Servo Raspberry Pi
VCC (red wire) 5V
GND (brown wire) GND
PWM signal (orange wire) Pin 11 (GPIO17)


Inside the folder ~/ai/examples/lite/examples/object_detection/raspberry_pi, create a new Python script file called servo.py. Put the following code inside the file:

# Import libraries
import RPi.GPIO as GPIO
import time

# Set GPIO numbering mode
GPIO.setmode(GPIO.BOARD)


class Servo():
    def __init__(self, pin, freq=50):
        self.pin = pin
        self.freq = freq
        self.started = False

    def start(self):
        GPIO.setup(self.pin, GPIO.OUT)
        self.output = GPIO.PWM(self.pin, self.freq)
        self.output.start(0)
        self.started = True
        print("Start sending signal to the servo.")

    def set_angle(self, angle):
        if not self.started:
            print('The servo has not started sending signal.')
            return
        if angle < 0 or angle > 180:
            raise ValueError('The angle must be between 0 and 180')
        self.output.ChangeDutyCycle(2+(angle/18))
        time.sleep(0.5)
        self.output.ChangeDutyCycle(0)

    def end(self):
        if self.started:
            self.started = False
            self.output.stop()
            GPIO.cleanup()
            print("Stop sending signal to the servo.")

    def __del__(self):
        self.end()


def main():
    try:
        # Set pin 11 as an output, and pulse 50Hz
        servo = Servo(11, 50)
        # Let the Raspberry Pi send signal to the servo motor
        servo.start()
        while True:
            try:
                # Ask user for angle and turn servo to it
                angle = float(input('Enter angle between 0 & 180: '))
                # Set the angle
                servo.set_angle(angle)
            except ValueError as e:
                print(e)
                continue
    except KeyboardInterrupt:
        # Press Ctrl+C to end the program
        print("End of program.")
    finally:
        # Cleanup at the end of the program
        if servo.started:
            servo.end()


if __name__ == "__main__":
    main()

You can execute the script servo.py by running the following command in the terminal:

(tfl) pi@raspberrypi:~/ai/examples/lite/examples/object_detection/raspberry_pi$ python servo.py

The program will ask you to enter a number from 0 to 180. The servo motor will turn to the specified position once you enter the number. We won’t go into the details of this script in this tutorial, but we recommand you to watch the following excellent video from ExplainingComputers to understand how this script actually works.

Modifying the Object Detection Program

Inside the folder ~/ai/examples/lite/examples/object_detection/raspberry_pi, there are other .py files other than the detect.py that we executed in the last tutorial and the servo.py that we just wrote. In particular, the file utils.py contains functions that handle the object detection results. We only need to modify this file for making our object tracking camera.

The strategy of moving the camera is simple. We just compare the centre of the bounding box of the target object with the image centre and determine, and move the servo motors such that the difference between these two centres can be reduced. In other words, the movement can be determined by the vector from the bounding box centre to the image centre. Both of these centres can be found in the visualize function inside utils.py. Therefore, if we add some codes to find the coordinates of these centres, calculate the vector and use this vector to move the servo in visualize, our object tracking project is basically done.

Before we modify visualize, we need to add some codes in utils.py.

First, we import the Servo class that we wrote earlier as well as the time library.

from servo import Servo
import time

Then, we set up the servo motor by creating an object of the Servo class. After the initialisation, we set the servo position to 90 degree as its initial centred position.

Then, we define two variables pan_angle and move_threshold. The pan_angle variable stores the current position of the pan servo and the x_move_threshold stores the minimum difference between the x-coordinate of the bounding box centre and that of the image centre that can trigger a movement.

pan_angle = 90
x_move_threshold = 50

Afterwards, we define the function move that moves the servo motor according to the vector from the bounding box centre to the image centre. The function itself is self-explanatory.

def move(vector):
  global pan_angle, pan_servo
  if abs(vector[0]) > x_move_threshold:
    if vector[0] < 0:       # object is on the right of the screen
      pan_angle += 5        # move the camera in the anticlockwise direction, i.e. increase the servo angle
    else:                   # object is on the left of the screen
      pan_angle -= 5        # move the camera in the clockwise direction, i.e. reduce the servo angle
    
    # Make sure that the angle is within 0 to 180
    if pan_angle < 0:
      pan_angle = 0
    if pan_angle > 180:
      pan_angle = 180
    pan_servo.set_angle(pan_angle)
    
    # The tilt angle can be set in the same way with vector[1]s

    # The servo needs some time to move
    time.sleep(1)

NOTE: If you use the PiCamera module to capture the image, the image is horizontally flipped and you may need to adjust the direction of movement accordingly.

Now, we can modify the visualize function. Before the line return image, add the following codes.

if (class_name == 'bottle' and probability > 0.5):
    image_centre = (image.shape[1]/2, image.shape[0]/2)
    xmin, xmax, ymin, ymax = detection.bounding_box.left, detection.bounding_box.right, detection.bounding_box.top, detection.bounding_box.bottom
    bounding_box_centre = ((xmin+xmax)/2, (ymin+ymax)/2)
    vector = np.array(image_centre) - np.array(bounding_box_centre)
    move(vector)

Let’s take a look of them in details. First of all, we need to decide what object we want to track and the detection sensitivity. We can use the class_name and probability variable to construct the condition we want. For instance, we can move the camera when a bottle is detected and the probability is over 0.5 (50%).

if (class_name == 'bottle' and probability > 0.5):

Then, we can calculate the image centre.

    image_centre = (image.shape[1]/2, image.shape[0]/2)

Similarly, we calculate the bounding box centre of the target object.

    xmin, xmax, ymin, ymax = detection.bounding_box.left, detection.bounding_box.right, detection.bounding_box.top, detection.bounding_box.bottom
    bounding_box_centre = ((xmin+xmax)/2, (ymin+ymax)/2)

With the two centres, we can calculate the vector we need.

    vector = np.array(image_centre) - np.array(bounding_box_centre)

Finally, we call the move function that we wrote to move the servo.

    move(vector)

After the modification, we can run detect.py (make sure that the virtual environment with all the necessary libraries has been activated) and the camera will track the target object!

The camera is tracking the bottle
The camera is tracking the bottle

The sample code for this project can be found here.