Here are some simple tools I hope you find useful in building your Mobot. My thanks to Henele Adams, Matt Mason, and Bob Wang.
This set of algorithms will allow you to detect lines of a known width and orientation. It works very well, and is insensitive to shadows or other artifacts of the environment. It was originally done as part of the SCS Robotic Buggy Project (Maayan Roth, Shafeeq Sinnamohideen, Arne Suppé, Chuck Thorpe, PhD - Advisor). Click here to see Mark Stehlik (SCS Undergraduate Dean) pushing in a Kilt!
It is derivative of the following work that was done for CMU’s NavLab:
Kluge, Karl, YARF: An Open-Ended Framework for Robot Road Following. Technical Report CMU-CS-93-104, School of Computer Science, Carnegie Mellon, February 1993.
The Sobel convolution mask gives us an approximation of the local image gradient. See here for more details. The mask is used by placing it atop each pixel of the image, multiplying the pixel’s neighbors by the corresponding coefficients, and summing the results to produce an output image.
for (I = 1; I < width-1; I++)
for (J = 1; J < height-1; J++)
out(I, J) = -1*in(I-1, J-1) + -2*in(I-1, J) + -1*in(I+1, J-1) + 1*in(I+1, J+1) + 2*in(I+1, J) + 1*in(I+1,J-1)
Here is an example from the robotic buggy. First, the original image:
Here is an image after edge detection in the region of interest. Note that we are looking for horizontal edges here but that is only a simple matter of changing coordinates (or rotating the Sobel mask).
A single column of the image has a magnitude plot with a positive and negative peak.
If we know the width of the line, we can create a mask to look for a positive and negative edge that are the expected distance apart, further filtering spurious edges. One simple way is to create another convolution mask:
[-1, -1, -1, -1, -1… 1, 1, 1, 1, 1]
Here, half the pixel width of the line is negative, the other half positive. From the plot above, we would expect a large positive peak when the operator moves over a matched pair of edges, properly spaced. Below are the corresponding column plot and image from the buggy.
From here, one can simply take the maximum value of each column and perform a line fit. You must still beware of outliers. You will find them primarily where there are no strong edge pairs, such as in the thick vertical line on the left part of the above image. Clearly, one solution is to not accept points whose score is below a certain threshold. This solution is a good heuristic, but there are more optimal estimators than least squares. I like least median of squares.
Least squares line fit with no outlier removal heuristic:
Least median of squares fit with no outlier removal heuristic:
An example of detection of a very faint line with many shadows:
Inside your R/C car, you are likely to find a standard hobby servo controlling the steering mechanism. The motor has three wires, for power, ground, and a control signal. The order of these wires will differ from one manufacturer to another. Servo Wiring Diagrams
It has been my experience that this is not as scary as the author would make it seem. Still, care is important when you are interfacing to your laptop. The following circuit gives some measure of protection to your laptop from the outside world through the use of an inverting transistor amplifier. Because the amplifier is inverting, it also means that if we output a one on the port, the servo sees a zero. (This is important in understanding the driver code) All the parts used here are available from Tech Electronics or the Squirrel Hill Radio Shack.
1 per channel
1 per channel
1 per channel
The beginning of this spec sheet will help you orient the transistors correctly.
So now that we can interface the servo, we need to know the form of the control signal. It is a square wave of the following characteristics:
The extremes are full rotation of the servo, left or right, with 1.5 milliseconds corresponding to the center position.
With this in mind, we can program Linux to make the parallel port generate these signals. (Interfacing to the IBM-PC Parallel Printer Port) In order to access the hardware on a register level, we need to run the program as the root user. On most PCs, the parallel port starts at address 0x278, 0x378, or 0x3BC. You can usually find the correct address in the BIOS at startup, or, if you have a dual boot machine, you can find this in Windows under the device manager. In Linux, you may find it if you type: less /proc/ioports
The code can be downloaded here. There is a #define to set the correct parallel port address. The program accepts a sequence of arguments that defines the pulse duration in microseconds for each channel, starting at zero. It will accept channel values from 500 to 2500 microseconds since most servos have some travel beyond the specified limits. However, it is not a good idea to leave a servo straining against the stops with the motor stalled!
Most laptops have automatic power management software. This will throttle the CPU when the laptop is not connected to a power source. Unfortunately, this is bad for our application, so you will need to disable this feature. The quick and simply way is to kill the apmd process. Type: ps –aux | grep apmd and kill that reported process.
The servo will still twitch a little bit. This is because we can’t monopolize the CPU forever. However, it’s the duration of the high portion of the pulse, which is 1 to 2 milliseconds long, that is important in determining the servo position. If we can prevent interruption here, we can solve the twitching problem.
There is a special define called PLAYIN_WITH_FIRE which can be invoked by editing the Makefile. This option allows us to disable interrupts during the two milliseconds that we are generating a signal. If the program should crash while the interrupts are disabled, the kernel will have no way of regaining control of the computer, and the computer will need to be rebooted. Fortunately, you should not need to modify the code in the procedure update_outputs, so this should not be a problem. Yes, this is considered extremely bad practice, but it allows us to make a very simple driver.
Yes, but you won’t be serving MP3’s while your mobot is running, so it’s not a problem. Then again, there is a pervasive wireless network and this is CMU…
Maybe, but I don’t consider a mobot a safety or time critical application. Avoiding needless complexity is my first rule of engineering. The key is to suitably define “needless.”
Yes, because this is running as a user level process, the kernel will occasionally interrupt the process. Because of this, the signal generated by the parallel port is sometimes wrong. However, since the receiver of this signal is analog, it is quite tolerant of the occasional glitch.
You can bend the cycle time of the servo signals. It can be slightly greater than 20 milliseconds and still work. It may not be necessary to process images at 30 Hz. The actuator is certainly not able to adjust that fast. It has been my experience that the outlined vision algorithm runs very efficiently on a PIII/600 (less than 5 percent CPU time, 640x480 resolution), so this shouldn’t be a problem.
The servos may be tolerant of the higher voltages. Have a look at the signals that are normally generated by the radio receiver with an oscilloscope. Otherwise, you can use a linear regulator such as the LM7805 to burn off the excess power.