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 simulatorPrint it: worksheet (PDF) · answers (PDF) · as a web page
Guide: Convolutions and CNNs explained
A 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")
From where the robot starts, the message is 111 characters: 21 of the 24 rows are the single run 32.. Try moving before capturing: after forward(30, distance=15) the balls and the wall are bigger and more broken up, and the message grows to 121 characters. Every change between # and . in a row adds to the message, so a picture with enough detail would pass 200 characters and 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
=.