Have you ever stood very close to a billboard, close enough to see how it's printed? It's a whole bunch of little speckles of ink. From nearby, you can't resolve an image. When you get farther away, it becomes clear.
I always think that effect is really beautiful and fascinating. I'm interested in ways of presenting images that are unreadable from nearby but resolve into clarity from a distance.
I've got a few ways I've explored reproducing this process graphically. The one I'm working on now reduces any image to a series of lines, each of which has changing thickness as it runs. Using an intentionally pixelated crop of Gustave Courbet's fairly intense self portrait as the input:
and producing this output:
Even as it's reduced to fit on your screen, the image becomes easier to see; but open the image in a new window (via right-click or control-click) and you'll see how backing a few feet away from the computer or blurring your eyes paradoxically helps you see finer detail.
Who cares about lines of varying thicknesses? I'll tell you who. My little friend the Roving Artist:
Two wheels in the back, and a spinning chalk holder in the front, attached to the bottom of the black motor. (And at the moment, a mess of electronics.)
The design of this little robot is supposed to dispose it to doing an artistic job: it will roll across the ground and as it goes it will be drawing with a piece of chalk dragging along in that wooden holder under the front motor, in place of a front wheel.
Maybe you already guessed? The idea is that by turning that piece of chalk as it's driving back and forth making lines, the rover will be able to change the thickness of the lines it draws—in principle making an image like the one shown above. While the chalk is rotated parallel to the rover's path, the line will be thin, and while it's perpendicular the line will be thick.
If the chalk is white and the asphalt is black, it will draw white lines. If I give it a piece of black pastel crayon and it draws on white paper, it will draw black lines. Maybe I'll even work my way up to color images with multiple passes...
This is very much a work in progress! I'm actively developing the Arduino and Processing code as well as building the hardware.
Major tasks to tackle in the coming weeks:
Have made a lot of progress in the past few weeks, including:
I've de-emphasized a few things for the time being:
I've also spent a fair bit of time working on the communication between the robot and computer. I have hit a few snags, as well as some strange unexplained motor behavior. I'm hoping to troubleshoot these problems this first week of December, and at least in principle the rover is nearly ready to rove wild and free.
(Of course the fat lady's singing always sounds like it's just around the next corner. I've seriously underestimated project completion times enough to realize that all I know is that I don't know.)
Here's what the rover is looking like these days:
The battery is in back to help give the drive wheels traction. Though the breadboard is getting fairly full but until/unless I need to add more components it's fine.
Here's a sample of some parallel chalk lines the rover drew on cardboard:
This week I'll try to iron out the motor and communications misbehavior!
With a little help from my friends (specifically, Daniel of ZeGo Robotics), I have gotten some of the strange anomalous timing problems ironed out. Daniel helped me see that the way I was using a timer in my code to debug the strange delays was not working as I wanted. Here's a slice of the debugging code I was running:
timer = millis() - timer;Serial.print("3.5 timer=\t");Serial.println(timer); timer = millis() - timer;Serial.print("3.51 timer=\t");Serial.println(timer); timer = millis() - timer;Serial.print("3.52 timer=\t");Serial.println(timer); timer = millis() - timer;Serial.print("3.53 timer=\t");Serial.println(timer); timer = millis() - timer;Serial.print("3.54 timer=\t");Serial.println(timer); timer = millis() - timer;Serial.print("3.55 timer=\t");Serial.println(timer); timer = millis() - timer;Serial.print("3.56 timer=\t");Serial.println(timer);
And here is the sort of output I got and couldn't understand:
3.5 timer= 7475 3.51 timer= 1 3.52 timer= 7490 3.53 timer= 20 3.54 timer= 7507 3.55 timer= 38 3.56 timer= 7524
See the alternating lines, like two separate timers running parallel? I didn't understand what the problem was. I printed the code out on paper to try to diagnose the trouble and Daniel came over and saw it in about a minute.
Here's an approximation (with pretend numbers) of the math I was accidentally doing:
1010 - 1000 = 10 1020 - 10 = 1010 1030 - 1010 = 20 1040 - 20 = 1020 1050 - 1020 = 30
...et cetera, et cetera. The first value was always the current
millis(). So far, so good. The second value, the one I subtracted from
millis() in each line, was always equal to the previous result. In my code, this looked like
timer = millis() - timer; and I did that every line. But see how when you keep doing that same operation over and over you're actually running two separate-but-related timers? Not something I anticipated in the code, and yet there it is in the output. I was truly stumped until Daniel kindly un-stumped me.
Now, to correctly implement the timer for the motor-motion subroutine in my code, I add another variable, which I call
t for convenience. Here's how it works, in schematic form:
timer = millis(); t = millis() - timer; Serial.println(t); t = millis() - timer; Serial.println(t);
timer = millis();it takes for any particular line to execute.
I kept getting a strange one-second delay after I'd send the robot instructions before it did anything. I mean like nearly exactly a second. Why? What was the wait?
It was a culprit I didn't expect. I send the robot serial commands that look like this:
h 100 200 128 . This command means the following, to the robot: "
h means I'm about to hear how far to move the motors.
100 is the distance the left motor should advance.
200 is the distance the right motor should advance.
128 is the value I should move the chalk motor to." So far, so good.
Here is how the Arduino takes the serial input
h 100 200 128 and actually reads it: it uses a great tool called
Serial.parseInt(). This command (documentation) reads the serial stream looking for an integer. As soon as it sees one, it keeps reading the integer until it sees a non-integer (like a space, a letter, or a punctuation mark). Then it knows it's reached the end of the integer, and stops reading. So, if you run
Serial.parseInt() on the string
h 100 200 128, it will return
100 and then exit, because it looked, found an integer, and then found a not-integer character.
How to get my three different integers out of the command string
h 100 200 128? Simply run
Serial.parseInt() three times in a row. The first
Serial.parseInt() will read
100, the second will read
200, and the third will read
128. In my sketch, it looks like this:
leftread = Serial.parseInt();
rightread = Serial.parseInt();
chalkread = Serial.parseInt();
Ok, fine: I've read a command stream and parsed three different values out of it, loaded those into variables, and now I can use the new values to actually run motors. But I created the mysterious second-long delay when I wrote the stream parsing to work like this. "Where?" you ask. Remember how
Serial.parseInt() works: it reads a string of characters, so long as they're all digits, until it sees a non-digit. Then, it says, "ok, I've seen a non-number, I'm done reading this integer" and returns the integer it got.
The problem is: the last
String.parseInt() is still waiting to see its integer end. All it sees is
128 and then nothing else. It's sitting there, waiting for what turns out to be 1000 milliseconds, seeing if this integer that started as
128 has any more digits to it. The solution is really simple: just put some sort of terminator on the command string I send the Arduino. If, for instance, I send it
h 100 200 128. with that period at the end, or
h 100 200 128& or
h 100 200 128Q or really any sort of termination that's not a digit after the final value, it'll immediately compute the third
String.parseInt() value and stop waiting.
Now that that oddly intractable problem has proven...tractable, I'm on to other, juicier problems. First up: ensuring that the Rover goes where it needs to go. I'll probably need to either add tank treads, or compensatory position tracking, or both, to take care of this. Presently the thing strays from a linear path pretty easily, which, if uncorrected, will immediately render its output garbagey. I'm not expecting figuring this out to be easy. But it should be fun.
(Watch this space for updates as I progress on this project.)
I wrote the code to turn images into series of lines in Processing 1.5.1. Here is a .zip containing the Processing code as well as the Courbet 1000×1000px image referenced above. Play with parameters to change the image rendering, or feed it an image of your own!