Rapid Capture and Processing PiCamera Image
By: Saurav Solanki
Tags: Raspberry Pi, Edge Computing, Edge Ai, Picamera App

import io

import time

import threading

import picamera



class ImageProcessor(threading.Thread):

"""Processor Thread processing the images from Pi Camera (like inference in Deep Learning) """

def __init__(self, owner):

super(ImageProcessor, self).__init__()

self.stream = io.BytesIO()

self.event = threading.Event()

self.terminated = False

self.owner = owner

self.start()



def run(self):

# This method runs in a separate thread

while not self.terminated:

# Wait for an image to be written to the stream

if self.event.wait(1):

try:

self.stream.seek(0)

# Read the image and do some processing on it

#Image.open(self.stream)

#...

#...

# Set done to True if you want the script to terminate

# at some point

#self.owner.done=True

finally:

# Reset the stream and event

self.stream.seek(0)

self.stream.truncate()

self.event.clear()

# Return ourselves to the available pool

with self.owner.lock:

self.owner.pool.append(self)



class ProcessOutput(object):

"""Image Input Thread maintaining streaming of the images from Pi Camera and handling ImageProcessor"""

def __init__(self):

self.done = False

# Construct a pool of 4 image processors along with a lock

# to control access between threads

self.lock = threading.Lock()

# no of ImageProcessor is a parameter here, depends on used hardware.

# This help us to scale the capturing and benchmark the processing limit

self.pool = [ImageProcessor(self) for i in range(4)]

self.processor = None



def write(self, buf):

"""Writing Image into Processor"""

# magic word when capturing in mjpeg format.

# magic word: 'b\xff\xd8' helps us in getting new frames

if buf.startswith(b'\xff\xd8'):

# New frame; set the current processor going and grab

# a spare one

if self.processor:

self.processor.event.set()

with self.lock:

if self.pool:

self.processor = self.pool.pop()

else:

# No processor's available, we'll have to skip

# this frame; you may want to print a warning

# here to see whether you hit this case

self.processor = None

if self.processor:

self.processor.stream.write(buf)



def flush(self):

# When told to flush (this indicates end of recording), shut

# down in an orderly fashion. First, add the current processor

# back to the pool

if self.processor:

with self.lock:

self.pool.append(self.processor)

self.processor = None

# Now, empty the pool, joining each thread as we go

while True:

with self.lock:

try:

proc = self.pool.pop()

except IndexError:

pass # pool is empty

proc.terminated = True

proc.join()



# resolution can be 'VGA' or 'BGR or 'RGB'

with picamera.PiCamera(resolution='VGA') as camera:

# start the preview of camera streaming

camera.start_preview()



# give sensors some time to start

time.sleep(2)



# make a output image processor object

output = ProcessOutput()



# start recording the camera

# learn about format here: https://picamera.readthedocs.io/en/release-1.10/api_camera.html#picamera.camera.PiCamera.start_recording

camera.start_recording(output, format='mjpeg')

while not output.done:

camera.wait_recording(1)

camera.stop_recording()

This code also gives us hint to scale the processing limit(FPS) for many other embedded device like Sparkfun Board, Arduino, Jetson Nano and many more.