element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Project14
  • Challenges & Projects
  • More
Project14
Show and Tell! Station 202: a camera that throws the image away
  • News
  • Member Updates
  • Competitions
  • Forum
  • Documents
  • Theme Suggestions
  • Polls
  • Members
  • More
  • Cancel
  • New
Join Project14 to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: apn201
  • Date Created: 29 Jun 2026 8:32 PM Date Created
  • Views 108 views
  • Likes 3 likes
  • Comments 3 comments
  • generativeart
  • picamera2
  • flask
  • privacy
  • opencv
  • asciiart
  • sse
  • raspberry pi
  • raspberrypi
  • canvas
  • picamera
  • art
Related
Recommended

Station 202: a camera that throws the image away

apn201
apn201
29 Jun 2026
Green pixelated camera output from a Raspberry Pi

I bought a Raspberry Pi 3 a few years ago to enter an art competition. I never opened the box.

The competition passed. The box sat on a shelf. Later I needed something small to run my home automation, so I finally opened it and put Home Assistant on it. It did that quietly for a long time. When I moved Home Assistant to a PC, the Pi was free again, and I decided to finally use it for what I bought it for. This is that project.

The plan

Station 202 is a small camera pointed at my desk that renders what it sees as a coarse field of green pixels in a web browser. It looks like a surveillance monitor from an old test rig that everyone forgot about.

The part I care about most is technical, not visual: no real image ever leaves the Pi. The camera frame is reduced on the device to a tiny grid of brightness levels, and only that grid is sent out. You cannot rebuild a face, a screen, or a document from it. The thing that makes it look like degraded art is the same thing that makes it safe to put on the public internet.

I work in IT security by day, where the whole job is deciding who is allowed to see what. The other half of my time goes to film and code. This sits exactly on the line between the two: a camera that is always on, watched by no one in particular, that is built so it physically cannot leak a usable picture.

.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

How it works

 Raspberry Pi 3 + camera web server browser
 ---------------------------       -----------------         -------------------
 capture -> grayscale ->           relay (Flask):            HTML5 canvas:
 downscale to 100x56 ->            keeps newest grid,        grid -> green pixels
 quantise to levels 0-9 ->         serves it over SSE        (or ASCII characters)
 push the grid only, outbound      one-way out               recorded "tapes" when
                                                             nobody is at the desk

The Pi never opens an inbound port. It only pushes outbound. The web server can be fully compromised and the worst anyone gets is a screen of green blocks.

How I built it, step by step

1. The Pi and the broken camera

The camera module is damaged. For normal photography it is useless - soft, low contrast, partly out of focus. For this it is perfect, and the defects are part of the look. Lesson one of the project: a broken sensor is still a working sensor if your output is 5,600 pixels of green.

2. Capture and reduce on the device

Capture with picamera2, convert to grayscale and downscale with OpenCV, then quantise every pixel to one of ten brightness levels. That last step is the whole privacy model. The output is a string of digits, nothing else.

Raspi
python
import cv2, numpy as np
from picamera2 import Picamera2

COLS, ROWS = 100, 56

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration(
    main={"size": (320, 240), "format": "RGB888"}))
picam2.start()

frame = picam2.capture_array()
gray  = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
small = cv2.resize(gray, (COLS, ROWS), interpolation=cv2.INTER_AREA)

# the only thing that ever leaves the Pi: 5,600 digits, 0-9
levels = (small.astype(np.uint16) * 9 // 255).clip(0, 9).astype(np.uint8)
grid   = "".join(map(str, levels.flatten().tolist()))

3. The browser renderer

The page reads the grid and draws one filled rectangle per cell, brightness scaled to green. The same data can be drawn as ASCII characters instead - that was the original look, but at this resolution the characters turn to mush, so blocky pixels are the default and ASCII is an option in a "tune" menu.

javascript
// grid is a string of digits; draw it as green blocks
for (let y = 0; y < rows; y++) {
  for (let x = 0; x < cols; x++) {
    const lv = grid.charCodeAt(y * cols + x) - 48;   // '0'..'9'
    if (lv === 0) continue;
    const s = lv / 9;
    ctx.fillStyle = `rgb(0, ${(255 * s) | 0}, ${(70 * s) | 0})`;
    ctx.fillRect(x * cw, y * ch, cw + 1, ch + 1);
  }
}

4. Recorded "tapes" for when I am away

Instead of video files, the recorded material is stored in the same grid format - a small JSON file of frames that the browser replays through the exact same renderer. So a recording and the live feed look identical, because they are the same kind of data. The files are almost all repeated digits, so they compress to very little.

5. The relay and the presence gate

A tiny Flask service on the web server keeps only the newest grid and streams it to browsers over Server-Sent Events. SSE because it reconnects itself and survives a tab left open for days.

python
@app.get("/feed")
def feed():
    def stream():
        last = -1
        while True:
            if state["version"] != last and fresh():
                last = state["version"]
                yield f"data: {state['frame']}\n\n"
            else:
                yield ": keepalive\n\n"
            time.sleep(0.25)
    return Response(stream(), mimetype="text/event-stream")

The Pi only pushes when it sees motion during set hours, so the live feed means I am actually at the desk. Otherwise the relay goes stale and the browser falls back to a recording on its own.

image

Problems I ran into

Per-character drawing was slow. The first ASCII version called the canvas text function thousands of times per frame. I capped the frame rate to about 12 fps (a degraded monitor should be slow anyway) and later moved the default to filled rectangles, which are much cheaper than glyphs.

Opening the page from a file did nothing. Browsers treat file:// as a unique origin and block the page from fetching the word list and recordings. Serving the folder over a normal web server (python3 -m http.server while testing) fixed it.

The canvas went blank when media came from another domain. Reading pixels back from a canvas that has loaded a cross-origin video taints it and throws. Hosting everything on one origin avoids it entirely.

SSE stuttered behind nginx. The reverse proxy was buffering the stream. Turning off proxy_buffering for that one location made it real-time again.

Proving the privacy claim. I had to be sure nothing recoverable leaves the device. The answer is in the pipeline: the only data on the wire is 5,600 digits between 0 and 9. There is no frame buffer to leak because it never exists outside the Pi.

Current status

Working: live capture and reduction on the Pi, the browser renderer in both pixel and ASCII modes, recorded tape playback, the relay over SSE, motion-gated live switching, and a "tune" panel for display settings. It runs continuously and is reachable on the public internet through a reverse proxy, with the Pi joined over Tailscale.

Not finished: the live presence detection still needs tuning so brief stillness does not drop the feed; autoplay of recorded material is fussy on some mobile browsers; and the damaged camera occasionally needs a restart, which I want to make automatic with a watchdog.

It started as a Pi I bought for a competition and never opened. It is now entering one.

  • Sign in to reply

Top Comments

  • e14phil
    e14phil 13 days ago +1
    That is super cool! I love the colour choice, very cyberpunk
  • DAB
    DAB 13 days ago +1
    Interesting project. If nothing else, you end up with some very artistic images.
  • apn201
    apn201 13 days ago in reply to DAB

    It is an art project after all.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB 13 days ago

    Interesting project.

    If nothing else, you end up with some very artistic images.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • e14phil
    e14phil 13 days ago

    That is super cool!  I love the colour choice, very cyberpunk

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube