Skip to content

Commit 93a0808

Browse files
committed
Fix #512
Merge branch 'colorspace'
2 parents 016b718 + 6f21a72 commit 93a0808

File tree

4 files changed

+109
-10
lines changed

4 files changed

+109
-10
lines changed

picamera/camera.py

+69-5
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class PiCameraMaxFramerate(object):
113113
'resolution',
114114
'framerate',
115115
'isp_blocks',
116+
'colorspace',
116117
))
117118

118119

@@ -341,6 +342,13 @@ class PiCamera(object):
341342
'sharpening': 1 << 22,
342343
}
343344

345+
COLORSPACES = {
346+
'auto': mmal.MMAL_COLOR_SPACE_UNKNOWN,
347+
'jfif': mmal.MMAL_COLOR_SPACE_JPEG_JFIF,
348+
'bt601': mmal.MMAL_COLOR_SPACE_ITUR_BT601,
349+
'bt709': mmal.MMAL_COLOR_SPACE_ITUR_BT709,
350+
}
351+
344352
_METER_MODES_R = {v: k for (k, v) in METER_MODES.items()}
345353
_EXPOSURE_MODES_R = {v: k for (k, v) in EXPOSURE_MODES.items()}
346354
_FLASH_MODES_R = {v: k for (k, v) in FLASH_MODES.items()}
@@ -350,6 +358,7 @@ class PiCamera(object):
350358
_STEREO_MODES_R = {v: k for (k, v) in STEREO_MODES.items()}
351359
_CLOCK_MODES_R = {v: k for (k, v) in CLOCK_MODES.items()}
352360
_ISP_BLOCKS_R = {v: k for (k, v) in ISP_BLOCKS.items()}
361+
_COLORSPACES_R = {v: k for (k, v) in COLORSPACES.items()}
353362

354363
__slots__ = (
355364
'_used_led',
@@ -432,6 +441,7 @@ def _parse_options(args, kwargs):
432441
'clock_mode': 'reset',
433442
'framerate_range': None,
434443
'isp_blocks': None,
444+
'colorspace': 'auto',
435445
}
436446
arg_names = (
437447
'camera_num',
@@ -536,6 +546,12 @@ def _init_config(cls, options):
536546
raise PiCameraValueError(
537547
'Invalid clock mode: %s' % options['clock_mode'])
538548

549+
try:
550+
colorspace = cls.COLORSPACES[options['colorspace']]
551+
except KeyError:
552+
raise PiCameraValueError(
553+
'Invalid colorspace: %s' % options['colorspace'])
554+
539555
all_blocks = set(cls.ISP_BLOCKS.keys())
540556
if options['isp_blocks'] is None:
541557
isp_blocks = 0
@@ -553,13 +569,15 @@ def _init_config(cls, options):
553569
clock_mode=clock_mode,
554570
resolution=cls.MAX_RESOLUTION,
555571
framerate=30,
556-
isp_blocks=0)
572+
isp_blocks=0,
573+
colorspace=mmal.MMAL_COLOR_SPACE_UNKNOWN)
557574
new_config = PiCameraConfig(
558575
sensor_mode=options['sensor_mode'],
559576
clock_mode=clock_mode,
560577
resolution=resolution,
561578
framerate=framerate,
562-
isp_blocks=isp_blocks)
579+
isp_blocks=isp_blocks,
580+
colorspace=colorspace)
563581
return old_config, new_config
564582

565583
def _init_led(self, options):
@@ -2159,8 +2177,8 @@ def _enable_camera(self):
21592177

21602178
def _configure_splitter(self):
21612179
"""
2162-
Ensures all splitter output ports have a sensible format (I420) and
2163-
buffer sizes.
2180+
Ensures the splitter has the same format as the attached camera
2181+
output port (the video port).
21642182
21652183
This method is used to ensure the splitter configuration is sane,
21662184
typically after :meth:`_configure_camera` is called.
@@ -2210,7 +2228,8 @@ def _get_config(self):
22102228
),
22112229
framerate=framerate,
22122230
isp_blocks=self._camera.control.params[
2213-
mmal.MMAL_PARAMETER_CAMERA_ISP_BLOCK_OVERRIDE]
2231+
mmal.MMAL_PARAMETER_CAMERA_ISP_BLOCK_OVERRIDE],
2232+
colorspace=self._camera.outputs[0].colorspace
22142233
)
22152234

22162235
def _configure_camera(self, old, new):
@@ -2306,6 +2325,7 @@ def _configure_camera(self, old, new):
23062325
else:
23072326
port.framesize = new.resolution
23082327
port.framerate = new.framerate
2328+
port.colorspace = new.colorspace
23092329
port.commit()
23102330
except:
23112331
# If anything goes wrong, restore original resolution and
@@ -2529,9 +2549,53 @@ def _set_isp_blocks(self, value):
25292549
25302550
camera.isp_blocks |= {{'white-balance'}}
25312551
2552+
The camera must not be closed, and no recording must be active when the
2553+
property is set.
2554+
25322555
.. versionadded:: 1.14
25332556
""".format(values=docstring_values(ISP_BLOCKS)))
25342557

2558+
def _get_colorspace(self):
2559+
return self._COLORSPACES_R[self._camera.outputs[0].colorspace]
2560+
def _set_colorspace(self, value):
2561+
self._check_camera_open()
2562+
self._check_recording_stopped()
2563+
try:
2564+
colorspace = self.COLORSPACES[value]
2565+
except KeyError:
2566+
raise PiCameraValueError("Invalid colorspace %s" % value)
2567+
config = self._get_config()
2568+
self._disable_camera()
2569+
self._configure_camera(config, config._replace(colorspace=colorspace))
2570+
self._configure_splitter()
2571+
self._enable_camera()
2572+
colorspace = property(_get_colorspace, _set_colorspace, doc="""\
2573+
Retrieves or sets the `color space`_ that the camera uses for
2574+
conversion between the `YUV`_ and RGB systems.
2575+
2576+
The value is a string that represents which of a series of fixed
2577+
conversion tables are used by the camera firmware (the firmware works
2578+
largely in the YUV color system internally). The following strings are
2579+
the valid values:
2580+
2581+
{values}
2582+
2583+
The "bt601" and "bt709" values correspond to the standard `SDTV and
2584+
HDTV tables`_. The "auto" value is the default and corresponds to
2585+
"bt601" in practice. One of these values is likely what you want when
2586+
recording H.264 video. However, when recording MJPEG video, you may
2587+
want to use the "jfif" table instead as it produces luma values in the
2588+
0-255 range, rather than the 16-235 range produced by the standard
2589+
tables.
2590+
2591+
The camera must not be closed, and no recording must be active when the
2592+
property is set.
2593+
2594+
.. _color space: https://en.wikipedia.org/wiki/Color_space
2595+
.. _YUV: https://en.wikipedia.org/wiki/YUV
2596+
.. _SDTV and HDTV tables: https://en.wikipedia.org/wiki/YUV#Conversion_to/from_RGB
2597+
""".format(values=docstring_values(COLORSPACES)))
2598+
25352599
def _get_resolution(self):
25362600
self._check_camera_open()
25372601
return mo.PiResolution(

picamera/mmal.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ def MMAL_FOURCC(s):
6666
return sum(ord(c) << (i * 8) for (i, c) in enumerate(s))
6767

6868
def FOURCC_str(n):
69-
return ''.join(chr(n >> i & 0xFF) for i in range(0, 32, 8))
69+
if n == 0:
70+
return '\\0'
71+
else:
72+
return ''.join(chr(n >> i & 0xFF) for i in range(0, 32, 8))
7073

7174
MMAL_MAGIC = MMAL_FOURCC('mmal')
7275

picamera/mmalobj.py

+26-4
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ def print_pipeline(port):
585585
Prints a human readable representation of the pipeline feeding the
586586
specified :class:`MMALVideoPort` *port*.
587587
"""
588-
rows = [[], [], [], [], []]
588+
rows = [[], [], [], [], [], []]
589589
under_comp = False
590590
for obj in reversed(list(debug_pipeline(port))):
591591
if isinstance(obj, (MMALBaseComponent, MMALPythonBaseComponent)):
@@ -607,11 +607,14 @@ def print_pipeline(port):
607607
rows[3].append('%dbps' % (obj._port[0].format[0].bitrate,))
608608
if under_comp:
609609
rows[4].append('frame')
610-
under_comp = False
611610
rows[4].append('%dx%d@%sfps' % (
612611
obj._port[0].format[0].es[0].video.width,
613612
obj._port[0].format[0].es[0].video.height,
614613
obj.framerate))
614+
if under_comp:
615+
rows[5].append('colorspc')
616+
under_comp = False
617+
rows[5].append(mmal.FOURCC_str(obj._port[0].format[0].es[0].video.color_space))
615618
elif isinstance(obj, MMALPythonPort):
616619
rows[0].append('[%d]' % obj._index)
617620
if under_comp:
@@ -633,17 +636,22 @@ def print_pipeline(port):
633636
obj._format[0].es[0].video.width,
634637
obj._format[0].es[0].video.height,
635638
obj.framerate))
639+
if under_comp:
640+
rows[5].append('colorspc')
641+
rows[5].append('???')
636642
elif isinstance(obj, (MMALConnection, MMALPythonConnection)):
637643
rows[0].append('')
638644
rows[1].append('')
639645
rows[2].append('-->')
640646
rows[3].append('')
641647
rows[4].append('')
648+
rows[5].append('')
642649
if under_comp:
643650
rows[1].append('encoding')
644651
rows[2].append('buf')
645652
rows[3].append('bitrate')
646653
rows[4].append('frame')
654+
rows[5].append('colorspc')
647655
cols = list(zip(*rows))
648656
max_lens = [max(len(s) for s in col) + 2 for col in cols]
649657
rows = [
@@ -1376,10 +1384,13 @@ class MMALVideoPort(MMALPort):
13761384

13771385
def __repr__(self):
13781386
if self._port is not None:
1379-
return '<MMALVideoPort "%s": format=MMAL_FOURCC(%r) buffers=%dx%d frames=%s@%sfps>' % (
1387+
return (
1388+
'<MMALVideoPort "%s": format=MMAL_FOURCC("%s") buffers=%dx%d '
1389+
'frames=%s@%sfps colorspace=MMAL_FOURCC("%s")>' % (
13801390
self.name, mmal.FOURCC_str(self.format),
13811391
self._port[0].buffer_num, self._port[0].buffer_size,
1382-
self.framesize, self.framerate)
1392+
self.framesize, self.framerate,
1393+
mmal.FOURCC_str(self.colorspace)))
13831394
else:
13841395
return '<MMALVideoPort closed>'
13851396

@@ -1425,6 +1436,17 @@ def _set_framerate(self, value):
14251436
changes effective.
14261437
""")
14271438

1439+
def _get_colorspace(self):
1440+
return self._port[0].format[0].es[0].video.color_space
1441+
def _set_colorspace(self, value):
1442+
self._port[0].format[0].es[0].video.color_space = value
1443+
colorspace = property(_get_colorspace, _set_colorspace, doc="""\
1444+
Retrieves or sets the color-space of the port's frames.
1445+
1446+
After setting this attribute, call :meth:`~MMALPort.commit` to make the
1447+
changes effective.
1448+
""")
1449+
14281450

14291451
class MMALAudioPort(MMALPort):
14301452
"""

tests/test_attr.py

+10
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,16 @@ def test_isp_blocks(camera, previewing):
442442
finally:
443443
camera.isp_blocks = save_blocks
444444

445+
def test_colorspace(camera, previewing):
446+
save_colorspace = camera.colorspace
447+
try:
448+
camera.colorspace = 'jfif'
449+
assert camera.colorspace == 'jfif'
450+
camera.colorspace = 'bt709'
451+
assert camera.colorspace == 'bt709'
452+
finally:
453+
camera.colorspace = save_colorspace
454+
445455
def test_framerate_delta(camera, previewing):
446456
for num in range(-10, 11):
447457
camera.framerate_delta = num / 10

0 commit comments

Comments
 (0)