Real-Time Object Tracker In C++
Contents
Introduction
Background
Using the code
The library
Results
Points of interest
Introduction
This article is about tracking moving or static objects with a conventional web cam at real-time speed. A simple way of tracking is to compare a predefined background image with the same background frame when objects start to appear. This scenario is applicable in static surroundings where you can learn the background image without any foreground articles you’d expect to track (e.g., indoors, landings, offices, shops, warehouses etc...). In the case that an article moves and remains static for a prolonged period of time in the scene, you may consider it as becoming the part of the scene now, and superimpose its image to the background to avoid further detections. The same is applicable if some background item is removed from the scene. The drawbacks of this approach include illumination or cam position change. In that case, you will have to estimate the new background again. Another solution is to use edge operators to avoid illumination specific changes. But you will need additional code to devise if you’d like to identify the object’s body and not just its outline.
The conventional web cam 640x480 resolution is far redundant. You may downscale it about three times to remove the noise usually present and significantly boost the speed of processing without essential loss of tracking abilities, unless you expect to track very minute objects. The downscaling step allows to achieve great processing speeds in object tracking. It runs about 100fps on a 2Ghz single core when no objects are present, and from 30 to 90fps when there are. The bigger the object, the more time is required to estimate all pixels belonging to the blob.
Background
ImageBlobs
ImageBlobs
ImageBlobs
The article and code are based on my previous submissions, except the ImageBlobs
class. You should have a look at the following articles if you have questions about the GUI interface or particular code fragments:
2D Fast Wavelet Transform Library for Image ProcessingFast Dyadic Image Scaling with Haar Transform2D Vector Class Wrapper SSE Optimized for Math OperationsVideo Preview and Frames Capture to Memory with SampleGrabber in Buffered ModeFace Detection C++ Library with Skin and Motion Analysis
The GUI is targeted at 640x480 web cams, and if you want to use it for different resolutions, have a look at the Face Detection article to add the required changes. You can not expect me to provide you a complete application for every custom video device.
Using the code
start tracking
start tracking
start tracking
Setup the camera and frames capture rate as described in Video Preview and Frames Capture to Memory with SampleGrabber in Buffered Mode and start video capture. Click the background radio box to start the background estimation process first. All the captured frames will be added together, and the mean background frame will be estimated and saved to a JPEG file, background.jpg, once you have clicked the start tracking
radio box to start object tracking. I suggest you point your camera to some place where you can expect moving objects. Estimate the background for about several seconds to cope with camera noise or some casual moving articles appearing in the scene for short periods of time. The mean background estimate will effectively remove them. Now, introduce the objects to the scene you want to track, or wait for them to appear there if they are alive.
The library
The mean estimate of the background frame is advisable as it allows to filter out any noise or tiny movements.
cpp
Shrink ▲ Copy Code
The new classes in the library are:
MotionDetector
ImageBlobs
MotionDetector
ImageResize
MotionDetector
ImageResize
MotionDetector
ImageResize
The changes in the MotionDetector
allow to set the background frame to which the comparison will be done with every new image frame, rather than taking the difference between consecutive frames as in the Face Detection article. The background frame and the new image frame are presented with ImageResize
objects.
MotionDetector
MotionDetector
MotionDetector
First, you need to initialize the MotionDetector
object with the video frame width, height, and downscaling factor:
void MotionDetector::init(unsigned int image_width, unsigned int image_height, float zoom);
zoom
zoom
zoom
To downscale the image three times, use zoom
= 0.125f.
set_background()
set_background()
set_background()
Invoke the set_background()
function once you finish the background estimation process:
cpp
Shrink ▲ Copy Code
detect()
detect()
detect()
Now, you may call the detect()
function to estimate the pixels belonging to the foreground objects:
cpp
Shrink ▲ Copy Code
You may use either RGB values comparison or gray image data. The latter, I found not robust for similar looking colors when converted to gray values. The dilation and erosion operators allow to fill any gaps in the object’s blobs that might appear due to similar pixel colors of the background and the object, and remove the noise of some tiny movements or very thin objects.
ImageBlobs
ImageBlobs
ImageBlobs
The returned vector is used for blobs extraction with the ImageBlobs
object. You will need the following functions to use the class:
void ImageBlobs::init(unsigned int width, unsigned int height);
int ImageBlobs::find_blobs(const vec2Dc& image, unsigned int min_elements_per_blob = 0);
void ImageBlobs::find_bounding_boxes();
void ImageBlobs::delete_blobs();
init(zoom * image_width, zoom * image_height)
find_blobs()
image
MotionDetector::detect()
image
init(zoom * image_width, zoom * image_height)
find_blobs()
image
MotionDetector::detect()
image
init(zoom * image_width, zoom * image_height)
find_blobs()
image
MotionDetector::detect()
image
First, you need to initialize the object to the downscaled image width and height (e.g., init(zoom * image_width, zoom * image_height)
). Then, you may proceed estimating blobs with find_blobs()
on the image
returned from the MotionDetector::detect()
function call. The function searches for non-zero elements in the image
vector adjoining horizontally or vertically, forming a blob. Then, it marks every element with the current found blob number.
image
image
image
For example, the 10x10 vector used as image
vector:
Shrink ▲ Copy Code
find_blobs()
image
const vec2Dc* ImageBlobs::get_image() const
find_blobs()
image
const vec2Dc* ImageBlobs::get_image() const
find_blobs()
image
const vec2Dc* ImageBlobs::get_image() const
find_blobs()
will estimate three blobs from that image
. You may get the vector containing the found blobs with the const vec2Dc* ImageBlobs::get_image() const
function.
Shrink ▲ Copy Code
min_elements_per_blob
min_elements_per_blob
min_elements_per_blob
min_elements_per_blob
min_elements_per_blob
min_elements_per_blob
With min_elements_per_blob
, you may discard small blobs from being detected (e.g., min_elements_per_blob
= 5 will leave the third blob undetected).
To access the elements of the found blobs, you may use the following functions:
inline unsigned int ImageBlobs::get_blobs_number() const;
inline const struct Blob* ImageBlobs::get_blob(unsigned int i) const;
Blob
Blob
Blob
The blob is returned in the Blob
structure:
cpp
Shrink ▲ Copy Code
elements_number
elements
Element
elements_number
elements
Element
elements_number
elements
Element
where elements_number
is the number of elements in the blob contained in the elements
array of Element
structures.
cpp
Shrink ▲ Copy Code
neighbs
coord
image
neighbs
coord
image
neighbs
coord
image
The neighbs
contain the directly adjoining neighboring elements, and coord
is the element coordinate in the image
vector.
cpp
Shrink ▲ Copy Code
find_blobs()
find_bounding_boxes()
Blobs::bounding_box
RECT
find_blobs()
ImageBlobs
delete_blobs()
find_blobs()
find_bounding_boxes()
Blobs::bounding_box
RECT
find_blobs()
ImageBlobs
delete_blobs()
find_blobs()
find_bounding_boxes()
Blobs::bounding_box
RECT
find_blobs()
ImageBlobs
delete_blobs()
After you call find_blobs()
, you may optionally invoke find_bounding_boxes()
to estimate the bounding boxes of all blobs found to Blobs::bounding_box
window’s RECT
structure. Before the next call to find_blobs()
, you need to delete the found blobs from the ImageBlobs
object with delete_blobs()
.
find_blobs()
find_blobs()
find_blobs()
The find_blobs()
function is shown below:
cpp
Shrink ▲ Copy Code
add_*_neighbour()
th
Element
blob
Blob
add_*_neighbour()
th
Element
blob
Blob
add_*_neighbour()
th
Element
blob
Blob
The add_*_neighbour()
functions check for the directly adjoining image element from above, below, left, or right from the ith Element
in the current blob
and adds it to the Blob
elements array:
cpp
Shrink ▲ Copy Code
has_neighbour()
is_element_present
blob
has_neighbour()
is_element_present
blob
has_neighbour()
is_element_present
blob
has_neighbour()
and is_element_present
determine if the new element is already present in the blob
:
cpp
Shrink ▲ Copy Code
Results
Here, I have selected a static background and performed some object tracking experiments with the objects at hand. The background I used is depicted below. It was averaged over a period of several seconds.
The next two objects (an LCD-TFT screen cleaner and mobile phone) are tracked at 66.67fps.
The mobile phone power adaptor is tracked at 71.43fps. You see the cord is not detected due to erosion and dilation operators, as it is quite thin.
Now, some pens and keys: 90.91fps.
A more complex scenario: three different objects detected at 29.41fps.
Jimi Hendrix (the Are You Experienced album, very good sound) and 9v batteries at 55.56fps.
The other background setup:
Several 9v batteries and a roll on the carpet detected at 90.91fps. You see one battery is left undetected due to the minimum number of elements limit.
Power adaptor, LCD-TFT cleaner, and 9v batteries detected at 90.91fps. The tiny ones are left undetected again, but this time due to erosion and dilation operators.
Next, some candle and mobiles at 83.33fps.
Now, as you can see, the bigger the size of the object, the more time is required to estimate the elements of the blobs. Also, it does not handle the shadows cast by objects on the white back wall and on the table. However, more pronounced shadows will be tracked as pertaining to the object.
Points of interest
You may extend the algorithm to monitor an object’s bounding box positions. In case they will be static for a specified amount of time, you may add that region to the background scene image. Thus, the object becomes part of the scene (e.g., some object moved to the scene and remaining static).