Skip to content

Commit

Permalink
Make PiBayerArray compatible with V2 module
Browse files Browse the repository at this point in the history
Adjust raw sizes (data's still 10-bit packed), and update the docs. Also
added some notes about the different res scaling for RGB video port
captures
  • Loading branch information
waveform80 committed Jun 18, 2016
1 parent b2c205d commit f7428b4
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 31 deletions.
73 changes: 52 additions & 21 deletions docs/recipes2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ demonstrates this along with the re-shaping necessary under Python 2.x::
output = output.reshape((112, 128, 3))
output = output[:100, :100, :]

.. warning::

Under certain circumstances (non-resized, non-YUV, video-port captures),
the resolution is rounded to 16x16 blocks instead of 32x16. Adjust your
resolution rounding accordingly.

.. versionadded:: 1.11


Expand Down Expand Up @@ -296,6 +302,12 @@ of data produced is:
.. image:: rgb_math.*
:align: center

.. warning::

Under certain circumstances (non-resized, non-YUV, video-port captures),
the resolution is rounded to 16x16 blocks instead of 32x16. Adjust your
resolution rounding accordingly.

The resulting `RGB`_ data is interleaved. That is to say that the red, green
and blue values for a given pixel are grouped together, in that order. The
first byte of the data is the red value for the pixel at (0, 0), the second
Expand Down Expand Up @@ -1235,13 +1247,14 @@ etc. This also means:
* Bayer data is *always* full resolution, regardless of the camera's output
:attr:`~PiCamera.resolution` and any ``resize`` parameter.

* Bayer data occupies the last 6,404,096 bytes of the output file. The first
32,768 bytes of this is header data which starts with the string ``'BRCM'``.
* Bayer data occupies the last 6,404,096 bytes of the output file for the V1
module, or the last 10,270,208 bytes for the V2 module. The first 32,768
bytes of this is header data which starts with the string ``'BRCM'``.

* Bayer data consists of 10-bit values, because this is the sensitivity of the
`OV5647`_ sensor used by the Pi's camera. The 10-bit values are organized as
4 8-bit values, followed by the low-order 2-bits of the 4 values packed into
a fifth byte.
`OV5647`_ and `IMX219`_ sensors used in the Pi's camera modules. The 10-bit
values are organized as 4 8-bit values, followed by the low-order 2-bits of
the 4 values packed into a fifth byte.

.. image:: bayer_bytes.*
:align: center
Expand Down Expand Up @@ -1286,34 +1299,51 @@ captures::
time.sleep(2)
# Capture the image, including the Bayer data
camera.capture(stream, format='jpeg', bayer=True)
ver = {
'RP_ov5647': 1,
'RP_imx219': 2,
}[camera.exif_tags['IFD0.Model']]

# Extract the raw Bayer data from the end of the stream, check the
# header and strip if off before converting the data into a numpy array

data = stream.getvalue()[-6404096:]
offset = {
1: 6404096,
2: 10270208,
}[ver]
data = stream.getvalue()[-offset:]
assert data[:4] == 'BRCM'
data = data[32768:]
data = np.fromstring(data, dtype=np.uint8)

# The data consists of 1952 rows of 3264 bytes of data. The last 8 rows
# of data are unused (they only exist because the actual resolution of
# 1944 rows is rounded up to the nearest 16). Likewise, the last 24
# bytes of each row are unused (why?). Here we reshape the data and
# strip off the unused bytes

data = data.reshape((1952, 3264))[:1944, :3240]

# Horizontally, each row consists of 2592 10-bit values. Every four
# bytes are the high 8-bits of four values, and the 5th byte contains
# the packed low 2-bits of the preceding four values. In other words,
# the bits of the values A, B, C, D and arranged like so:
# For the V1 module, the data consists of 1952 rows of 3264 bytes of data.
# The last 8 rows of data are unused (they only exist because the maximum
# resolution of 1944 rows is rounded up to the nearest 16).
#
# For the V2 module, the data consists of 2480 rows of 4128 bytes of data.
# There's actually 2464 rows of data, but the sensor's raw size is 2466
# rows, rounded up to the nearest multiple of 16: 2480.
#
# Likewise, the last few bytes of each row are unused (why?). Here we
# reshape the data and strip off the unused bytes.

reshape, crop = {
1: ((1952, 3264), (1944, 3240)),
2: ((2480, 4128), (2464, 4100)),
}[ver]
data = data.reshape(reshape)[:crop[0], :crop[1]]

# Horizontally, each row consists of 10-bit values. Every four bytes are
# the high 8-bits of four values, and the 5th byte contains the packed low
# 2-bits of the preceding four values. In other words, the bits of the
# values A, B, C, D and arranged like so:
#
# byte 1 byte 2 byte 3 byte 4 byte 5
# AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD AABBCCDD
#
# Here, we convert our data into a 16-bit array, shift all values left
# by 2-bits and unpack the low-order bits from every 5th byte in each
# row, then remove the columns containing the packed bits
# Here, we convert our data into a 16-bit array, shift all values left by
# 2-bits and unpack the low-order bits from every 5th byte in each row,
# then remove the columns containing the packed bits

data = data.astype(np.uint16) << 2
for byte in range(4):
Expand Down Expand Up @@ -1629,6 +1659,7 @@ acts as a flash LED with the Python script above.
.. _numpy: http://www.numpy.org/
.. _ring buffer: http://en.wikipedia.org/wiki/Circular_buffer
.. _OV5647: http://www.ovt.com/products/sensor.php?id=66
.. _IMX219: http://www.sony.net/Products/SC-HP/new_pro/april_2014/imx219_e.html
.. _Bayer CFA: http://en.wikipedia.org/wiki/Bayer_filter
.. _de-mosaicing: http://en.wikipedia.org/wiki/Demosaicing
.. _color balance: http://en.wikipedia.org/wiki/Color_balance
Expand Down
23 changes: 16 additions & 7 deletions picamera/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,12 @@ class PiBayerArray(PiArrayOutput):
camera.capture(output, 'jpeg', bayer=True)
print(output.array.shape)
Note that Bayer data is *always* full resolution, so the resulting array
always has the shape (1944, 2592, 3); this also implies that the optional
*size* parameter (for specifying a resizer resolution) is not available
with this array class. As the sensor records 10-bit values, the array uses
the unsigned 16-bit integer data type.
Note that Bayer data is *always* full resolution, so the resulting
array always has the shape (1944, 2592, 3) with the V1 module, or
(2464, 3280, 3) with the V2 module; this also implies that the
optional *size* parameter (for specifying a resizer resolution) is not
available with this array class. As the sensor records 10-bit values,
the array uses the unsigned 16-bit integer data type.
By default, `de-mosaicing`_ is **not** performed; if the resulting array is
viewed it will therefore appear dark and too green (due to the green bias
Expand Down Expand Up @@ -353,14 +354,22 @@ def __init__(self, camera):
def flush(self):
super(PiBayerArray, self).flush()
self._demo = None
ver = 1
data = self.getvalue()[-6404096:]
if data[:4] != b'BRCM':
raise PiCameraValueError('Unable to locate Bayer data at end of buffer')
ver = 2
data = self.getvalue()[-10270208:]
if data[:4] != b'BRCM':
raise PiCameraValueError('Unable to locate Bayer data at end of buffer')
# Strip header
data = data[32768:]
# Reshape into 2D pixel values
reshape, crop = {
1: ((1952, 3264), (1944, 3240)),
2: ((2480, 4128), (2464, 4100)),
}[ver]
data = np.frombuffer(data, dtype=np.uint8).\
reshape((1952, 3264))[:1944, :3240]
reshape(reshape)[:crop[0], :crop[1]]
# Unpack 10-bit values; every 5 bytes contains the high 8-bits of 4
# values followed by the low 2-bits of 4 values packed into the fifth
# byte
Expand Down
8 changes: 6 additions & 2 deletions tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,12 @@ def test_bayer_array(camera, mode):
with picamera.array.PiBayerArray(camera) as stream:
camera.capture(stream, 'jpeg', bayer=True)
# Bayer data is always full res
assert stream.array.shape == (1944, 2592, 3)
assert stream.demosaic().shape == (1944, 2592, 3)
if camera.exif_tags['IFD0.Model'] == 'RP_ov5647':
assert stream.array.shape == (1944, 2592, 3)
assert stream.demosaic().shape == (1944, 2592, 3)
else:
assert stream.array.shape == (2464, 3280, 3)
assert stream.demosaic().shape == (2464, 3280, 3)

def test_motion_array1(camera, mode):
resolution, framerate = mode
Expand Down
5 changes: 4 additions & 1 deletion tests/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,10 @@ def test_capture_bayer(camera, mode):
camera.capture(stream, format='jpeg', bayer=True)
# Bayer data is always the last 6404096 bytes of the stream, and starts
# with 'BRCM'
stream.seek(-6404096, io.SEEK_END)
if camera.exif_tags['IFD0.Model'] == 'RP_ov5647':
stream.seek(-6404096, io.SEEK_END)
else:
stream.seek(-10270208, io.SEEK_END)
assert stream.read(4) == 'BRCM'

def test_capture_sequence_bayer(camera, mode):
Expand Down

0 comments on commit f7428b4

Please sign in to comment.