Motion detection of waving

Looking for a low cost way to add detection of waving to outdoor Halloween animatronic figures. Would have to detect kids in Halloween costumes waving at the figures from ~2-5 ft away. Just the bulk motion of waving, not hand detection or gesture recognition. Can it be done for < $10? Would have to differentiate waving motion from all other motions ideally. Ideally would run on an ESP8266 board.

Exactly which parts of their bodies are these children waving?

1 Like

Very likely, with an ESP32-CAM running shape and motion detection code on captured images.

1 Like

LOL. If you can do hand gesture detection on kids outdoors wearing Halloween costumes in the dark for < $10 please do share! :wink: Or possibly glove, mitten, talon and hook gesture detection?

The idea I have is use optical or ultrasonic sensors. I had a small motion activated toy once that I dissected and found it had a photoresistor at the far end of a small ~1" long black plastic tube which opened to the front of the toy, narrowing it's field of view. I was surprised it worked in a variety of light conditions. I guess the chip was programmed to look at the rate of change of the signal not the base level. I figured I might be able to use a similar arrangement and apply some fancy signal processing to detect waving. Maybe a FFT to detect repetitive motion in a certain frequency range? I have found many Lux sensors, IR sensors, UV sensors, ultrasonic sensors. I have a few and will run experiments. Maybe multiple sensors are needed to detect oscillating motion. I'm sure there's a solution that has already been developed. The figures (~10) are humanoid like life size so kids might wave at them from 'normal waving distances' so the fluctuations in signal would prob be pretty small, but there are some pretty sensitive sensors. I'm willing to make this an open project its just a Halloween display for my home. I have already presence detection working well with std PIR sensors, it required stabilizing the power supply and good connections. I am just beginning investigations today but I thought I'd ask because I assume its prob. been done before many times. I do have an ESP32cam so I'll look into that also, do you know of a similar project for it I can look at?

Certainly worth a try. A narrow digital bandpass filter can be much faster than an FFT.

1 Like

Paying 10 bucks to some other kids to hide in the back and pull strings when seeing a waving kid would work better.

Anyway, "other projects" use the ESP32-CAM as a scheap webcam and run object detection on a RPi - or something bigger.

1 Like

Well I tried the HC-SR04 ultrasonic sensor, 2 of them, but the signals were often varying erratically and I used 50-100ms between pings. However sometimes the signals were clean and it possible to see the signal moving during waving, but using a digital LPF to reduce noise dampened the response such that it appeared not usable for detection. The signals were clean only when waving a larger object like a file folder, and within about 4ft. The test was indoors, I thought maybe there could be interfering echos, I'll try outside. I also tested a TSL2561 luminosity sensor and shielding it. In dim lighting it was at the minimum of its range at the high gain setting. It would show small signal variation if I shined a flashlight on my body and moved my hand through the light beam as it reflected more light than my shirt. But only from ~ 2-3 ft away. I am going to test a TSL2591 High Dynamic Range light sensor and a ALS-PT19 analog light sensor both from Adafruit. These seemed like they would be good at higher sensitivity and low light levels. Any recommendations? I also thought to try shielding a pir sensor to reduce sensitivity and field of view. BTW I estimate waving frequency to be ~2-4Hz (back and forth as 1 cycle).

You might try the inexpensive RCWL microwave motion detector (it is NOT a proximity detector, nor is it "RADAR", as the title of the article suggests).

1 Like

I have some of those and found them extremely sensitive. Especially to their mounting. I found if I moved the sensor any detectable amount it would detect motion. Also it almost continuously false signaled in my opinion. I was going to see if the signal they are using to determine motion threshold could be accessed. There is a pir sensor where you can access the actual signal from SparkFun called OpenPir but it's $18. Maybe I'll try that later. Do you know of radar sensors where you can access the actual signal?

I don't know what you mean by the "actual signal", but the link I posted to Roger Clarks' page describes investigation of the internal analog signals on the motion detectors.

1 Like

Ok I followed the link and watched the signal on my RCWL-0516 module and it's promising, along the lines of what might work, thanks.

