Project: send a picture
Capture a camera frame, make it 1-bit, compress it with RLE and send it by radio.
Do this lesson in the simulatorA team of robots is exploring. One robot sees something interesting and wants to show the others, but the radio carries at most 200 characters in a message, and a picture is hundreds of pixels. In this project BugBot captures what it sees, turns it into a black and white picture, compresses it with run length encoding until it fits, and sends it. Every idea in F8 is in here: pixels, colour depth, file size, and compression.
The brief
The robot captures a 32 × 24 camera image and converts it to 1-bit black and white:
#for a pixel whose average of red, green and blue is below 128,.otherwise. It compresses each row with RLE, joins the rows with/, and checks the whole message fits in 200 characters. If it fits, it sends it withsend(); if not, it printstoo bigand sends nothing. It reports the uncompressed and compressed sizes, and proves the compression is lossless by decoding the message back into the picture.
Step 1: capture and convert
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
img = camera_image(32, 24)
picture = []
for row in img:
line = ""
for r, g, b in row:
line = line + ("#" if (r + g + b) / 3 < 128 else ".")
picture.append(line)
for line in picture:
print(line)
print("uncompressed:", 32 * 24, "bits")
The picture is now a list of 24 strings, one per row: a 1-bit image, stored as characters.
Step 2: compress it
Use rle_encode from lesson F8.9 on every row, and join them with / so the receiver knows where each row ends. The / is the metadata the receiver needs to rebuild the rows.
message = "/".join(rle_encode(line) for line in picture)
"/".join(...) puts a / between the encoded rows. Print the message and its len. A plain view of the mat compresses very well, because most rows are one long run.
Step 3: decode and check
The receiver splits the message at / and decodes each part. If the decoded picture is exactly the original, the compression was lossless:
rows = [rle_decode(part) for part in message.split("/")]
print("lossless:", rows == picture)
Step 4: send it
if len(message) <= 200:
send(message)
else:
print("too big")
Try turning the robot to face something busier before capturing, such as both balls and the wall. The message grows, and past 200 characters it will not fit: the more detail in a picture, the less RLE can squeeze it.
Task: send a picture
Build the program from the brief. Print the 24 picture lines, then uncompressed: 768 bits (calculated), compressed: <n> characters, and lossless: True. Send the message if it has 200 characters or fewer.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
img = camera_image(32, 24)
Challenges
- Compress the whole picture as one string instead of row by row. Which is shorter, and why?
- Send a 64 × 48 picture by splitting its message into several messages of 200 characters each, numbered so they can be put back in order.
- Invent a way to make the message shorter still, for example by writing a row that repeats the one above it as
=.