Skip to content

Instantly share code, notes, and snippets.

@skypanther
Last active July 8, 2020 14:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skypanther/04b44eaa455f358eb70e59336c068312 to your computer and use it in GitHub Desktop.
Save skypanther/04b44eaa455f358eb70e59336c068312 to your computer and use it in GitHub Desktop.
Accessing multiple PiCams with the Arducam adapter
import cv2
from multicam import Multicam
mcam = Multicam(gpio_mode='bcm')
cv2.imshow('Cam A', mcam.capture(cam='a'))
cv2.imshow('Cam B', mcam.capture(cam='b'))
cv2.imshow('Cam C', mcam.capture(cam='c'))
cv2.imshow('Cam D', mcam.capture(cam='d'))
# cv2.imshow('Cam E', mcam.capture(cam='e'))
# cv2.imshow('Cam F', mcam.capture(cam='f'))
cv2.waitKey(0)
mcam.cleanup()
# Author: Tim Poulsen, github.com/skypanther
# License: MIT
# 2018-08-22
import RPi.GPIO as gpio
import time
from picamera import PiCamera
from picamera.array import PiRGBArray
LOW = False
HIGH = True
class Multicam(object):
"""
Arducam Multi-camera adapter board class
Example:
```
multicam = Multicam(num_boards=2)
# optionally, set the resolution
multicam.set_resolution(width=640, height=480)
image = multicam.capture(cam='a')
```
Returned image is a 'bgr' OpenCV object.
Default resolution is 640 x 480.
Default capture camera is board 1, camera A.
"""
# Pin numbers - Arducam docs/samples use BOARD (physical) numbers
# You can switch to BCM numbers adding the gpio_mode arg on instantiation
channel_select_pin = 7
b1_oe_pin_1 = 11
b1_oe_pin_2 = 12
b2_oe_pin_1 = 15
b2_oe_pin_2 = 16
b3_oe_pin_1 = 21
b3_oe_pin_2 = 22
b4_oe_pin_1 = 23
b4_oe_pin_2 = 24
# camera settings:
iso = 400
def __init__(self, gpio_mode='board'):
"""
Initialize the multi-camera board by setting gpio mode and initial state
per http://www.arducam.com/multi-camera-adapter-module-raspberry-pi/
"""
self.camera = None
self.resolution = (640, 480)
self.gpio_mode = gpio_mode
gpio.setwarnings(False)
if gpio_mode == 'board':
gpio.setmode(gpio.BOARD)
else:
self.__set_bcm_mode()
gpio.setmode(gpio.BCM)
# set pin modes to output
gpio.setup(self.channel_select_pin, gpio.OUT)
gpio.setup(self.b1_oe_pin_1, gpio.OUT)
gpio.setup(self.b1_oe_pin_2, gpio.OUT)
gpio.setup(self.b2_oe_pin_1, gpio.OUT)
gpio.setup(self.b2_oe_pin_2, gpio.OUT)
gpio.setup(self.b3_oe_pin_1, gpio.OUT)
gpio.setup(self.b3_oe_pin_2, gpio.OUT)
gpio.setup(self.b4_oe_pin_1, gpio.OUT)
gpio.setup(self.b4_oe_pin_2, gpio.OUT)
# set initial values to select board 1, camera A
gpio.output(self.channel_select_pin, LOW)
gpio.output(self.b1_oe_pin_1, LOW)
gpio.output(self.b1_oe_pin_2, HIGH)
gpio.output(self.b2_oe_pin_1, HIGH)
gpio.output(self.b2_oe_pin_2, HIGH)
gpio.output(self.b3_oe_pin_1, HIGH)
gpio.output(self.b3_oe_pin_2, HIGH)
gpio.output(self.b4_oe_pin_1, HIGH)
gpio.output(self.b4_oe_pin_2, HIGH)
time.sleep(0.1)
def __set_bcm_mode(self):
"""
Swaps gpio pin numbers to be BCM mode
"""
self.channel_select_pin = 4
self.b1_oe_pin_1 = 17
self.b1_oe_pin_2 = 18
self.b2_oe_pin_1 = 22
self.b2_oe_pin_2 = 23
self.b3_oe_pin_1 = 9
self.b3_oe_pin_2 = 25
self.b4_oe_pin_1 = 11
self.b4_oe_pin_2 = 8
def __select_camera(self, cam='a'):
"""
Select a specific camera to use, defaults to A
Uses letters on the Arducam board, continuing up
the alphabet as you stack more boards
"""
a_channels = ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o']
b_channels = ['b', 'd', 'f', 'h', 'j', 'l', 'n', 'p']
if cam in a_channels:
gpio.output(self.channel_select_pin, LOW)
elif cam in b_channels:
gpio.output(self.channel_select_pin, HIGH)
else:
raise 'Invalid camera assignment'
gpio.output(self.b1_oe_pin_1, HIGH)
gpio.output(self.b1_oe_pin_2, HIGH)
gpio.output(self.b2_oe_pin_1, HIGH)
gpio.output(self.b2_oe_pin_2, HIGH)
gpio.output(self.b3_oe_pin_1, HIGH)
gpio.output(self.b3_oe_pin_2, HIGH)
gpio.output(self.b4_oe_pin_1, HIGH)
gpio.output(self.b4_oe_pin_2, HIGH)
if cam == 'a' or cam == 'b':
gpio.output(self.b1_oe_pin_1, LOW)
elif cam == 'c' or cam == 'd':
gpio.output(self.b1_oe_pin_2, LOW)
elif cam == 'e' or cam == 'f':
gpio.output(self.b2_oe_pin_1, LOW)
elif cam == 'g' or cam == 'h':
gpio.output(self.b2_oe_pin_2, LOW)
elif cam == 'i' or cam == 'j':
gpio.output(self.b3_oe_pin_1, LOW)
elif cam == 'k' or cam == 'l':
gpio.output(self.b3_oe_pin_2, LOW)
elif cam == 'm' or cam == 'n':
gpio.output(self.b4_oe_pin_1, LOW)
elif cam == 'o' or cam == 'o':
gpio.output(self.b4_oe_pin_2, LOW)
else:
# default to selecting camera A
gpio.output(self.b1_oe_pin_1, LOW)
time.sleep(0.1)
def set_resolution(self, width=640, height=480):
"""
Sets the capture resolution. Must be one of the supported sizes, see
https://picamera.readthedocs.io/en/latest/fov.html#sensor-modes
:param width: Width in whole pixels
:param height: Height in whole pixels
"""
w = int(width)
h = int(height)
self.resolution = (w, h)
def capture(self, cam='a', image_format='bgr'):
"""
Capture an image
:param cam: Camera from which to capture; uses letters on the Arducam board,
continuing up the alphabet as you stack more boards
:return: image
"""
self.__select_camera(cam=cam.lower())
if self.camera is None:
self.__initialize_camera()
tmp_image = PiRGBArray(self.camera, size=self.resolution)
self.camera.capture(tmp_image, image_format)
return tmp_image.array
def cleanup(self):
if self.camera:
self.camera.close()
self.camera = None
# gpio.cleanup() # throws errors, so commented out
def __initialize_camera(self):
camera = PiCamera()
camera.resolution = self.resolution
camera.iso = self.iso
# let the camera exposure settle
time.sleep(1)
# now, fix the values
camera.shutter_speed = camera.exposure_speed
g = camera.awb_gains
camera.awb_mode = 'off'
camera.awb_gains = g
self.camera = camera
@mrosener
Copy link

