-
-
Save skypanther/04b44eaa455f358eb70e59336c068312 to your computer and use it in GitHub Desktop.
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 |
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.
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.
Could you please help me schedule the recording of the captured images?
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.
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?