patterncppMinor
Algorithm for motion detection using ultrasonic sensor on Arduino
Viewed 0 times
arduinosensoralgorithmusingdetectionformotionultrasonic
Problem
I am using a ultrasonic sensor to detect motion by looking at the variance of the sensor readings, however my algorithm is very prone to give false positives thus erroneously rising a motion event.
Algorithm Background
What I do is send a ping 20 times a second and wait for a pulse to come back using the input capture on arduino. I have a limit of 2 metres because I don't need to detect motion beyond that. Using a modifed version of the NewPing library which has a method
The following conditions need to be met to rise a motion event:
-
If the last measured pulse was a valid pulse (i.e
another positive pulse and only then test for the variance of the two
pulses. However if it failed to measure a valid pulse after the first
success it will reset the array.
-
If a variance greater than or equal to 500 is measured within a time
frame it will rise a motion event.
The code for my algorithm is this:
```
#include
#define TRIGGER_PIN 10 // Arduino pin tied to trigger pin on ping sensor.
#define ECHO_PIN 9 // Arduino pin tied to echo pin on ping sensor.
#define MAX_DISTANCE 200 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
unsigned int pingSpeed = 50; // How frequently are we going to send out a ping (in milliseconds). 50ms would be 20 times a second.
unsigned long pingTimer; // Holds the next ping time.
unsigned long last = 0;
unsigned int samples = 2;
unsigned int distances[2];
unsigned int sum = 0;
void setup() {
Serial.begin(9600); // Open serial monitor at 9600 baud to see ping results.
pingTimer = millis(
Algorithm Background
What I do is send a ping 20 times a second and wait for a pulse to come back using the input capture on arduino. I have a limit of 2 metres because I don't need to detect motion beyond that. Using a modifed version of the NewPing library which has a method
check_timer which returns a value greater than 1 if the pulse from the arduino took more than a corresponding wait time for 2 metres and returns 1 if no pulse has been measured yet or 0 if it successfully got a pulse.The following conditions need to be met to rise a motion event:
-
If the last measured pulse was a valid pulse (i.e
check_timer returns 0) it will save it in an array and wait foranother positive pulse and only then test for the variance of the two
pulses. However if it failed to measure a valid pulse after the first
success it will reset the array.
-
If a variance greater than or equal to 500 is measured within a time
frame it will rise a motion event.
The code for my algorithm is this:
```
#include
#define TRIGGER_PIN 10 // Arduino pin tied to trigger pin on ping sensor.
#define ECHO_PIN 9 // Arduino pin tied to echo pin on ping sensor.
#define MAX_DISTANCE 200 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
unsigned int pingSpeed = 50; // How frequently are we going to send out a ping (in milliseconds). 50ms would be 20 times a second.
unsigned long pingTimer; // Holds the next ping time.
unsigned long last = 0;
unsigned int samples = 2;
unsigned int distances[2];
unsigned int sum = 0;
void setup() {
Serial.begin(9600); // Open serial monitor at 9600 baud to see ping results.
pingTimer = millis(
Solution
Late or spurious echoes
One thing that can happen is that during one ping interval of 50ms you can get echoes from objects up to 8.5m away (
As you can guess, this would be bad, it would wrongly appear as being a close echo while it in fact is distant. I'm not familiar with the strength of the ultrasonic on your chip; 8.5m may be out of range in which case this isn't the problem. But if it isn't then your library should send each ping request with a different frequency and check the frequency of the echo to know if you got the right echo, it should also check the amplitude of the echo for feasibility. Again I don't know if your library does any of this but you should check. Otherwise this might be causing your troubles. If 8.5m is just on the edge of the range it is conceivable that some times you get the echo and some times you don't.
Filtering
If you can rule out the above and you're certain your implementation is not at fault, then you can simply assume that it's a transient at work. They happen when dealing with sensors, and sound is finicky that way with echoes and everything.
Best way to deal with transients is to use some kind of filtering. Median might work well for you and I see that NewPing has a "ping_median" function, try this with a filter size of maybe 5 or thereabouts see if that helps.
Variance
Using two samples to estimate any kind of variance is way too little, the result is not in any way reliable. You are also very sensitive to transients and errors are you are calculating the square of the values. I would suggest another approach.
Another approach
If you want to detect movement you want to detect changes in the echo delay. So what you do is setup your timer to consistently ping at a fixed rate then keep a low pass filtered running average of the echo delay and look at the differential of this (the rate of change).
Some pseudocode:
Explanation
Every 50 ms you send out a ping and look for the echo. If you don't get an echo for 10 iterations or so, simply reset the
If you get an echo you low pass filter it with
Hope this was of some help :s
One thing that can happen is that during one ping interval of 50ms you can get echoes from objects up to 8.5m away (
0.05[s] *354[m/s] / 2 ). If the object is a little further than that, say 9m, you will get the echo early during the next ping period instead.As you can guess, this would be bad, it would wrongly appear as being a close echo while it in fact is distant. I'm not familiar with the strength of the ultrasonic on your chip; 8.5m may be out of range in which case this isn't the problem. But if it isn't then your library should send each ping request with a different frequency and check the frequency of the echo to know if you got the right echo, it should also check the amplitude of the echo for feasibility. Again I don't know if your library does any of this but you should check. Otherwise this might be causing your troubles. If 8.5m is just on the edge of the range it is conceivable that some times you get the echo and some times you don't.
Filtering
If you can rule out the above and you're certain your implementation is not at fault, then you can simply assume that it's a transient at work. They happen when dealing with sensors, and sound is finicky that way with echoes and everything.
Best way to deal with transients is to use some kind of filtering. Median might work well for you and I see that NewPing has a "ping_median" function, try this with a filter size of maybe 5 or thereabouts see if that helps.
Variance
Using two samples to estimate any kind of variance is way too little, the result is not in any way reliable. You are also very sensitive to transients and errors are you are calculating the square of the values. I would suggest another approach.
Another approach
If you want to detect movement you want to detect changes in the echo delay. So what you do is setup your timer to consistently ping at a fixed rate then keep a low pass filtered running average of the echo delay and look at the differential of this (the rate of change).
Some pseudocode:
float g = 0.9f
float avg_time = max_time
float avg_dt = 0.0f
float dt_hysteresis = 0.01f
for each 50ms
if no echo for 10 iterations
avg_time = max_time
continue
float echo_time = sensor.read()
if avg_time == max_time
avg_time = echo_time
avg_dt = 0.0f
else
float prev_avg_time = avg_time;
avg_time = avg_time * g + (1.0f - g) * echo_time
avg_dt = avg_dt * g + (1.0f - g) * (avg_echo_time - prev_avg_time);
if avg_dt +dt_hysteresis
// Some one leaving
end forExplanation
g is a filter coefficient between 0 and 1.0, the higher it is the more "inert" your filter will be. It will be slower to react but be highly resistant to noise. If g is low the filter will react quickly but also more susceptible to noise, to put it simply. You can think of it as a low-pass filter where g implicitly controls the cut-off frequency. Normal values are in the range 0.7 to 0.95.Every 50 ms you send out a ping and look for the echo. If you don't get an echo for 10 iterations or so, simply reset the
avg_time variable so that you can start fresh the next time you get an echo and not be affected by what happened to be stored there since the last echo.If you get an echo you low pass filter it with
avg_time = avg_time g + (1.0f - g) echo_time and then look at the lowpass filtered change in the lowpass filtered time value avg_dt = avg_dt g + (1.0 - g) (avg_echo_time - prev_avg_time). If this change is larger than +- the hysteresis threshold dt_hysteresis you then deduce that some one is approaching or moving away from the sensor. You can also consider looking directly at avg_echo_time - prev_avg_time the extra lowpass filtering is simply to get rid of noise which is always present when we differentiate a real signal.Hope this was of some help :s
Code Snippets
float g = 0.9f
float avg_time = max_time
float avg_dt = 0.0f
float dt_hysteresis = 0.01f
for each 50ms
if no echo for 10 iterations
avg_time = max_time
continue
float echo_time = sensor.read()
if avg_time == max_time
avg_time = echo_time
avg_dt = 0.0f
else
float prev_avg_time = avg_time;
avg_time = avg_time * g + (1.0f - g) * echo_time
avg_dt = avg_dt * g + (1.0f - g) * (avg_echo_time - prev_avg_time);
if avg_dt < -dt_hysteresis
// Some one approaching
else if avg_dt > +dt_hysteresis
// Some one leaving
end forContext
StackExchange Code Review Q#110211, answer score: 3
Revisions (0)
No revisions yet.