I think I might have found a gold mine with heart rate detectors. 3Hz is at the high end of heart rate (180bpm) but the mid range of waving (one 'arm stroke' /cycle). And there is a lot of info about heart rate or ECG filters, which I'm looking into that could be applied to any signal. I think my waving freq range will be 3-6 'arm strokes' / sec [Hz]. I designed up a bandpass filter on analog devices (filter designer and came up with a 10th order Butterworth filter for the application but probably very expensive to implement, as analog (50+ components) or digital. And I don't really know how sharp the cuttoffs need to be. I'm looking into how to develop an optimum filter, any suggestions?
Design.pdf (925.3 KB)

You could try a digital bandpass filter. A 1 second filter detection lag to pick up a 4 Hz "wave" could be acceptable.

I've used this calculator with success, and this result looks sort of promising:

Thanks! I tried these input values but got something different, it has 407 taps instead of 101, see attached. I just added bands and types in the values from your plot. What am I missing?

No idea. It worked again for me, maybe it is browser-dependent. Try changing the sample rate and the frequency cutoffs.

On the other hand, take a look at the generated code for the 101 tap solution. I'm suspicious that the coefficients are all very large numbers. This looks like a numerically unstable result.

#include "SampleFilter.h"

static double filter_taps[SAMPLEFILTER_TAP_NUM] = {
  -284218.010344307,
  849161.5666288622,
  -1403261.4174914218,
  1938054.4506893347,
  -2443168.239965458,
  2905405.4834241206,
  -3307586.309628628,
  3626934.119512577,
  -3832659.1861475576,
  3882205.803355604,
  -3715391.9733348885,
  3245371.722107538,
  -2345022.9539214787,
  827022.7453444075,
  1584417.269983877,
  -5292356.030127799,
  10878632.450928943,
  -19169888.43907966,
  31319145.87586416,
  -48902696.40448723,
  74030227.74255069,
  -109463723.69610414,
  158737834.44477588,
  -226271306.79822987,
  317455988.6079875,
  -438707265.40018636,
  597458020.1281992,
  -802077806.8659958,
  1061700368.8322053,
  -1385946243.8585005,
  1784533178.7669098,
  -2266775329.1758575,
  2840982397.127562,
  -3513781273.240729,
  4289394430.85314,
  -5168920080.604898,
  6149667612.097359,
  -7224606844.940544,
  8381989974.489063,
  -9605200073.6966,
  10872869334.176481,
  -12159294216.026207,
  13435154258.822603,
  -14668518025.217934,
  15826095457.372221,
  -16874673141.987679,
  17782649855.97774,
  -18521576466.817287,
  19067598364.789642,
  -19402701109.31374,
  19515671001.76647,
  -19402701109.31374,
  19067598364.789642,
  -18521576466.817287,
  17782649855.97774,
  -16874673141.987679,
  15826095457.372221,
  -14668518025.217934,
  13435154258.822603,
  -12159294216.026207,
  10872869334.176481,
  -9605200073.6966,
  8381989974.489063,
  -7224606844.940544,
  6149667612.097359,
  -5168920080.604898,
  4289394430.85314,
  -3513781273.240729,
  2840982397.127562,
  -2266775329.1758575,
  1784533178.7669098,
  -1385946243.8585005,
  1061700368.8322053,
  -802077806.8659958,
  597458020.1281992,
  -438707265.40018636,
  317455988.6079875,
  -226271306.79822987,
  158737834.44477588,
  -109463723.69610414,
  74030227.74255069,
  -48902696.40448723,
  31319145.87586416,
  -19169888.43907966,
  10878632.450928943,
  -5292356.030127799,
  1584417.269983877,
  827022.7453444075,
  -2345022.9539214787,
  3245371.722107538,
  -3715391.9733348885,
  3882205.803355604,
  -3832659.1861475576,
  3626934.119512577,
  -3307586.309628628,
  2905405.4834241206,
  -2443168.239965458,
  1938054.4506893347,
  -1403261.4174914218,
  849161.5666288622,
  -284218.010344307
};

void SampleFilter_init(SampleFilter* f) {
  int i;
  for(i = 0; i < SAMPLEFILTER_TAP_NUM; ++i)
    f->history[i] = 0;
  f->last_index = 0;
}

void SampleFilter_put(SampleFilter* f, double input) {
  f->history[f->last_index++] = input;
  if(f->last_index == SAMPLEFILTER_TAP_NUM)
    f->last_index = 0;
}

double SampleFilter_get(SampleFilter* f) {
  double acc = 0;
  int index = f->last_index, i;
  for(i = 0; i < SAMPLEFILTER_TAP_NUM; ++i) {
    index = index != 0 ? index-1 : SAMPLEFILTER_TAP_NUM-1;
    acc += f->history[index] * filter_taps[i];
  };
  return acc;
}

I ran it again using 50 Hz sample rate, and got a MUCH more sensible result, with 32 taps and reasonable coefficients.

Code:

#include "SampleFilter.h"

static double filter_taps[SAMPLEFILTER_TAP_NUM] = {
  -0.03279707747559653,
  0.0564197803772894,
  -0.10578342205356728,
  0.20285493607739602,
  -0.2876916909488668,
  0.42136877711926246,
  -0.5390065156446532,
  0.7255507258873886,
  -0.7541870635732753,
  0.9006053536065983,
  -0.9790790995202291,
  0.7383339815444103,
  -0.9337661385743616,
  0.5523324183427147,
  -0.3013876828851143,
  0.34123417195489286,
  0.34123417195489286,
  -0.3013876828851143,
  0.5523324183427147,
  -0.9337661385743616,
  0.7383339815444103,
  -0.9790790995202291,
  0.9006053536065983,
  -0.7541870635732753,
  0.7255507258873886,
  -0.5390065156446532,
  0.42136877711926246,
  -0.2876916909488668,
  0.20285493607739602,
  -0.10578342205356728,
  0.0564197803772894,
  -0.03279707747559653
};

void SampleFilter_init(SampleFilter* f) {
  int i;
  for(i = 0; i < SAMPLEFILTER_TAP_NUM; ++i)
    f->history[i] = 0;
  f->last_index = 0;
}

void SampleFilter_put(SampleFilter* f, double input) {
  f->history[(f->last_index++) & 31] = input;
}

double SampleFilter_get(SampleFilter* f) {
  double acc = 0;
  int index = f->last_index, i;
  for(i = 0; i < SAMPLEFILTER_TAP_NUM; ++i) {
    acc += f->history[(index--) & 31] * filter_taps[i];
  };
  return acc;
}

#ifndef SAMPLEFILTER_H_
#define SAMPLEFILTER_H_

/*

FIR filter designed with
 http://t-filter.appspot.com

sampling frequency: 50 Hz

* 0 Hz - 2 Hz
  gain = 0
  desired attenuation = -40 dB
  actual attenuation = -39.99747410361548 dB

* 4 Hz - 8 Hz
  gain = 1
  desired ripple = 5 dB
  actual ripple = 4.177219343095732 dB

* 10 Hz - 20 Hz
  gain = 0
  desired attenuation = -40 dB
  actual attenuation = -39.99747410361548 dB

*/

#define SAMPLEFILTER_TAP_NUM 32

typedef struct {
  double history[SAMPLEFILTER_TAP_NUM];
  unsigned int last_index;
} SampleFilter;

void SampleFilter_init(SampleFilter* f);
void SampleFilter_put(SampleFilter* f, double input);
double SampleFilter_get(SampleFilter* f);

#endif

Thanks, I'm getting your numbers now. Do you have experience implementing digital filters? I was wondering if there's a way to adjust it while running the filter in real time.

This type of filter can't be adjusted in real time without a significant discontinuity, as you have to change all the coefficients and possibly, the sample history.

Try it out and see if the approach is even useful before worrying about such details.

What happens if my update rate is different from the one I set in the filter?

You won't get the expected result. It is no problem at all to ensure a sample rate of 50 Hz.