Friday, May 8, 2015

Python + RPi Camera Module: Capturing to a network stream, display on Tkinter GUI

This example modify from Picamera tutorial: Basic Recipes - 4.9. Capturing to a network streamHere we have two scripts: a server (presumably on a fast machine) which listens for a connection from the Raspberry Pi, and a client which runs on the Raspberry Pi and sends a continual stream of images to the server.

Instead of do nothing on server side, I create Tkinter GUI to display the received image.

This video show how both client and server run on Raspberry Pi 2, remotely with xrdp/remmina.

In server side, in order to run both Tkinter GUI and server connection in parallel, I have to implement both in Thread. For this part, reference to O'Reilly Python Cookbook (2002): Combining Tkinter and Asynchronous I/O with Threads.

import time
import threading
import random
import Queue
from PIL import Image, ImageTk
import Tkinter as tk
import socket
import struct
import io
import tkMessageBox

class GuiPart:
    def __init__(self, master, queue, endCommand):
        self.queue = queue
        self.endCommand = endCommand
        self.root.title('My Pictures')

        imageFile = "pi.jpg"
        self.image1 = ImageTk.PhotoImage(
        w = 640
        h = 480
        x = 0
        y = 0
        self.root.geometry("%dx%d+%d+%d" % (w, h, x, y))

        self.panel1 = tk.Label(self.root, image=self.image1)

        self.panel1.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)

        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def on_closing(self):
        if tkMessageBox.askokcancel("Quit", "Do you want to quit?"):

    def processIncoming(self):
        """Handle all messages currently in the queue, if any."""
        while self.queue.qsize(  ):
            except Queue.Empty:
                # just on general principles, although we don't
                # expect this branch to be taken in this case
    def update_image(self, newImage):

class ThreadedClient:
    def __init__(self, master):
        self.master = master
        self.queue = Queue.Queue(  )
        self.gui = GuiPart(master, self.queue, self.endApplication)

        # Set up the thread to do asynchronous I/O
        # More threads can also be created and used, if necessary
        self.running = 1
        self.thread1 = threading.Thread(target=self.workerThread1)
        self.thread1.start(  )

        # Start the periodic call in the GUI to check if the queue contains
        # anything
        self.periodicCall(  )

    def periodicCall(self):
        Check every 200 ms if there is something new in the queue.
        self.gui.processIncoming(  )
        if not self.running:
            # This is the brutal stop of the system. You may want to do
            # some cleanup before actually shutting it down.
            import sys
        self.master.after(200, self.periodicCall)

    def workerThread1(self):
        #set up server
        # Start a socket listening for connections on ( means
        # all interfaces)
        self.server_socket = socket.socket()
        self.server_socket.bind(('', 8000))

        # Accept a single connection and make a file-like object out of it
        self.connection = self.server_socket.accept()[0].makefile('rb')

            while self.running:
                # Read the length of the image as a 32-bit unsigned int. If the
                # length is zero, quit the loop
                image_len = struct.unpack('<L','<L')))[0]
                if not image_len:
                # Construct a stream to hold the image data and read the image
                # data from the connection
                image_stream = io.BytesIO()



    def endApplication(self):
        self.running = 0

root = tk.Tk(  )

client = ThreadedClient(root)
root.mainloop(  )

In client side, basically same as in Picamera tutorial: Basic Recipes - 4.9. Capturing to a network stream.

import io
import socket
import struct
import time
import picamera

# Connect a client socket to my_server:8000 (change my_server to the
# hostname of your server)
client_socket = socket.socket()
client_socket.connect(('', 8000))

# Make a file-like object out of the connection
connection = client_socket.makefile('wb')
    with picamera.PiCamera() as camera:
        camera.resolution = (640, 480)
        # Start a preview and let the camera warm up for 2 seconds

        # Note the start time and construct a stream to hold image data
        # temporarily (we could write it directly to connection but in this
        # case we want to find out the size of each capture first to keep
        # our protocol simple)
        start = time.time()
        stream = io.BytesIO()
        for foo in camera.capture_continuous(stream, 'jpeg'):
            # Write the length of the capture to the stream and flush to
            # ensure it actually gets sent
            connection.write(struct.pack('<L', stream.tell()))
            # Rewind the stream and send the image data over the wire
            # If we've been capturing for more than 30 seconds, quit
            if time.time() - start > 30:
            # Reset the stream for the next capture
    # Write a length of zero to the stream to signal we're done
    connection.write(struct.pack('<L', 0))