Sunday, December 22, 2013

New Project - Sonar experiments

Digital Sonar Experiment Part 1

For the last 10 years or so, I have wanted to build an underwater robot of some sort. I assume this is the result of reading too much Cussler, and doing a bit of SCUBA diving, but I can't seem to shake it. I've been following a number of projects, partitcularly OpenROV, with great interest. One of the challenges that does not seem to have been tackled much by hobbyists is an affordable underwater sonar. There are plenty of inexpensive sonar units for land and aerial robotics - the AR Drone makes good use of them for low altitude sensing - but there don't seem to be any for water use yet.

I figured it made the most sense to understand the algorithms and get them debugged in air first, since sound travels much faster in water, and there are issues with acoustic coupling and waterproofing in water that are not a problem in air.

I knew nothing of signal processing when I started this, I was surprised that it's not really difficult from an algorithm standpoint. It does involve a fair amount of computation. Essentially, an audio signal, once normalized, consists of a bunch of amplitude values between -1 and 1 at some sampling rate. For human audible frequencies, that's commonly 44100 samples per second. The reason for this is that for perfect reproduction of a signal, you must sample it at twice the frequency you are looking to reproduce. This is the Nyquist Rate. Therefore, 44100 hz should be able to perfectly reproduce the signal up to 22050 hz, which I figured was the limit of my laptop's audio hardware. A second's worth of audio is 44100 float values between -1 and 1.

The simplest way of performing a correlation function is to load one array with the reference signal you are looking for - the ping - and another array with the recorded results. Start with the recorded array at position 0, and multiply each value in the chirp with the next $chirplength samples from the recorded audio. Sum the products of each mutiplication (essentially calculating the area under the resulting curve) and record it into a results array at position 0. Shift to position 1 in the recorded audio array, and repeat, this time storing the results in position 1 of the results array. Repeat for all positions in the recorded audio array, up until you hit the spot where the chirp length would pass the end of the recorded audio array, and stop.

What you are doing is creating a sliding window, comparing the chirp to each chunk of audio in the recorded audio array. The output of the correlation is really interesting - when the audio is a very close match, you get a large positive spike in the results that correspond to a position in the recorded audio. That spike represents a copy of your chirp in the recorded audio. The first time you see it, it's the chirp your speaker sent. After that, it's echoes of that chirp off objects.

Knowing the position of each echo, you can compute the time that it took from when the primary pulse was sent with your sample rate. That's the time the sound was in flight. Divide by two for the round trip, compare against the speed of sound, and boom, you've got distance to your target. Neat, huh?

The correlation function works best on a chirp signal - audio that changes in frequency over time. The chirp must be very short, or the echo will arrive before the chirp finishes playing. I found that chirps in the 1.5 ms range gave best results for me - it results in a minimum range of 3-4 feet. I generated the chirp in Audacity, sweeping from 4000-10000 hz at full amplitude.

Here's a recorded slice of audio, showing the chirp, some echoes and background noise.

Here's the output of the correlation function:

Once you can do it for a single chirp, you can assign an intensity to the spikes, and graph them over many pulses. This gives you a fish finder type view - in this example, time goes from top to bottom, and distance from left to right. Note the noise on the far left side of the image - that's probably caused by the speaker continuing to "ring" for a few milliseconds after the signal stops.

I chose WinPython with the PyAudio library to test with, because it comes bundled with graphing libraries and other useful things. I used a USB combined microphone/speaker intended for chat, because I could move it around and it was pretty directional.

It works - here's a sample run showing the returns with the speaker/mic starting near the wall, and then moving slowly away, then back in, etc. You can see a white line that look like peaks and valleys corresponding to the distance, along with background noise and multipath echoes. The minimum distance was about 3 feet - the maximum was about 8.

The left side of the screen represents the location of the sensor - imagine it's attached to the hull of a boat. White and grey dots represent echoes at different distances. The white line that is produced as I bring the speaker/mic away from the wall gets farther to the right, indicating more time in flight, and longer distances. As I bring it closer to the wall, it gets closer to the left side of the screen. One way to visualize this - if the sensor was mounted on a boat, the line would show the depth of water over the bottom. You also get weaker returns for sounds that have bounced around on indirect paths, or schools of fish.

10 comments:

what if you used four microphones instead of one, and you read the mic data at very short intervals and in each interval you used the slight delays between the mics picking the signal up to compute a 3d location. the intervals would be even shorther than the click, so you could gather many 3d points per click.

Hi. Nice piece of work. One thing: You must sample at greater-than the Nyquist rate. If you look at a plot of a sin(t), you will see that sampling at exactly the Nyquist rate will give you a constant value. Output will be zero if the sampling and signal have no phase difference. This is a common error, probably because it is stated incorrectly in so many text books. Wikepedia has it right http://en.wikipedia.org/wiki/Nyquist_rate You need to band-limit to the Nyquist rate and sample at greater-than. A lot greater-than unless you want a slowly varying result. 8 times faster is a good place to start.

Take a look at synthetic aperture methods. You can get resolution that is independent of range - with a lot more computing. If you find an affordable way to chirp in water with reasonable power, I would love to hear about it. I have been trying various things for years.

Mr. Springer: Thanks very much for the clarification, and the pointer to the synthetic aperture methods, I'll do some reading on both to understand them better. I would be interested in learning what has and has not worked for your in your experiments - if you have some time and wouldn't mind discussing it, feel free to drop me an email. I'm thinking that the availability of cheap computing hardware (like the Launchpad and it's kin) should open up new possibilities for hobbyists that would have been expensive or bulky before now.

Nice project! I ran into a few problems when I tried to run your code at first but they were all solved. First I had to generate the chirp file, but you had nice instructions of how to do that. I could only choose whole milliseconds as duration though. I also had a few runtime problems, it turned out I had an old version of PyAudio.

To other people out there: make sure you have at least PyAudio v0.2.7 installed, the debian repos only have v0.2.4, but v0.2.7 can be downloaded as a .deb file at http://people.csail.mit.edu/hubert/pyaudio/

Hey, thanks for the feedback! Could you provide a little information about your setup? I'm curious what you were using for a mic and speaker, as well as which OS you were using if you don't mind sharing.

By the way, you can get fractions of a millisecond on your chirp in Audacity by selecting "samples" as the unit when generating the chirp rather than time.

About Me

One guy's wanderings through science and technology, just for the fun of it. Currently focused on astronomy and hobby robotics, but likely to wander into photography, DIY drones, CNC and 3D printing, or whatever seems interesting at the time.