I started coding my acquisition package for the multicam module last weekend but I am quite puzzled with the camera initialization stage. According to your code, camera initialization would occur only once, on the first capture call. I was expecting something like a single camera feed for the whole system, but individual camera config (shutter speed, iso, etc...). How come you are only sending config information to the first camera that was called? Is the multicam board dispatching config parameters to all cameras at once?
I am currently working with one board, but expect to stack two of them soon. I'll let you know if I encounter the same issue you have.

@mrosener
Copy link

mrosener commented Dec 4, 2018

second board stacked! I am able to boot a 5 camera RPi system with 5V 3A power input. As soon as I add a sixth one , the boot sequence cannot complete. Any chance that the pbm you mentionned on your website (https://www.timpoulsen.com/2018/multiple-cameras-with-a-single-raspberry-pi.html) could be related to power input?

@mrosener
Copy link

mrosener commented Dec 6, 2018

I think I was able to reproduce the pbm you mentioned on your website. With one multiplex board and 4 cameras, everything works fine. As soon as I add the second mutiplex board, accessing any camera of the first board will cause the RPi to crash.
I was only able to access the fifth camera I had on the second board.

@mrosener
Copy link

mrosener commented Dec 7, 2018

Me again...
I was able to get images from all 5 cameras, using stacked multiplex boards. The trick is to specify that you want to use the video port when you call camera.capture:

self.camera.capture(tmp_image, image_format, use_video_port = True) # line 161 in your code

If you don't, the capture call will default on the capture port which seems to be part of the pbm. This solution might come with a drop in image quality (is it necessary to set a lower frame rate then?). I haven't tested the max resolution yet, but will let you know if there is a pbm.

@mimijunior40
Copy link

Could you please help me schedule the recording of the captured images?

@skypanther
Copy link
Author

Sorry, I just realized that GitHub doesn't send me any sort of notice when someone comments on my gists.

Thanks for the tip on the use_video_port setting. I'm no longer using this board. But I'll try to remember this if I do.

As for scheduling the recording, I'd suggest either setting up a cron job to call your script as needed. Check out the PiCamera docs for an example of taking time-lapse photos, which is maybe what you mean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment