A Nerd and a Raspberry Pi Walk Into a Bar

Example tap menu

Good friends of mine run mikkeller.ch. They rotate craft beers on 20 taps, sometimes changing the lineup a few times a day. The folks there keep this gorgeous, hand-drawn tap menu on a blackboard.

We’ve argued plenty of ideas through the years. Few survived a round of beer. Even fewer survived a round of truco. One of them is in this blog post: how cool would it be to keep an up-to-date photo of the tap board on the website?

Hardware

V1

V1 front

V1 back

I took the first stab at it with hardware I had lying around. A Raspberry Pi Zero 2 W, a high quality camera module, the popular 2-lens kit and a case from this design.

The 6mm lens’ field of view was a good match for the distance between the Pi and the tap board. Its wider-angle has a pronounced fisheye effect, which I had to correct in software.

V1 image Example of a V1 pic from Jan 2025. Defisheyed, sharpened, equalized, on a good day. Not great

I never managed to get a nice, sharp pic out of it. The combination of the 12MP sensor, fisheye transformations, cropping and my spotty-at-best photography knowledge made it hard to read the small text on the final image.

V2

The front of the camera

The guts of the camera V2’s guts. I later swapped the Pi 4 for a Pi 5

I threw a little money at the problem and upgraded the camera to the 64MP ArduCam module. This is no match for the Pi Zero’s 512MB of RAM. Out with the Zero, in with a Pi 5.

It was a considerable improvement. A lot of problems immediately went away: better resolution, less distortion, more light, autofocus and HDR. I finally got some crisp photos out of it:

V2 image V2, April 12 2025, noon

V2 image V2, May 26 2025, 6pm

V2 image V2, July 27 2025, 7pm

I think the daytime pics look okay now. At night, they could still use some improvement. Tuning the metering mode and white balance helped, but I suspect the better, simpler fix is to improve lighting diffusion.

There is a raw LED strip under the board. See if you can guess on which side the power supply is connected. I think these bright spots are not playing well with the sensor’s dynamic range.

Technicalities

Maintenance

The Pi is connected to an isolated VLAN, with no ports exposed to the internet nor to other local clients. I SSH into it through Tailscale, and I cannot recommend it enough.

Reliability Learnings

The overlay filesystem mitigates wearing on the SD card in the long run and prevents corruption on sudden power losses. The root filesystem is mounted read-only, and writes are done to an ephemeral, in-memory filesystem.

A watchdog can be an inelegant, blanket band-aid for once-in-blue-moon hiccups. To be used responsibly. Or just, you know, save your sanity and make sure you have a good, reliable power supply. Whatever. I don’t wanna talk about it.

Software

The Pi runs a tiny Python script on a cron schedule. It calls picamera2 and POSTs the image. On the other side, a handler on a VPS processes the image, runs some checks and later serves it through a conservatively TTL’d Cloudflare caching proxy.

A Matter of Perspective

I mounted the Pi next to the ceiling on a side wall. The tap board is also high up, well above everyone’s head height.

There’s just one proverbial obstacle in the way: a hanging lamp. Out of the many hacks in this project, aligning its cable with the board is the one of which I’m most mildly proud. It’s chaotic good:

Annotator

Now, we want to transform the image so that the board appears flat on the screen, as if looking head-on at it. This is done with a 4-point transform.

I wrote an annotator to draw up the four points on a <canvas> element. The interesting part is that we can plug in OpenCV.js and see the expected results in real-time in the browser. This turned out pretty sweet. Here’s a video (or a screenshot):

There I also set up some light touch-ups to the image, like a very mild sharpening filter. You can see it in the video above. I experimented with color/contrast equalization with CLAHE, but I felt it could make the image look a bit unnatural. In the end I thought there’s a certain honest charm in the lighting variation.

The annotation is exported to the database and the same operations are done in the backend when a new image is uploaded.

Over{thinking, engineering}: Pick Two

Hypothetically, months in, your so-called friend “accidentally” bumps into the camera while installing a projector screen for a private event. They don’t realize, but Pi tilts slightly.

To hopefully catch some issues like this, I set up a few checks to run whenever a new pic is received.

Checks

In the above image there are two of them. If the average brightness is below a certain threshold, drop the image.

A line detector (nay, a probabilistic Hough line transform) checks for tilts and occlusions: we expect the best lines to be vertical or horizontal. If the board is skewed or occluded, the found segments are unlikely to be axis-aligned.

A third checker looks for the wooden frame around the board. It has a known shape, moments and color. I set up some heuristics and trained a HSV-space classifier to assert the presence of the unoccluded frame.

Checks

As a last safety step, I unconditionally run the final image through a few local, dockerized object detectors. If anything remotely sus pops up, the image is dropped. One of them is detr-resnet-50, which I was surprised to find runs well on a small virtual machine.

I’m very fine with false positives and very not fine with false negatives, so I cranked the confidence threshold almost all the way down. Here are some fun samples from my test set. By design, all of these would not make the cut:

Hand detected

Hand detected

If, for any reason, we want to take the camera offline, there’s a one-click “maintenance mode” button. It immediately purges the cache and serves the following image instead:

Maintenance Mode

Alright

The setup is up and running for seven months now. Around 5k snaps. While time allows and everyone is onboard with it, I’ll keep it going. After that, it will live as a beautiful memory in this blog post. I still want to to fix the night lighting, though.

This is a for-fun project I run on my spare time. If you find any issues, pretty please let me know. I don’t have a bug bounty budget, but perhaps I can offer a beer? I know a spot.