----- Date:

Timelapse camera rig for Rhe, pt 1

Rhe asked if I could come up with a way to make time lapse videos of her building a model on the desk. We could do that with a real camera on a tripod taking timed exposures, but the lack of immediate feedback kinda sucks. I've got a Raspberry Pi free and a camera module for it that I haven't played with yet, so I'm building that out.

I have an 8gb sd card, looking at it on the win7 machine, there's old pi image on it I don't care about, so win32 disk imager and the 2018.11.13 raspibian-stretch-full.img that's to hand go onto it. Fresh stock install of a year old distro.

I had to look up how to setup SSH:

For headless setup, SSH can be enabled by placing a file named ssh,
without any extension, onto the boot partition of the SD card from
another computer. When the Pi boots, it looks for the ssh file. If it
is found, SSH is enabled and the file is deleted. The content of the
file does not matter; it could contain text, or nothing at all.

Then once its booted and logged in, configure ssh to stay on via raspi-config or

sudo systemctl enable ssh
sudo systemctl start ssh

Got that all set up and now: the goal was doing time lapse and the awesome RasPi foundation folks have provided a timelapse howto even

raspistill -o test.jpg It works!

raspi camera test

Reading docs, it says it will be a H264 (or MJPEG!) video camera to lan if you ask it to be. with all the lovely hardware options exposed by their nice little CLI apps. awesome. 15 years ago I spent considerable time and effort to achieve something similar, so let's see just how hard it is today.

Looking at a picamera docs example, not hard at all.

picamera lowlight note

([6 second exposure] the maximum the Pi V1 camera module is capable of; the V2 camera module can manage 10 second exposures)

interesting fact. There's all sorts of interesting things i can see to do already, without even looking intot he tools I know exist with OpenCV etc. Later.

picamera API reference for docs on attributes like hflip etc.

In a couple hours, I've got a LAN ready webstreaming camera on a 2ft goose-neck mount fully ready for further customization depending on just what exactly she wants to do. I'm thinking a live feedback monitor she can see and high quality frames every second or so we can further decimate as needed for making the real videos. We shall see.

My python web streaming script, thus far:

import io
import picamera
import logging
import socketserver
from threading import Condition
from http import server

HEIGHT = 1024
WIDTH = 768
FPS = 10
PORT= 8000


PAGE="""\
<html>
<head>
<title>picamera MJPEG streaming demo</title>
</head>
<body>
<h1>PiCamera MJPEG Streaming Demo</h1>
<img src="stream.mjpg" width="%s" height="%s" />
</body>
</html>
"""

PAGE = PAGE % (HEIGHT,WIDTH)


class StreamingOutput(object):
    def __init__(self):
        self.frame = None
        self.buffer = io.BytesIO()
        self.condition = Condition()

    def write(self, buf):
        if buf.startswith(b'\xff\xd8'):
            # New frame, copy the existing buffer's content and notify all
            # clients it's available
            self.buffer.truncate()
            with self.condition:
                self.frame = self.buffer.getvalue()
                self.condition.notify_all()
            self.buffer.seek(0)
        return self.buffer.write(buf)

class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            self.send_response(301)
            self.send_header('Location', '/index.html')
            self.end_headers()
        elif self.path == '/index.html':
            content = PAGE.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', len(content))
            self.end_headers()
            self.wfile.write(content)
        elif self.path == '/stream.mjpg':
            self.send_response(200)
            self.send_header('Age', 0)
            self.send_header('Cache-Control', 'no-cache, private')
            self.send_header('Pragma', 'no-cache')
            self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
            self.end_headers()
            try:
                while True:
                    with output.condition:
                        output.condition.wait()
                        frame = output.frame
                    self.wfile.write(b'--FRAME\r\n')
                    self.send_header('Content-Type', 'image/jpeg')
                    self.send_header('Content-Length', len(frame))
                    self.end_headers()
                    self.wfile.write(frame)
                    self.wfile.write(b'\r\n')
            except Exception as e:
                logging.warning(
                    'Removed streaming client %s: %s',
                    self.client_address, str(e))
        else:
            self.send_error(404)
            self.end_headers()

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True

with picamera.PiCamera(resolution=(HEIGHT,WIDTH), framerate=FPS) as camera:
    output = StreamingOutput()

    camera.hflip=True
    camera.vflip=True
    camera.exposure_mode='backlight'
    camera.meter_mode='backlit'

    camera.start_recording(output, format='mjpeg')
    try:
        address = ('', PORT)
        server = StreamingServer(address, StreamingHandler)
        server.serve_forever()
    finally:
        camera.stop_recording()