Let's start by looking at the camera modules themselves. The v1 camera module is capable of 2x2 and 4x4 binning (see the camera modes table); I've heard there's an 8x8 binning mode as well but the firmware devs weren't able to get it working. This is why the v1 module can achieve full field of view (FoV) in most modes.
The v2 camera module by comparison, unfortunately, is only capable of 2x2 binning which explains why many of its modes have a partial FoV (I think the v2 module can also do line skipping, but I don't think that's used by the Pi's camera firmware). However, this isn't the whole story. This is only the beginning of the image processing pipeline. In other words, this is just what the sensor itself passes to the ISP block in the GPU which handles the rest of the processing, including any resizing. Whilst the camera has several discrete modes (listed in that table), it can effectively work in any resolution up to the listed maximum. If you read a bit further on from those tables in the documentation, you'll find a description of the heuristic used to select the sensor mode according to the requested resolution and frame-rate.
Provided you either wind up in, or force the use of, a full-frame sensor mode you'll be capturing from all pixels on the sensor anyway. As for concerns about CPU usage: don't worry. The CPU isn't used for any part of the camera's imaging pipeline, it's almost entirely GPU based (except for that first binning step which is done by the sensor ISP). The only time the CPU gets involved is when it receives the final output and has to do something with it.
If you're looking for minimal CPU usage, this is a major plus of the camera module compared to USB webcams which, because the USB bus is polled by the CPU, use significant CPU time (USB3 uses interrupts instead of polling but we're talking about Pis here which only have USB2, and besides: most USB webcams don't use USB3 at the time of writing).
Onto your specific requirements. You want:
- to produce a 120x90 image
- with full field of view
- in black and white (I'll take this to mean luma only is fine)
- to a Python numpy array
- as fast as possible
Easy enough. We'll use sensor mode 4 which provides full field of view and uses the sensor to perform an initial 2x2 binning. We'll set the output resolution of the camera to 120x90 (this simply means the GPU's resizing block will take the full-frame 2x2 binned sensor data and downsize it down to 120x90). Finally, we'll capture straight into a numpy array but we'll only make it large enough for the Y (luminance) plane of the data; it'll throw an error because the array's not large enough for all the data, but that's okay - we can ignore that and it'll still write the Y data out:
import time
import picamera
import numpy as np
with picamera.PiCamera(
sensor_mode=4,
resolution='120x90',
framerate=40) as camera:
time.sleep(2) # let the camera warm up and set gain/white balance
y_data = np.empty((96, 128), dtype=np.uint8)
try:
camera.capture(y_data, 'yuv')
except IOError:
pass
y_data = y_data[:120, :90]
# y_data now contains the Y-plane only
print(y_data.max())
This is more or less copied straight from the Unencoded image capture (YUV) recipe which also explains why we're actually capturing 128x96 here (the camera works in 32x16 blocks).
What about rapid continual capture? I'm assuming you're interested in this simply because you want this as fast as possible (which generally means you want as many as possible as well). In this case it's best to use a custom output with a YUV recording (which will receive one write() call per frame), then use numpy's very handy frombuffer
method to layer a numpy array on top of the front of the captured data (note: this is extremely fast because we're not allocating the numpy array or copying the data, we're just saying "make a numpy array on this existing block of memory"):
import time
import picamera
import numpy as np
class MyOutput(object):
def write(self, buf):
# write will be called once for each frame of output. buf is a bytes
# object containing the frame data in YUV420 format; we can construct a
# numpy array on top of the Y plane of this data quite easily:
y_data = np.frombuffer(
buf, dtype=np.uint8, count=128*96).reshape((96, 128))
# do whatever you want with the frame data here... I'm just going to
# print the maximum pixel brightness:
print(y_data[:90, :120].max())
def flush(self):
# this will be called at the end of the recording; do whatever you want
# here
pass
with picamera.PiCamera(
sensor_mode=4,
resolution='120x90',
framerate=40) as camera:
time.sleep(2) # let the camera warm up and set gain/white balance
output = MyOutput()
camera.start_recording(output, 'yuv')
camera.wait_recording(10) # record 10 seconds worth of data
camera.stop_recording()
Despite producing 40 frames per second of image data in numpy arrays, the CPU usage of this script is minimal. Comment out the print statement, which is actually quite CPU heavy, to see the overall CPU usage: it's about 2% on my Pi3 so there's plenty left over for whatever image processing you want to do.
v4l2-ctl
to set things on the camera that you can then use the/dev/video
device directly. I would try that and see how you get on. If you get stuck contact the author about any real problem or question. He is a very nice guy that loves to help out with real problems. Good Luck and please answer with how you solved your problem.