Skip to content

Instantly share code, notes, and snippets.

@KarimAziev
Last active December 30, 2025 20:07
Show Gist options
  • Select an option

  • Save KarimAziev/701838056a11c79143f2fc9f86c699f5 to your computer and use it in GitHub Desktop.

Select an option

Save KarimAziev/701838056a11c79143f2fc9f86c699f5 to your computer and use it in GitHub Desktop.
I converted the Picamera 2 PDF manual (2025-03) to Markdown because, apparently, the Raspberry Pi Foundation couldn't be bothered to provide one. #md

Table of Contents

Chapter 1. Introduction

Picamera2 is a Python library that gives convenient access to the camera system of the Raspberry Pi. It is designed for cameras connected with the flat ribbon cable directly to the connector on the Raspberry Pi itself, and not for other types of camera, although there is some limited support for USB cameras.

Picamera2 is built on top of the open source libcamera project, which provides support for complex camera systems in Linux. Picamera2 directly uses the Python bindings supplied by libcamera, although the Picamera2 API provides access at a higher level. Most users will find it significantly easier to use for Raspberry Pi applications than libcamera’s own bindings, and Picamera2 is tuned specifically to address the capabilities of the Raspberry Pi’s built-in camera and imaging hardware.

Picamera2 is the replacement for the legacy PiCamera Python library. It provides broadly the same facilities, although many of these capabilities are exposed differently. Picamera2 provides a very direct and more accurate view of the Pi’s camera system, and makes it easy for Python applications to make use of them. Those still using the legacy camera stack should continue to use the old PiCamera library. Note that the legacy camera stack and the old PiCamera library have been deprecated for a number of years and no longer receive any kind of support.

Note

This document assumes general familiarity with Raspberry Pis and Python programming. A working understanding of images and how they can be represented as a two-dimensional array of pixels will also be highly beneficial. For a deeper understanding of Picamera2, some basic knowledge of Python’s numpy library will be helpful. For some more advanced use-cases, an awareness of OpenCV (the Python cv2 module) will also be useful.

Software version This manual describes Picamera2 version 0.3.25 which is at the time of writing the most up-to-date release.

Chapter 2. Getting started

2.1. Requirements

Picamera2 is designed for systems running either Raspberry Pi OS or Raspberry Pi OS Lite, using a Bullseye or later image. Picamera2 is pre-installed in current images obtained using the Raspberry Pi Imager tool. Alternatively the latest images can also be downloaded from the Raspberry Pi website. We strongly recommend users with older images to consider updating them or to proceed to the installation instructions.

Picamera2 can operate in a headless manner, not requiring an attached screen or keyboard. When first setting up such a system we would recommend attaching a keyboard and screen if possible, as it can make trouble-shooting easier.

Raspberry Pi OS Bullseye and later images by default run the libcamera camera stack, which is required for Picamera2.

You can check that libcamera is working by opening a command window and typing:

rpicam-hello

You should see a camera preview window for about five seconds. If you do not, please refer to the Raspberry Pi camera documentation.

Using lower-powered devices

Some lower-powered devices, such as the Raspberry Pi Zero, are generally much slower at running desktop GUI (Graphical User Interface) software. Correspondingly, performance may be poor trying to run the camera system with a preview window that has to display images through the GUI’s display stack.

On such devices we would recommend either not displaying the images, or displaying them without the GUI. The Pi can be configured to boot to the console (avoiding the GUI) using the raspi-config tool, or if you are using the GUI it can temporarily be suspended by holding the Ctrl+Alt+F1 keys (and use Ctrl+Alt+F7 to return again).

2.2. Installation and updating

We strongly recommend starting by updating to the latest version of Raspberry Pi OS. Most straightforwardly, this can be downloaded using the Raspberry Pi Imager tool for flashing micro SD cards. We also recommend updating your Raspberry Pi installation once it’s booted with before proceeding further:

sudo apt update
sudo apt full-upgrade

As of mid-September 2022, Picamera2 is pre-installed in Raspberry Pi OS images, but not in Raspberry Pi OS Lite images. Where Picamera2 is not pre-installed, it can be installed with

sudo apt install -y python3-picamera2 --no-install-recommends

which installs a slightly reduced version with fewer windowing or GUI components (this would be suitable for a "Lite" system). Or to install a full version, use:

sudo apt install -y python3-picamera2

2.1. Requirements

When updating your Picamera2 installation we recommend doing so through a full system upgrade as we did earlier (sudo apt update followed by sudo apt full-upgrade). This ensures that all your system libraries are guaranteed to be compatible. Note that you should always back up anything critical from your system before performing the full upgrade.

Installation using pip

Warning

We strongly recommend installing and updating Picamera2 using apt which will avoid compatibility problems. If you do wish to install Picamera2 using pip, please read to the end of this section before starting. On Bookworm and later images you will also need to be familiar with Python virtual environments.

The easiest way to get the dependencies that you need will be to install Picamera2 first using apt if it was not already pre-installed (see above). Subsequently, any virtual environment should be created to inherit system packages (for example, use the --system-site-packages option when creating a venv ). A subsequent pip installation will then take precedence over the one from apt:

pip install picamera2

This does rely on the new version of Picamera2 being compatible with the libcamera Python bindings that were installed from apt. Note that there is no libcamera package available through pip, so if you find that your version of Picamera2 is not compatible then you may need to build libcamera for yourself.

Users wanting to use Picamera2 with different operating systems that do not include libcamera, or with different non-system versions of Python, are also likely to need to build their own version of libcamera. Instructions for this are available on the Raspberry Pi website, however, it does lie beyond the scope of this guide.

Configuring the /boot/firmware/config.txt file

Normally no changes should be required to the /boot/firmware/config.txt file (or /boot/config.txt on some old versions of the OS) from the one supplied when the operating system was installed.

Some users, for some applications, may find themselves needing to allocate more memory to the camera system. In this case, please consider increasing the amount of available CMA memory.

In the past, legacy camera stack users may have increased the amount of gpu_mem to enable the old camera system to run. Picamera2 does not use this type of memory, so any such lines should be deleted from your /boot/config.txt file as they will simply cause system memory to be wasted.

2.3. A first example

The following script will:

  1. Open the camera system
  2. Generate a camera configuration suitable for preview
  3. Configure the camera system with that preview configuration
  4. Start the preview window
  5. Start the camera running
  6. Wait for two seconds and capture a JPEG file (still in the preview resolution)

Note

Users of Raspberry Pi 3 or earlier devices will need to enable Glamor in order for this example script using X Windows to work. To do this, run sudo raspi-config in a command window, choose Advanced Options and then enable Glamor graphic acceleration. Finally reboot your device.

GUI users should enter:

from picamera2 import Picamera2, Preview
import time

picam2 = Picamera2()
camera_config = picam2.create_preview_configuration()
picam2.configure(camera_config)
picam2.start_preview(Preview.QTGL)
picam2.start()
time.sleep( 2 )
picam2.capture_file("test.jpg")

Non-GUI users should use the same script, but replacing Preview.QTGL by Preview.DRM, so as to use the non-GUI preview implementation:

from picamera2 import Picamera2, Preview
import time

picam2 = Picamera2()
camera_config = picam2.create_preview_configuration()
picam2.configure(camera_config)
picam2.start_preview(Preview.DRM)
picam2.start()
time.sleep( 2 )
picam2.capture_file("test.jpg")

2.4. Picamera2’s high-level API

Picamera2 has some high-level and very convenient functions for capturing images and video recordings. These are ideal for those who do not want to know too much about the details of how Picamera2 works and "just want a picture" (or video), though most users will want a more complete understanding of what happens under the hood, as we saw above.

If you simply want to capture an image, the following is sufficient:

from picamera2 import Picamera

picam2 = Picamera2()

picam2.start_and_capture_file("test.jpg")

You can capture multiple images with the start_and_capture_files function. Or, to record a five second video:

from picamera2 import Picamera

picam2 = Picamera2()

picam2.start_and_record_video("test.mp4", duration=5)

We will learn more about these functions later on, for both still images and video.

2.5. Multiple Cameras

With the appropriate hardware, it is possible to connect multiple cameras to a Raspberry Pi and, with a compute module or a Raspberry Pi 5, to drive two of them simultaneously. For more information, please refer to the corresponding section in the Advanced Topics.

2.6. Additional software

When using Picamera2, it’s often useful to install other packages. For convenience we list some common ones here.

2.6.1. OpenCV

OpenCV is not a requirement for Picamera2, though a number of examples use it. It can by installed from apt very easily, avoiding the long build times involved in some other methods:

sudo apt install -y python3-opencv
sudo apt install -y opencv-data

Installing from apt also ensures you do not get a version that is incompatible with the Qt GUI toolkit that is installed with Picamera2.

2.6.2. TensorFlow Lite

TensorFlow Lite can be installed with:

pip3 install tflite-runtime

2.6.3. FFmpeg

Some features of Picamera2 make use of the FFmpeg library. Normally this should be installed by default on a Raspberry Pi, but in case it isn’t the following should fetch it:

sudo apt install -y ffmpeg

2.7. Further examples

Throughout this guide we’ll give lots of examples, but we’ll also highlight some of those in Picamera2’s GitHub repository.

The examples folder in the repository can be found here. There are some additional examples framed as Qt applications which can be found here.

Chapter 3. Preview windows

3.1. Preview window parameters

In the previous section we have already seen two different types of preview window. There are in fact four different versions, which we shall discuss below.

All four preview implementations accept exactly the same parameters so that they are interchangeable:

  • x - the x-offset of the preview window
  • y - the y-offset of the preview window
  • width - the width of the preview window
  • height - the height of the preview window
  • transform - a transform that allows the camera image to be horizontally and/or vertically flipped on the display.

All the parameters are optional, and default values will be chosen if omitted. The following example will place an 800x600 pixel preview window at (100, 200) on the display, and will horizontally mirror the camera preview image:

from picamera2 import Picamera2, Preview
from libcamera import Transform

picam2 = Picamera2()
picam2.start_preview(Preview.QTGL, x=100, y=200, width=800, height=600,
transform=Transform(hflip=1))
picam2.start()

The supported transforms are:

  • Transform() - the identity transform, which is the default
  • Transform(hflip=1) - horizontal flip
  • Transform(vflip=1) - vertical flip
  • Transform(hflip=1, vflip=1) - horizontal and vertical flip (equivalent to a 180 degree rotation)

It’s important to realise that the display transform discussed here does not have any effect on the actual images received from the camera. It only applies the requested transform as it renders the pixels onto the screen. We’ll encounter camera transforms again when it comes actually to transforming the images as the camera delivers them.

Please also note that in the example above, the start_preview() function must be called before the call to picam2.start().

Finally, if the camera images have a different aspect ratio to the preview window, they will be letter- or pillar-boxed to fit, preserving the image’s proper aspect ratio.

3.2. Preview window implementations

3.2.1. QtGL preview

This preview window is implemented using the Qt GUI toolkit and uses GLES hardware graphics acceleration. It is the most efficient way to display images on the screen when using the GUI environment and we would recommend it in nearly all circumstances when a GUI is required.

The QtGL preview window can be started with:

from picamera2 import Picamera2, Preview

picam2 = Picamera2()
picam2.start_preview(Preview.QTGL)

The QtGL preview window is not recommended when the image needs to be shown on a remote display (not connected to the Pi). Please refer to the Qt preview window below.

Users of Pi 3 or earlier devices will need to enable Glamor graphic acceleration to use the QtGL preview window.

Note

There is a limit to the size of image that the 3D graphics hardware on the Pi can handle. For Raspberry Pi 4 and later devices this limit is 4096 pixels in either dimension. For Pi 3 and earlier devices this limit is 2048 pixels. If you try to feed a larger image to the QtGL preview window it will report an error and the program will terminate.

3.2.2. DRM/KMS preview

The DRM/KMS preview window is for use when X Windows is not running and camera system can lease a "layer" on the display for displaying graphics. It can be started with:

from picamera2 import Picamera2, Preview

picam2 = Picamera2()
picam2.start_preview(Preview.DRM)

Because X Windows is not running, it is not possible to move or resize this window with the mouse.

The DRM/KMS preview will be the natural choice for Raspberry Pi OS Lite users. It is also strongly recommended for lower-powered Raspberry Pis that would find it expensive to pass a preview (for example at 30 frames per second) through the X Windows display stack.

Note

The DRM/KMS preview window is not supported when using the legacy fkms display driver. Please use the recommended kms display driver (dtoverlay=vc4-kms-v3d in your /boot/config.txt file) instead.

3.2.3. Qt preview

Like the QtGL preview, this window is also implemented using the Qt framework, but this time using software rendering rather than 3D hardware acceleration. As such, it is computationally costly and should be avoided where possible. Even a Raspberry Pi 4 will start to struggle once the preview window size increases.

The Qt preview can be started with:

from picamera2 import Picamera2, Preview

picam2 = Picamera2()

picam2.start_preview(Preview.QT)

The main use case for the Qt preview is displaying the preview window on another networked computer using X forwarding, or using the VNC remote desktop software. Under these conditions the 3D-hardware-accelerated implementation either does not work at all, or does not work very well.

Users of Raspberry Pi 3 or earlier devices will need to enable Glamor graphic acceleration to use the Qt preview window.

3.2.4. NULL preview

Normally it is the preview window that actually drives the libcamera system by receiving camera images, passing them to the application, and then recycling those buffers back to libcamera once the user no longer needs them. The consequence is then that even when no preview images are being displayed, something still has to run in order to receive and then return those camera images.

This is exactly what the NULL preview does. It displays nothing; it merely drives the camera system.

The NULL preview is in fact started automatically whenever the camera system is started (picam2.start()) if no preview is yet running, which is why alternative preview windows must be started earlier. You can start the NULL preview explicitly like this:

from picamera2 import Picamera2, Preview

picam2 = Picamera2()
picam2.start_preview(Preview.NULL)

though in fact the call to start_preview is redundant for the NULL preview and can be omitted.

The NULL preview accepts the same parameters as the other preview implementations, but ignores them completely.

3.3. Starting and stopping previews

We have seen how to start preview windows, including the NULL preview which actually displays nothing. In fact, the first parameter to the start_preview function can take the following values:

  • None - No preview of any kind is started. The application would have to supply its own code to drive the camera system.
  • False - The NULL preview is started.
  • True - One of the three other previews is started. Picamera2 will attempt to auto-detect which one it should start, though this is purely on a "best efforts" basis.
  • Any of the four Preview enum values.

Preview windows can be stopped; an alternative one should then be started. We do not recommend starting and stopping preview windows because it can be quite expensive to open and close windows, during which time camera frames are likely to be dropped.

The Picamera2.start function accepts a show_preview parameter which can take on any one of these same values. This is just a convenient shorthand that allows the amount of boilerplate code to be reduced. Note that stopping the camera (Picamera2.stop) does not stop the preview window, so the stop_preview function would have to be called explicitly before starting another.

For example, the following script would start the camera system running, run for a short while, and then attempt to auto- detect which preview window to use in order actually to start displaying the images:

from picamera2 import Picamera2, Preview
import time

picam2 = Picamera2()
config = picam2.create_preview_configuration()
picam2.configure(config)
picam2.start()

time.sleep(2)

picam2.stop_preview()
picam2.start_preview(True)

time.sleep(2)

In this example:

  1. The NULL preview will start automatically with the picam2.start() call and will run for 2 seconds
  2. It will then be stopped and a preview that displays an actual window will be started
  3. This will then run for 2 more seconds

It’s worth noting that nothing particularly bad happens if you stop the preview and then fail to restart another, or do not start another immediately. All the buffers that are available will be filled with camera images by libcamera. But with no preview running, nothing will read out these images and recycle the buffers, so libcamera will simply stall. When a preview is restarted, normal operation will resume, starting with those slightly old camera images that are still queued up waiting to be read out.

Note

Many programmers will be familiar with the notion of an event loop. Each type of preview window implements an event loop to dequeue frames from the camera, so the NULL preview performs this function when no other event loop (such as the one provided by Qt) is running.

3.4. Remote preview windows

The preview window can be displayed on a remote display, for example when you have logged in to your Pi over ssh or through VNC. When using ssh:

  • You should use the -X parameter (or equivalent) to set up X-forwarding.
  • The QtGL (hardware accelerated) preview will not work and will result in an error message. The Qt preview must be used instead, though being software rendered (and presumably travelling over a network), framerates can be expected to be significantly poorer.

When using VNC:

  • The QtGL (hardware accelerated) window works adequately if you also have a display connected directly to your Raspberry Pi

  • If you do not have a display connected directly to the Pi, the QtGL preview will work very poorly, and the Qt preview window should be used instead

If you are not running, or have suspended, the GUI on the Pi but still have a display attached, you can log into the Pi without X-forwarding and use the DRM/KMS preview implementation. This will appear on the display that is attached directly to the Pi.

3.5. Other Preview Features

3.5.1. Setting the Preview Title Bar

For the Qt and QtGL preview windows, camera information may optionally be displayed on the window title bar. Specifically, information from the metadata that accompanies each image can be displayed by listing the required metadata fields in the Picamera2 object’s title_fields property. For example

from picamera2 import Picamera

picam2 = Picamera2()
picam2.start(show_preview=True)

picam2.title_fields = ["ExposureTime", "AnalogueGain"]

This will display every image’s exposure time and analogue gain values on the title bar. If any of the given field names are misspelled or unavailable then the value INVALID is reported for them.

When using the NULL preview or DRM preview, or when Picamera2 is embedded in a larger Qt application, then the title_fields property has no effect.

The metadata that is available can easily be inspected using the capture_metadata method. Alternatively, more information on the different forms of metadata is available in the appendices.

3.5.2. Further Preview Topics

Further preview features are covered in the Advanced topics section.

  • Overlays (transparent images overlaid on the live camera feed) are discussed among the Advanced Topics.
  • For Qt applications, displaying a preview window doesn’t make sense as the Qt framework will run the event loop. However, the underlying widgets are still useful and are discussed further later.

3.6. Further examples

Most of the GitHub examples will create and start preview windows of some kind, for example:

  • preview.py - starts a QtGL preview window
  • preview_drm.py - starts a DRM preview window
  • preview_x_forwarding.py - starts a Qt preview window

Chapter 4. Configuring the camera

4.1. Generating and using a camera configuration

Once a Picamera2 object has been created, the general pattern is that a configuration must be generated for the camera, that the configuration must be applied to the camera system (using the Picamera2.configure method), and that then the camera system can be started.

Picamera2 provides a number of configuration-generating methods that can be used to provide suitable configurations for common use cases:

  • Picamera2.create_preview_configuration will generate a configuration suitable for displaying camera preview images on the display, or prior to capturing a still image
  • Picamera2.create_still_configuration will generate a configuration suitable for capturing a high-resolution still image
  • Picamera2.create_video_configuration will generate a configuration suitable for recording video files

There is nothing inherently different about any one of these methods over another, they differ only in that they supply slightly different defaults so that it’s easier to get something appropriate to the use case at hand. But, if you choose the necessary parameters carefully, you can use a configuration from any of these functions for any use case.

So, for example, to set up the camera to start delivering a stream of preview images you might use:

from picamera2 import Picamera

picam2 = Picamera2()
config = picam2.create_preview_configuration()
picam2.configure(config)
picam2.start()

This is fairly typical, though the configuration-generating methods allow numerous optional parameters to adjust the resulting configuration precisely for different situations. Additionaly, once a configuration object has been created, applications are free to alter the object’s recommendations before calling picam2.configure.

One thing we shall learn is that configurations are just Python dictionaries, and it’s easy for us to inspect them and see what they are saying.

4.2. Configurations in more detail

Picamera2 is easier to configure with some basic knowledge of the camera system on the Raspberry Pi. This will make it clearer why particular image streams are available and why they have some of the features and properties that they do.

The sequence of events is as follows:

  1. On the left we have the camera module, which delivers images through the flat ribbon cable to the Pi. The images delivered by the camera are not human-viewable images, but need lots of work to clean them up and produce a realistic picture.
  2. A hardware block called a CSI-2 Receiver on the Pi transfers the incoming camera image into memory.
  3. The Pi has an Image Signal Processor (ISP) which reads this image from memory. It performs all these cleaning and processing steps on the pixels that were received from the camera.
  4. The ISP can produce up to two output images for every input frame from the camera. We designate one of them as the main image, and it can be in either RGB or YUV formats.
  5. The second image is a lower resolution image, referred to often as the "lores" image; it must be no larger than the main image. On a Pi 4 or earlier device, the lores image must be in a YUV format, whereas on a Pi 5 (or later device) it can be RGB or YUV, like the main image.
  6. Finally, the image data that was received from the sensor and written directly to memory can also be delivered to applications. This is called the raw image.

The configuration of Picamera2 therefore divides into:

  • General parameters that apply globally to the Picamera2 system and across the whole of the ISP.

  • And per-stream configuration within the ISP that determines the output formats and sizes of the main and lores streams. We note that the main stream is always defined and delivered to the application, using default values if the application did not explicitly request one.

  • Some applications need to be able to control the mode (resolution, bit depth and so on) that the sensor is running in. This can be done using the sensor part of the camera configuration or, if this is absent, it will be inferred from the specification of the raw stream (if present).

  • Mostly, a configuration does not include camera settings that can be changed at runtime (such as brightness or contrast). However, certain use cases do sometimes have particular preferences about certain of these control values, and they can be stored as part of a configuration so that applying the configuration will apply the runtime controls automatically too.

4.2.1. General configuration parameters

The configuration parameters that affect all the output streams are:

  • transform - whether camera images are horizontally or vertically mirrored, or both (giving a 180 degree rotation). All three streams (if present) share the same transform.
  • colour_space - the colour space of the output images. The main and lores streams must always share the same colour space. The raw stream is always in a camera-specific colour space.
  • buffer_count - the number of sets of buffers to allocate for the camera system. A single set of buffers represents one buffer for each of the streams that have been requested.
  • queue - whether the system is allowed to queue up a frame ready for a capture request.
  • sensor - parameters that allow an application to select a particular mode of operation for the sensor. This is quite an involved topic, which we shall cover later.
  • display - this names which (if any) of the streams are to be shown in the preview window. It does not actually affect the camera images in any way, only what Picamera2 does with them.
  • encode - this names which (if any) of the streams are to be encoded if a video recording is started. This too does not affect the camera images in any way, only what Picamera2 does with them. This parameter only specifies a default value that can be overridden when the encoders are started.

4.2.1.1. More on transforms

Transforms can be constructed as shown below:

>>> from libcamera import Transform

>>> Transform()
<libcamera.Transform 'identity'>
>>> Transform(hflip=True)
<libcamera.Transform 'hflip'>
>>> Transform(vflip=True)
<libcamera.Transform 'vflip'>
>>> Transform(hflip=True, vflip=True)
<libcamera.Transform 'hvflip'>

Transforms can be passed to all the configuration-generating methods using the transform keyword parameter. For example:

from picamera2 import Picamera
from libcamera import Transform

picam2 = Picamera2()
preview_config = picam2.create_preview_configuration(transform=Transform(hflip=True))

Picamera2 only supports the four transforms shown above. Other transforms (involving image transposition) exist but are not supported. If unspecified, the transform always defaults to the identity transform.

4.2.1.2. More on colour spaces

The implementation of colour spaces in libcamera follows that of the Linux V4L2 API quite closely. Specific choices are provided for each of colour primaries, the transfer function, the YCbCr encoding matrix and the quantisation (or range).

In addition, libcamera provides convenient shorthand forms for commonly used colour spaces:

>>> from libcamera import ColorSpace
>>> ColorSpace.Sycc()
<libcamera.ColorSpace 'sYCC'>
>>> ColorSpace.Smpte170m()
<libcamera.ColorSpace 'SMPTE170M'>
>>> ColorSpace.Rec709()
<libcamera.ColorSpace 'Rec709'>

These are in fact the only colour spaces supported by the Pi’s camera system. The required choice can be passed to all the configuration-generating methods using the colour_space keyword parameter:

from picamera2 import Picamera
from libcamera import ColorSpace

picam2 = Picamera2()
preview_config = picam2.create_preview_configuration(colour_space=ColorSpace.Sycc())

When omitted, Picamera2 will choose a default according to the use case:

  • create_preview_configuration and create_still_configuration will use the sYCC colour space by default (by which we mean sRGB primaries and transfer function and full-range BT.601 YCbCr encoding).

  • create_video_configuration will choose sYCC if the main stream is requesting an RGB format. For YUV formats it will choose SMPTE 170M if the resolution is less than 1280x720, otherwise Rec.709.

4.2.1.3. More on the buffer_count

This number defines how many sets of buffers (one for each requested stream) are allocated for the camera system to use. Allocating more buffers can mean that the camera will run more smoothly and drop fewer frames, though the downside is that particularly at high resolutions, there may not be enough memory available.

The configuration-generating methods all choose an appropriate number of buffers for their use cases:

  • create_preview_configuration requests four sets of buffers
  • create_still_configuration requests just one set of buffers (as these are normally large full resolution buffers)
  • create_video_configuration requests six buffers, as the extra work involved in encoding and outputting the video streams makes it more susceptible to jitter or delays, which is alleviated by the longer queue of buffers.

The number of buffers can be overridden in all the configuration-generating methods using the buffer_count keyword parameter:

from picamera2 import Picamera

picam2 = Picamera2()
preview_config = picam2.create_still_configuration(buffer_count=2)

4.2.1.4. More on the queue parameter

By default, Picamera2 keeps hold of the last frame to be received from the camera and, when you make a capture request, this frame may be returned to you. This can be useful for burst captures, particularly when an application is doing some processing that can take slightly longer than a frame period. In these cases, the queued frame can be returned immediately rather than remaining idle until the next camera frame arrives.

But this does mean that the returned frame can come from slightly before the moment of the capture request, by up to a frame period. If this behaviour is not wanted, please set the queue parameter to False. For example:

from picamera2 import Picamera

picam2 = Picamera2()
preview_config = picam2.create_preview_configuration(queue=False)

Note that, when the buffer_count is set to one, as is the case by default for still capture configurations, then no frames are ever queued up (because holding on to the only buffer would completely stall the camera pipeline).

4.2.1.5. More on the sensor parameters

These work in conjunction with some of the stream parameters and are discussed just below.

4.2.1.6. More on the display stream

Normally we would display the main stream in the preview window. In some circumstances it may be preferable to display a lower resolution image (from the lores stream) instead. We could use:

from picamera2 import Picamera

picam2 = Picamera2()
config = picam2.create_still_configuration(lores={"size": (320, 240)}, display="lores")

This would request a full resolution main stream, but then also a QVGA lores stream which would be displayed (recall that the main stream is always defined even when the application does not explicitly request it).

The display parameter may take the value None which means that no images will be rendered to the preview window. In fact this is the default choice of the create_still_configuration method.

4.2.1.7. More on the encode stream

This is similar to the display parameter, in that it names the stream (main or lores) that will be encoded if a video recording is started. By default we would normally encode the main stream, but a user might have an application where they want to record a low resolution video stream instead:

from picamera2 import Picamera

picam2 = Picamera2()
config = picam2.create_video_configuration(main={"size": (2048, 1536)}, lores={"size": (320,
240)}, encode="lores")

This would enable a QVGA stream to be recorded, while allowing 2048x1536 still images to be captured simultaneously.

The encode parameter may also take the value None, which is again the default choice of the create_still_configuration method.

Note

The encode parameter in the configuration is retained principally for backwards compatibility. Recent versions of Picamera2 allow multiple encoders to run at the same time, using either the same or different streams. For this reason, the Picamera2.start_encoder and Picamera2.start_recording methods accept a name parameter to define which stream is being recorded. If this is omitted, the encode stream from the configuration will be used.

4.2.2. Stream configuration parameters

All the three streams can be requested from the configuration-generating methods using the main, lores and raw keyword parameters, though as we have noted, a main stream will always be defined. The main stream is also the first argument to these functions, so the main= syntax can normally be omitted. By default the lores stream is not normally delivered to applications unless it was requested.

To request one of these streams, a dictionary should be supplied. The dictionary may be an empty dictionary, at which point that stream will be generated for the application but populated by default values:

from picamera2 import Picamera2

picam2 = Picamera2()
config = picam2.create_preview_configuration(lores={})

Here, the main stream will be produced as usual, but a lores stream will be produced as well. By default it will have the same resolution as the main stream, but using the YUV420 image format.

The keys that may be specified are:

  • "size" - a tuple of two values giving the width and height of the image
  • "format" - a string defining one of libcamera 's allowable image formats

4.2.2.1. Image Sizes

The configuration-generating functions can make minimal changes to the configuration where they detect something is invalid. But they will attempt to observe whatever values they are given even where others may be more efficient. The most obvious case of this is in relation to the image sizes.

Here, hardware restrictions means that images can be processed more efficiently if they are particular sizes. Other sizes will have to be copied more frequently in the Python world, but these special image alignment rules are somewhat arcane. They are covered in detail in the appendices.

If a user wants to request these optimal image sizes, they should use the align_configuration method. For example:

>>> from picamera2 import Picamera2
>>> picam2 = Picamera2()
>>> config = picam2.create_preview_configuration({"size": (808, 606)})
>>> config["main"]
{'format': 'XBGR8888', 'size': (808, 606)}
>>> picam2.align_configuration(config)
# Picamera2 has decided an 800x606 image will be more efficient.
>>> config["main"]
{'format': 'XBGR8888', 'size': (800, 606)}
>>> picam2.configure(config)
{'format': 'XBGR8888', 'size': (800, 606), 'stride': 3200, 'framesize': 1939200}

At the end we have an 800x606 image, which will result in less data copying. Observe also how, once a configuration has been applied, Picamera2 knows some extra things: the length of each row of the image in bytes (the stride), and the total amount of memory every such image will occupy.

4.2.2.2. Image Formats

A wide variety of image formats are supported by libcamera, as described in the appendices. For our purposes, "however", these are some of the most common ones. For the main stream:

  • XBGR8888 - every pixel is packed into 32-bits, with a dummy 255 value at the end, so a pixel would look like [R, G, B, 255] when captured in Python. (These format descriptions can seem counter-intuitive, but the underlying infrastructure tends to take machine endianness into account, which can mix things up!)
  • XRGB8888 - as above, with a pixel looking like [B, G, R, 255].
  • RGB888 - 24 bits per pixel, ordered [B, G, R].
  • BGR888 - as above, but ordered [R, G, B].
  • YUV420 - YUV images with a plane of Y values followed by a quarter plane of U values and then a quarter plane of V values.

For the lores stream, only 'YUV420' is really used on Pi 4 and earlier devices. On Pi 5, the lores stream may specify an RGB format.

Warning

Picamera2 takes its pixel format naming from libcamera, which in turn takes them from certain underlying Linux components. The results are not always the most intuitive. For example, OpenCV users will typically want each pixel to be a (B, G, R) triple for which the RGB888 format should be chosen, and not BGR888. Similarly, OpenCV users wanting an alpha channel should select XRGB8888.

4.2.2.3. Raw streams and the Sensor Configuration

Image sensors normally have a number of different modes in which they can operate. Modes are distinguished by producing different output resolutions, some of them may give you a different field of view, and there’s often a trade-off where lower resolution sensor modes can produce higher framerates. In many applications, it’s important to understand the available sensor modes, and to be able to choose the one that is the most appropriate.

We can inspect the available sensor modes by querying the sensor_modes property of the Picamera2 object. You should normally do this as soon as you open the camera, because finding out all the reported information will require the camera to be stopped, and will reconfigure it multiple times. For the HQ camera, we obtain:

>>> from pprint import *
>>> from picamera2 import Picamera2
>>> picam2 = Picamera2()
# output omitted
>>> pprint(picam2.sensor_modes)
# output omitted
[{'bit_depth': 10,
'crop_limits': (696, 528, 2664, 1980),
'exposure_limits': (31, 66512892),
'format': SRGGB10_CSI2P,
'fps': 120.05,
'size': (1332, 990),
'unpacked': 'SRGGB10'},
{'bit_depth': 12,
'crop_limits': (0, 440, 4056, 2160),
'exposure_limits': (60, 127156999),
'format': SRGGB12_CSI2P,
'fps': 50.03,
'size': (2028, 1080),
'unpacked': 'SRGGB12'},
{'bit_depth': 12,
'crop_limits': (0, 0, 4056, 3040),
'exposure_limits': (60, 127156999),
'format': SRGGB12_CSI2P,
'fps': 40.01,
'size': (2028, 1520),
'unpacked': 'SRGGB12'},
{'bit_depth': 12,
'crop_limits': (0, 0, 4056, 3040),
'exposure_limits': (114, 239542228),
'format': SRGGB12_CSI2P,
'fps': 10.0,
'size': (4056, 3040),
'unpacked': 'SRGGB12'}]

This gives us the exact sensor modes that we can request, with the following information for each mode:

  • bit_depth - the number of bits in each pixel sample.
  • crop_limits - this tells us the exact field of view of this mode within the full resolution sensor output. In the example above, only the final two modes will give us the full field of view.
  • exposure_limits - the maximum and minimum exposure values (in microseconds) permitted in this mode.
  • format - the packed sensor format. This can be passed as the "format" when requesting the raw stream.
  • fps - the maximum framerate supported by this mode.
  • size - the resolution of the sensor output. This value can be passed as the "size" when requesting the raw stream.
  • unpacked - use this in place of the earlier format in the raw stream request if unpacked raw images are required (see below). We recommend anyone wanting to access the raw pixel data to ask for the unpacked version of the format.

In this example there are three 12-bit modes (one at the full resolution) and one 10-bit mode useful for higher framerate applications (but with a heavily cropped field of view).

Note

For a raw stream, the format normally begins with an S, followed by four characters that indicate the Bayer order of the sensor (the only exception to this is for raw monochrome sensors, which use the single letter R instead). Next is a number, 10 or 12 here, which is the number of bits in each pixel sample sent by the sensor (some sensors may have eight-bit modes too). Finally there may be the characters _CSI2P. This would mean that the pixel data will be packed tightly in memory, so that four ten-bit pixel values will be stored in every five bytes, or two twelve-bit pixel values in every three bytes. When _CSI2P is absent, it means the pixels will each be unpacked into a 16-bit word (or eight-bit pixels into a single byte). This uses more memory but can be useful for applications that want easy access to the raw pixel data. Pi 5 uses a compressed raw format in place of the _CSI2P version, and which we shall learn about later.

The Sensor Configuration

This discussion of the sensor configuration applies only to Raspberry Pi OS Bookworm or later. Bullseye users should configure the sensor by specifying the raw stream format, as shown here, except that the 'sensor' field will not be reported back as part of the applied configuration. Both packed (format ending in _CSI2P) and unpacked formats may be requested. Configuring the raw stream in this way is also supported in Bookworm, for backwards compatibility with Bullseye.

The best way to ask for a particular sensor mode is to set with sensor parameter when requesting a camera configuration. The sensor configuration accepts the following parameters:

  • output_size - the resolution of the sensor mode, which you can find in the size field in the sensor modes list, and
  • bit_depth - the bit depth of each pixel sample from the sensor, also available in the sensor modes list.

All other properties are ignored. So for example, to request the fast framerate sensor mode (the first in the list) we could generate a configuration like this:

mode = picam2.sensor_modes[0]
config = picam2.create_preview_configuration(sensor={'output_size': mode['size'], 'bit_depth':
mode['bit_depth']})
picam2.configure(config)

Whatever output size and bit depth you specify, Picamera2 will try to choose the best match for you. In order to be sure you get the sensor mode that you want, specify the exact size and bit depth for that sensor mode.

The Raw Stream Configuration

On a Pi 4 (or earlier)

The raw stream configuration will always be filled in for you from the sensor configuration where a sensor configuration was given. For example:

>>> modes = picam2.sensor_modes
>>> mode = modes[0]
>>> mode
{'format': SRGGB10_CSI2P, 'unpacked': 'SRGGB10', 'bit_depth': 10, 'size': (1332, 990), 'fps':
120.05, 'crop_limits': (696, 528, 2664, 1980), 'exposure_limits': (31, 2147483647, None)}
>>> config = picam2.create_preview_configuration(sensor={'output_size': mode['size'],
'bit_depth': mode['bit_depth']})
>>> picam2.configure(config)
>>> picam2.camera_configuration()['raw']
{'format': 'SBGGR10_CSI2P', 'size': (1332, 990), 'stride': 1696, 'framesize': 1679040}

Note how the raw stream configuration has been updated to match the sensor configuration even though we never specified it. By default we get packed raw pixels; had we wanted unpacked pixels it would have been sufficient to request an unpacked raw format (even though the size, bit depth and Bayer order may get overwritten):

>>> config = picam2.create_preview_configuration(raw={'format': 'SRGGB12'},
sensor={'output_size': mode['size'], 'bit_depth': mode['bit_depth']})
>>> picam2.configure(config)
>>> picam2.camera_configuration()['raw']
{'format': 'SBGGR10', 'size': (1332, 990), 'stride': 2688, 'framesize': 2661120}

Again, the raw stream has been updated and the request for unpacked pixels respected, even though the bit depth in the raw format has been changed to match the value in the sensor configuration.

For backwards compatibility with earlier versions of Picamera2, when no sensor configuration is given but a raw stream configuration is supplied, Picamera2 will take the size and bit depth from the raw stream in order to select the correct sensor configuration. After Picamera2 is configured, the configuration that you supplied will have the updated sensor configuration filled in for you. For example:

>>> config = picam2.create_preview_configuration(raw={'format': 'SRGGB10', 'size': (1332, 990)})
>>> picam2.configure(config)
>>> picam2.camera_configuration()['sensor']
{'bit_depth': 10, 'output_size': (1332, 990)}

On a Pi 5 (or later)

The situation on a Pi 5 is very similar, so familiarity with the Pi 4 discussion above will be assumed.

  • The sensor configuration is the best way to request a particular sensor mode.

  • When present, the sensor configuration takes precedence.

  • A raw stream with unpacked pixels can be requested by asking for a raw stream with an unpacked format, with everything else coming from the sensor configuration.

  • For backwards compatibility, if no sensor configuration is specified, the raw stream (if present) will be used instead.

However, the Pi 5 is different because it uses different raw stream formats. It supports:

  • Compressed raw pixels. Here, camera samples are compressed to 8 bits per pixel using a visually lossless compression scheme. This format is more memory efficient, but not suitable if an application wants access to the raw pixels (because they would have to be decompressed).

  • Uncompressed pixels. These are stored as one pixel per 16-bit word. It differs from the Pi 4 unpacked scheme in that pixels are left-shifted so that the zero padding bits are at the least significant end (the earlier devices will pad with zeros at the most significant end), and the full 16-bit dynamic range is used. These pixels have never been through the compression scheme, and so give a bit-exact version of what came out of the sensor (allowing for the shift that is applied).

Again for backwards compatibility, Picamera2 will translate a request for packed pixels in the raw stream into a request for compressed pixels on a Pi 5. Requests for unpacked raw stream pixels will be translated into uncompressed (and left-shifted) pixels.

Let’s see this in action.

>>> config = picam2.create_preview_configuration(raw={'format': 'SBGGR10_CSI2P', 'size': (1332,
990)})
>>> picam2.configure(config)
>>> picam2.camera_configuration()['sensor']
{'bit_depth': 10, 'output_size': (1332, 990)}
>>> picam2.camera_configuration()['raw']
{'format': 'BGGR16_PISP_COMP1', 'size': (1332, 990), 'stride': 1344, 'framesize': 1330560}

Here, the 'SBGGR10_CSI2P' format, which would have remained unchanged on a Pi 4, has become 'BGGR16_PISP_COMP1' or "BGGR order 16-bit Pi ISP Compressed 1" format. And the sensor configuration tells us the sensor mode, as it did before.

Looking at the unpacked/uncompressed case:

>>> config = picam2.create_preview_configuration(raw={'format': 'SBGGR10', 'size': (1332, 990)})
>>> picam2.configure(config)
>>> picam2.camera_configuration()['sensor']
{'bit_depth': 10, 'output_size': (1332, 990)}
>>> picam2.camera_configuration()['raw']
{'format': 'SBGGR16', 'size': (1332, 990), 'stride': 2688, 'framesize': 2661120}

The sensor configuration tells us the sensor mode as it did in the previous example, but the 'SBGGR10' format that would have remained unchanged on a Pi 4 has become 'SBGGR16' (BGGR order 16-bits per pixel, full 16-bit dynamic range). It should be clear that, in order to determine the bit depth that the sensor is operating in, an application needs to check the sensor configuration , and that this method works on all Pi devices. Checking the raw stream format for this value will not work on a Pi 5 or later device.

Tip

After configuring the camera, it’s often helpful to inspect picam2.camera_configuration() to check what you actually got!

4.2.3. Configurations and runtime camera controls

The final element in a Picamera2 configuration is runtime camera controls. As the description suggests, we normally apply these at runtime, and they are not really part of the camera configuration. However, there are some occasions when it can be helpful to associate them directly with a "configuration".

One such example is in video recording. Normally the camera can run at a variety of framerates, and this can be changed by an application while the camera is running. When recording a video, however, people commonly prefer to record at a fixed 30 frames per second, even if the camera was set to something else previously.

The configuration-generating methods therefore supply some recommended runtime control values corresponding to the use case. These can be overridden or changed, but as the the optimal or usual values are sometimes a bit technical, it’s helpful to supply them automatically:

>>> from picamera2 import Picamera2
>>> picam2 = Picamera2()
>>> picam2.create_video_configuration()["controls"]
{'NoiseReductionMode': <NoiseReductionMode.Fast: 1>, 'FrameDurationLimits': (33333, 33333)}

We see here that for a video use-case, we’re recommended to set the NoiseReductionMode to Fast (because when encoding video it’s important to get the frames quickly), and the FrameDurationLimits are set to (33333, 33333). This means that every camera frame may not take less than the first value (33333 microseconds), and may not take longer than the second (also 33333 microseconds). Therefore the framerate is fixed to 30 frames per second.

New control values, or ones to override the default ones, can be specified with the controls keyword parameter. If you wanted a 25 frames-per-second video you could use:

from picamera2 import Picamera2

picam2 = Picamera2()
config = picam2.create_video_configuration(controls={"FrameDurationLimits": (40000, 40000)})

When controls have been set into the returned configuration object, we can think of them as being part of that configuration. If we hold on to the configuration object and apply it again later, then those control values will be restored.

We are of course always free to set runtime controls later using the Picamera2.set_controls method, but these will not become part of any configuration that we can recover later.

For a full list of all the available runtime camera controls, please refer to the relevant appendix.

4.3. Configuration objects

We have seen numerous examples of creating camera configurations using the create_xxx_configuration methods which return regular Python dictionaries. Some users may prefer the "object" style of syntax, so we provide this as an alternative. Neither style is particularly recommended over the other.

Camera configurations can be represented by the CameraConfiguration class. This class contains the exact same things we have seen previously, namely:

  • the buffer_count
  • a Transform object
  • a ColorSpace object
  • the name of the stream to display (if any)
  • the name of the stream to encode (if any)
  • a controls object which represents camera controls through a Controls class
  • a SensorConfiguration object for setting the sensor mode
  • a main stream
  • and optionally a lores and/or a raw stream

When a Picamera2 object is created, it contains three embedded configurations, in the following fields:

  • preview_configuration - the same configuration as is returned by the create_preview_configuration method
  • still_configuration - the same configuration as is returned by the create_still_configuration method
  • video_configuration - the same configuration as is returned by the create_video_configuration method

Finally, CameraConfiguration objects can also be passed to the configure method. Alternatively, the strings "preview", "still" and "video" may be passed as shorthand for the three embedded configurations listed above.

We conclude with some examples.

Changing the size of the main stream

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.preview_configuration.main.size = (800, 600)
picam2.configure("preview")

Here, the configuration is equivalent to that generated by picam2.create_preview_configuration({"size": (800, 600)}). As another piece of shorthand, the main field can be elided, so picam2.preview_configuration.size = (800, 600) would have been identical.

Configuring a lores or raw stream

Before setting the size or format of the optional streams, they must first be enabled with:

configuration_object.enable_lores()

or:

configuration_object.enable_raw()

as appropriate. This would normally be advised after the main stream size has been set up so that they can pick up more appropriate defaults. After that, the size and the format fields can be set in the usual way:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.preview_configuration.enable_lores()
picam2.preview_configuration.lores.size = (320, 240)
picam2.configure("preview")

Setting the format field is optional as defaults will be chosen - "YUV420" for the lores stream. In the case of the raw stream format, this can be left at its default value (None) and the system will use the sensor’s native format.

Sensor configuration and the raw stream

The sensor configuration and raw stream behave in the same way as they do when using dictionaries instead of configuration objects. This means that:

  • When the sensor object in the camera configuration contains proper values, they will take precedence over any in the raw stream.
  • When the sensor object does not contain meaningful values, any values in the raw stream will be used to decide the sensor mode.
  • After configuration, the sensor field is updated to reflect the sensor mode being used, whether or not it was configured previously.

The SensorConfiguration object allows the application to set the output_size and bit_depth, just as was the case with dictionaries. For example on a Pi 4:

>>> picam2 = Picamera2()
>>> picam2.preview_configuration.sensor.output_size = (1332, 990)
>>> picam2.preview_configuration.sensor.bit_depth = 10
>>> picam2.configure('preview')
>>> picam2.preview_configuration.raw
StreamConfiguration({'size': (1332, 990), 'format': 'SBGGR10_CSI2P', 'stride': 1696,
'framesize': 1679040})

Here, the raw stream has been configured from the programmed sensor configuration. But if we don’t fill in the sensor configuration, it will be deduced from the raw stream (for backwards compatibility).

>>> picam2 = Picamera2()
>>> picam2.preview_configuration.raw.size = (1332, 990)
>>> picam2.preview_configuration.raw.format = 'SBGGR10'
>>> picam2.configure('preview')
>>> picam2.preview_configuration.sensor
SensorConfiguration({'output_size': (1332, 990), 'bit_depth': 10})

Warning

This means that, because these configuration objects are persistent, after doing one configuration you can no longer update just the raw stream and expect a different sensor mode to be chosen. The application should either update the sensor configuration, or, if it wants to deduce it from the raw stream again, the sensor configuration should be cleared: picam2.preview_configuration.sensor = None

Stream alignment

Just as with the dictionary method, stream sizes are not forced to optimal alignment by default. This can easily be accomplished using the configuration object’s align method:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.preview_configuration.main.size = (808, 600)
picam2.preview_configuration.main.format = "YUV420"
picam2.preview_configuration.align()
picam2.configure("preview")

Supplying control values

We saw earlier how control values can be associated with a configuration. Using the object style of configuration, the equivalent to that example would be:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.video_configuration.controls.FrameDurationLimits = (40000, 40000)
picam2.configure("video")

For convenience, the "controls" object lets you set the FrameRate instead of the FrameDurationLimits in case this is easier.

You can give it either a range (as FrameDurationLimits) or a single value, where framerate = 1000000 / frameduration, and frameduration is given in microseconds (as we did above):

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.video_configuration.controls.FrameRate = 25.0
picam2.configure("video")

We discuss setting control values at runtime in a subsequent section.

4.4. Configuring a USB Camera

Picamera2 has limited supported for USB cameras such as webcams. You can connect several USB cameras and CSI2 cameras (the latter to a Pi’s dedicated camera ports) at the same time, so long as the necessary ports and connectors are available. For more information, please refer to the section on multiple cameras.

You can create the Picamera2 object in the usual way, but only the main stream will be available. The supported formats will depend on the camera, but Picamera2 can in principle deal with both MJPEG and YUYV cameras, and where the camera supports both you can select by requesting the format "MJPEG" or "YUYV".

USB cameras can only use the software-rendered Qt preview window (Preview.QT). None of the hardware assisted rendering is supported. MJPEG streams can be rendered directly, but YUYV would require OpenCV to be installed in order to convert the image into a format that Qt understands. Both cases will use a significant extra amount of CPU.

The capture_buffer method will give you the raw camera data for each frame (a JPEG bitstream from an MJPEG camera, or an uncompressed YUYV image from a YUYV camera). A simple example:

from picamera2 import Picamera2, Preview

picam2 = Picamera2()
config = picam2.create_preview_configuration({"format": "MJPEG"})
picam2.configure(config)

picam2.start_preview(Preview.QT)
picam2.start()

jpeg_buffer = picam2.capture_buffer()

If you have multiple cameras and need to discover which camera to open, please use the Picamera2.global_camera_info method.

In general, users should assume that other features, such as video recording, camera controls that are supported on Raspberry Pi cameras, and so forth, are not available. Hot-plugging of USB cameras is also not supported - Picamera2 should be completely shut down and restarted when cameras are added or removed.

4.5. Further examples

Most of the GitHub examples will configure the camera, for example:

capture_dng_and_jpeg.py - shows how you can configure a still capture for a full resolution main stream and also obtain the raw image buffer.

#!/usr/bin/python3

# Capture a DNG and a JPEG made from the same raw data.

import time

from picamera2 import Picamera2, Preview

picam2 = Picamera2()
picam2.start_preview(Preview.QTGL)

preview_config = picam2.create_preview_configuration()
capture_config = picam2.create_still_configuration(raw={}, display=None)
picam2.configure(preview_config)

picam2.start()
time.sleep(2)

r = picam2.switch_mode_capture_request_and_stop(capture_config)
r.save("main", "full.jpg")
r.save_dng("full.dng")

capture_motion.py - shows how you can capture both a main and a lores stream.

#!/usr/bin/python3

import time

import numpy as np

from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import FileOutput

lsize = (320, 240)
picam2 = Picamera2()
video_config = picam2.create_video_configuration(main={"size": (1280, 720), "format": "RGB888"},
                                                 lores={"size": lsize, "format": "YUV420"})
picam2.configure(video_config)
encoder = H264Encoder(1000000)
picam2.start()

w, h = lsize
prev = None
encoding = False
ltime = 0

while True:
    cur = picam2.capture_buffer("lores")
    cur = cur[:w * h].reshape(h, w)
    if prev is not None:
        # Measure pixels differences between current and
        # previous frame
        mse = np.square(np.subtract(cur, prev)).mean()
        if mse > 7:
            if not encoding:
                encoder.output = FileOutput(f"{int(time.time())}.h264")
                picam2.start_encoder(encoder)
                encoding = True
                print("New Motion", mse)
            ltime = time.time()
        else:
            if encoding and time.time() - ltime > 2.0:
                picam2.stop_encoder()
                encoding = False
    prev = cur

rotation.py - shows a 180 degree rotation being applied to the camera images. In this example the rotation is applied after the configuration has been generated, though we could have passed the transform in to the create_preview_configuration function with transform=Transform(hflip=1, vflip=1) too.

#!/usr/bin/python3

# Run the camera with a 180 degree rotation.
import time

import libcamera

from picamera2 import Picamera2, Preview

picam2 = Picamera2()
picam2.start_preview(Preview.QTGL)

preview_config = picam2.create_preview_configuration()
preview_config["transform"] = libcamera.Transform(hflip=1, vflip=1)
picam2.configure(preview_config)

picam2.start()
time.sleep(5)

still_capture_with_config.py - shows how to configure a still capture using the configuration object method. In example we also request a raw stream.

#!/usr/bin/python3

# Use the configuration structure method to do a full res capture.

import time

from picamera2 import Picamera2

picam2 = Picamera2()

# We don't really need to change anyhting, but let's mess around just as a test.
picam2.preview_configuration.size = (800, 600)
picam2.preview_configuration.format = "YUV420"
picam2.still_configuration.size = (1600, 1200)
picam2.still_configuration.enable_raw()
picam2.still_configuration.raw.size = picam2.sensor_resolution

picam2.start("preview", show_preview=True)
time.sleep(2)

picam2.switch_mode_and_capture_file("still", "test_full.jpg")

Chapter 5. Camera controls and properties

5.1. Camera controls

Camera controls represent parameters that the camera exposes, and which we can alter to change the nature of the images it outputs in various ways. In Picamera2, all camera controls can be changed at runtime. Anything that cannot be changed at runtime is regarded not as a control but as configuration. We do, however, allow the camera’s configuration to include controls in case there are particular standard control values that could be conveniently applied along with the rest of the configuration.

For example, some obvious controls that we might want to set while the camera is delivering images are:

  • Exposure time
  • Gain
  • White balance
  • Colour saturation
  • Sharpness

... and there are many more. A complete list of all the available camera controls can be found in the appendices, and also by inspecting the camera_controls property of the Picamera2 object:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.camera_controls

This returns a dictionary with the control names as keys, and each value being a tuple of (min, max, default) values for that control. The default value should be interpreted with some caution as in many cases libcamera’s default value will be overwritten by the camera tuning as soon as the camera is started.

Things that are not controls include:

  • The image resolution
  • The format of the pixels

These can only be set up when the camera is configured.

One example of a control that might be associated with a configuration is the camera’s framerate (or equivalently, the frame duration). Normally we might let a camera operate at whatever framerate is appropriate to the exposure time requested. For video recording, however, it’s quite common to fix the framerate to (for example) 30 frames per second, and so this might be included by default along with the rest of the video configuration.

5.1.1. How to set camera controls

There are three distinct times when camera controls can be set:

  1. Into the camera configuration. These will be stored with the configuration so that they will be re-applied whenever that configuration is requested. They will be enacted before the camera starts.
  2. After configuration but before the camera starts. The controls will again take effect before the camera starts, but will not be stored with the configuration and so would not be re-applied again automatically.
  3. After the camera has started. The camera system will apply the controls as soon as it can, but typically there will be some number of frames of delay.

Camera controls can be set by passing a dictionary of updates to the set_controls method, but there is also an object-style syntax for accomplishing the same thing.

5.1.1.1. Setting controls as part of the configuration

We have seen an example of this when discussing camera configurations. One important feature of this is that the controls are applied before the camera even starts, meaning the very first camera frame will have the controls set as requested.

5.1.1.2. Setting controls before the camera starts

This time we set the controls after configuring the camera, but before starting it. For example:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration())
picam2.set_controls({"ExposureTime": 10000, "AnalogueGain": 1.0})
picam2.start()

Here too the controls will already have been applied on the very first frame that we receive from the camera.

5.1.1.3. Setting controls after the camera has started

This time, there will be a delay of several frames before the controls take effect. This is because there is perhaps quite a large number of requests for camera frames already in flight, and for some controls (exposure time and analogue gain specifically), the camera may actually take several frames to apply the updates.

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration())
picam2.start()
picam2.set_controls({"ExposureTime": 10000, "AnalogueGain": 1.0})

In this case we cannot rely on any specific frame having the value we want, so would have to check the frame’s metadata.

5.1.2. Object syntax for camera controls

We saw previously how control values can be associated with a particular camera configuration.

There is also an embedded instance of the Controls class inside the Picamera2 object that allows controls to be set subsequently. For example, to set controls after configuration but before starting the camera:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure("preview")
picam2.controls.ExposureTime = 10000
picam2.controls.AnalogueGain = 1.0
picam2.start()

To set these controls after the camera has started we should use:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure("preview")
picam2.start()
with picam2.controls as controls:
    controls.ExposureTime = 10000
    controls.AnalogueGain = 1.0

In this final case we note the use of the with construct. Although you would normally get by without it (just set the picam2.controls directly), that would not absolutely guarantee that both controls would be applied on the same frame. You could technically find the analogue gain being set on the frame after the exposure time.

In all cases, the same rules apply as to whether the controls take effect immediately or incur several frames of delay.

5.2. Autofocus Controls

Autofocus controls obey the same general rules as all other controls, however some guidance will be necessary before they can be used effectively. These controls should work correctly so long as the version of libcamera being used (such as that supplied by Raspberry Pi) implements libcamera's published autofocus API correctly, and the attached camera supports autofocus.

Note

Camera modules that do not support autofocus (including earlier Raspberry Pi camera modules and the HQ camera) will not advertise these options as being available (in the Picamera2.camera_controls property), and attempting to set them will fail.

5.2.1. Autofocus Modes and State

The autofocus (AF) state machine has three modes, and its activity in each of these modes can be monitored by reading the AfState metadata that is returned with each image. The three modes are:

  • Manual - The lens will never move spontaneously, but the LensPosition control can be used to move the lens "manually". The units for this control are dioptres (1 / distance in metres), so that zero can be used to denote "infinity". The LensPosition can be monitored in the image metadata too, and will indicate when the lens has reached the requested location.
  • Auto - In this mode the AfTrigger control can be used to start an autofocus cycle. The AfState metadata that is received with images can be inspected to determine when this finishes and whether it was successful, though we recommend the use of helper functions that save the user from having to implement this. In this mode too, the lens will never move spontaneously until it is "triggered" by the application.
  • Continuous - The autofocus algorithm will run continuously, and refocus spontaneously when necessary.

Applications are free to switch between these modes as required.

5.2.2. Continuous Autofocus

For example, to put the camera into continuous autofocus mode:

from picamera2 import Picamera2
from libcamera import controls

picam2 = Picamera2()
picam2.start(show_preview=True)
picam2.set_controls({"AfMode": controls.AfModeEnum.Continuous})

5.2.3. Setting the Lens Position Manually

To put the camera into manual mode and set the lens position to infinity:

from picamera2 import Picamera2
from libcamera import controls

picam2 = Picamera2()
picam2.start(show_preview=True)
picam2.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": 0.0})

The lens position control (use picam2.camera_controls["LensPosition"]) gives three values which are the minimum, maximum and default lens positions. The minimum value defines the furthest focal distance, and the maximum specifies the closest achievable focal distance (by taking its reciprocal). The third value gives a "default" value, which is normally the hyperfocal position of the lens.

The minimum value for the lens position is most commonly 0.0 (meaning infinity). For the maximum, a value of 10.0 would indicate that the closest focal distance is 1 / 10 metres, or 10cm. Default values might often be around 0.5 to 1.0, implying a hyperfocal distance of approximately 1 to 2m.

In general, users should expect the distance calibrations to be approximate as it will depend on the accuracy of the tuning and the degree of variation between the user’s module and the module for which the calibration was performed.

5.2.4. Triggering an Autofocus Cycle

For triggering an autofocus cycle in Auto mode, we recommend using a helper function that monitors the autofocus algorithm state for you, handles any complexities in the state transitions, and returns when the AF cycle is complete:

from picamera2 import Picamera2
from libcamera import controls

picam2 = Picamera2()
picam2.start(show_preview=True)
success = picam2.autofocus_cycle()

The function returns True if the lens focused successfully, otherwise False. Should an application wish to avoid blocking while the autofocus cycle runs, we recommend replacing the final line (success = picam2.autofocus_cycle()) with:

job = picam2.autofocus_cycle(wait=False)

# Now do some other things, and when you finally want to be sure the autofocus
# cycle is finished:
success = picam2.wait(job)

This is in fact the normal method for running requests asynchronously - please see the section on asynchronous capture for more details.

5.2.5. Other Autofocus Controls

The other standard libcamera autofocus controls are also supported, including:

  • AfRange - adjust the focal distances that the algorithm searches
  • AfSpeed - try to run the autofocus algorithm faster or slower
  • AfMetering and AfWindows - lets the user change the area of the image used for focus

To find out more about these controls, please consult the appendices or the libcamera documentation and search for Af.

Finally, there is also a Qt application that demonstrates the use of the autofocus API.

5.3. Camera properties

Camera properties represent information about the sensor that applications may want to know. They cannot be changed by the application at any time, neither at runtime nor while configuring the camera, although the value of these properties may change whenever the camera is configured.

Camera properties may be inspected through the camera_properties property of the Picamera2 object:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.camera_properties

Some examples of camera properties include the model of sensor and the size of the pixel array. After configuring the camera into a particular mode it will also report the field of view from the pixel array that the mode represents, and the sensitivity of this mode relative to other camera modes.

A complete list and explanation of each property can be found in the appendices.

5.4. Further examples

The following examples demonstrate setting controls:

  • controls.py - shows how to set controls while the camera is running. In this example we query the ExposureTime, AnalogueGain and ColourGains and then fix them so that they can no longer vary.
  • opencv_mertens_merge.py - demonstrates how to stop and restart the camera with multiple new exposure values.
  • zoom.py - shows how to implement a smooth digital zoom. We use capture_metadata to synchronise the control value updates with frames coming from the camera.
  • controls_3.py - illustrates the use of the Controls class, rather than dictionaries, to update control values.

Chapter 6. Capturing images and requests

Once the camera has been configured and started, Picamera2 automatically starts submitting requests to the camera system. Each request will be completed and returned to Picamera2 once the camera system has filled in an image buffer for each of the streams that were configured.

The process of requests being submitted, returned and then sent back to the camera system is transparent to the user. In fact, the process of sending the images for display (when a preview window has been set up) or forwarding them to a video encoder (when one is running) is all entirely automatic too, and the application does not have to do anything. The user application only needs to say when it wants to receive any of these images for its own use, and Picamera2 will deliver them. The application can request a single image belonging to any of the streams, or it can ask for the entire request, giving access to all the images and the associated metadata.

Note

In this section we make use of some convenient default behaviour of the start function. If the camera is completely unconfigured, it will apply the usual default preview configuration before starting the camera.

6.1. Capturing images

Camera images are normally represented as numpy arrays, so some familiarity with numpy will be helpful. This is also the representation used by OpenCV so that Picamera2, numpy and OpenCV all work together seamlessly.

When capturing images, the Picamera2 functions use the following nomenclature:

  • arrays — two-dimensional arrays of pixels, usually the most convenient way to manipulate images. They are often three-dimensional numpy arrays because every pixel has several colour components, adding another dimension.
  • images — Python Image Library (PIL) images, useful when interfacing to other modules that expect this format.
  • buffers — the entire block of memory where the image is stored as a one-dimensional numpy array; the two- or three-dimensional array form is generally more useful.

There are also capture functions for saving images directly to files, and for switching between camera modes so as to combine fast framerate previews with high resolution captures.

6.1.1. Capturing arrays

The capture_array function will capture the next camera image from the stream named as its first argument (and which defaults to "main" if omitted). The following example starts the camera with a typical preview configuration, waits for one second (during which time the camera is running), and then captures a three-dimensional numpy array:

from picamera2 import Picamera2
import time

picam2 = Picamera2()
picam2.start()

time.sleep(1)

array = picam2.capture_array("main")

Although we regard this as a two-dimensional image, numpy will often report a third dimension of size three or four depending on whether every pixel is represented by three channels (RGB) or four channels (RGBA, that is RGB with an alpha channel). Remember also that numpy lists the height as the first dimension.

  • shape will report (height, width, 3) for three-channel RGB type formats.
  • shape will report (height, width, 4) for four-channel RGBA (alpha) type formats.
  • shape will report (height × 3 / 2, width) for YUV420 formats.

YUV420 is a slightly special case because the first height rows give the Y channel, the next height/4 rows contain the U channel and the final height/4 rows contain the V channel. For the other formats, where there is an alpha value it will take the fixed value 255.

6.1.2. Capturing PIL images

PIL images are captured identically, but using the capture_image function.

from picamera2 import Picamera2
import time

picam2 = Picamera2()
picam2.start()

time.sleep(1)
image = picam2.capture_image("main")

6.1.3. Switching camera mode and capturing

A common use case is to run the camera in a mode where it can achieve a fast framerate for display in a preview window, but can also switch to a (slower framerate) high-resolution capture mode for the best quality images. There are special switch_mode_and_capture_array and switch_mode_and_capture_image functions for this.

from picamera2 import Picamera2
import time

picam2 = Picamera2()
capture_config = picam2.create_still_configuration()
picam2.start(show_preview=True)

time.sleep(1)
array = picam2.switch_mode_and_capture_array(capture_config, "main")

This will switch to the high resolution capture mode and return the numpy array, and will then switch automatically back to the preview mode without any user intervention.

We note that the process of switching camera modes can be performed manually, if preferred:

from picamera2 import Picamera2
import time

picam2 = Picamera2()
preview_config = picam2.create_preview_configuration()
capture_config = picam2.create_still_configuration()
picam2.configure(preview_config)
picam2.start(show_preview=True)

time.sleep(1)
picam2.switch_mode(capture_config)
array = picam2.capture_array("main")
picam2.switch_mode(preview_config)

Temporal denoise and Pi 5

From Pi 5 onwards, temporal denoise is supported. Therefore it can sometimes be beneficial to allow several frames to go by after a mode switch so that temporal denoise can start to operate. This is enabled by the switch_mode_and_capture family of methods with a delay parameter, which indicates how many frames to skip before actually capturing (and switching back).

In the earlier example, we would simply replace:

array = picam2.switch_mode_and_capture_array(capture_config, "main")

with (for example):

array = picam2.switch_mode_and_capture_array(capture_config, "main", delay=10)

This is entirely optional in most circumstances, though we do recommend it for Pi 5 HDR captures.

On Pi 4 or earlier devices, there is no benefit in setting the delay parameter.

6.1.4. Capturing straight to files and file-like objects

For convenience, the capture functions all have counterparts that save an image straight to file:

from picamera2 import Picamera2
import time

picam2 = Picamera2()
capture_config = picam2.create_still_configuration()
picam2.start(show_preview=True)

time.sleep(1)
picam2.switch_mode_and_capture_file(capture_config, "image.jpg")

The file format is deduced automatically from the filename. Picamera2 uses PIL to save the images, and so supports JPEG, BMP, PNG and GIF files.

Applications can also capture to file-like objects. A typical example would be memory buffers from Python’s io library. In this case there is no filename so the format of the “file” must be given by the format parameter:

from picamera2 import Picamera2
import io
import time

picam2 = Picamera2()
picam2.start()

time.sleep(1)
data = io.BytesIO()
picam2.capture_file(data, format="jpeg")

The format parameter may take the values "jpeg", "png", "bmp" or "gif".

Setting the image quality

The image quality can be set globally in the Picamera2 object’s options field (though it can be changed while Picamera2 is running).

Option name     Default value   Description
quality         90              JPEG quality level, where 0 is the worst quality and 95 is best.
compress_level  1               PNG compression level, where 0 gives no compression, 1 is the fastest that actually does any compression, and 9 is the slowest.

Please refer to the Python PIL module for more information.

For example, you can change these default quality parameters as follows:

from picamera2 import Picamera2
import time

picam2 = Picamera2()
picam2.options["quality"] = 95
picam2.options["compress_level"] = 2
picam2.start()

time.sleep(1)
picam2.capture_file("test.jpg")
picam2.capture_file("test.png")

6.2. Capturing metadata

Every image that is output by the camera system comes with metadata that describes the conditions of the image capture. Specifically, the metadata describes the camera controls that were used to capture that image, including, for example, the exposure time and analogue gain of the image.

Metadata is returned as a Python dictionary and is easily captured by:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.start()

metadata = picam2.capture_metadata()
print(metadata["ExposureTime"], metadata["AnalogueGain"])

Capturing metadata is a good way to synchronise an application with camera frames (if you have no actual need of the frames). The first call to capture_metadata (or indeed any capture function) will often return immediately because Picamera2 usually holds on to the last camera image internally. But after that, every capture call will wait for a new frame to arrive (unless the application has waited so long to make the request that the image is once again already there). For example:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.start()

for i in range(30):
    metadata = picam2.capture_metadata()
    print("Frame", i, "has arrived")

The process of obtaining the metadata that belongs to a specific image is explained through the use of requests.

Object-style access to metadata

For those who prefer the object-style syntax over Python dictionaries, the metadata can be wrapped in the Metadata class:

from picamera2 import Picamera2, Metadata

picam2 = Picamera2()
picam2.start()

metadata = Metadata(picam2.capture_metadata())
print(metadata.ExposureTime, metadata.AnalogueGain)

6.3. Capturing multiple images at once

A good way to do this is by capturing an entire request, but sometimes it is convenient to obtain copies of multiple numpy arrays in much the same way as we were able to capture a single one.

For this reason some of the functions we saw earlier have “pluralised” versions. The table below lists them explicitly:

Capture single array Capture multiple arrays
Picamera2.capture_buffer Picamera2.capture_buffers
Picamera2.switch_mode_and_capture_buffer Picamera2.switch_mode_and_capture_buffers
Picamera2.capture_array Picamera2.capture_arrays
Picamera2.switch_mode_and_capture_array Picamera2.switch_mode_and_capture_arrays

All these functions work in the same way as their single-capture counterparts except that:

  • Instead of providing a single stream name, a list of stream names must be provided.
  • The return value is a tuple of two values, the first being the list of arrays (in the order the names were given), and the second being the image metadata.

For example:

from picamera2 import Picamera2

picam2 = Picamera2()
config = picam2.create_preview_configuration(lores={})
picam2.configure(config)
picam2.start()

(main, lores), metadata = picam2.capture_arrays(["main", "lores"])

In this case we configure both a main and a lores stream. We then ask to capture an image from each and these are returned to us along with the metadata for that single capture from the camera.

To facilitate using these images, Picamera2 has a small helpers library that can convert arrays to PIL images, save them to a file, and so on. The table below lists the available helpers:

Helper Description
picam2.helpers.make_array Make a 2D (or 3D, for multiple colour channels) array from a flat 1D array (as returned by, for example, capture_buffer).
picam2.helpers.make_image Make a PIL image from a flat 1D array (as returned by, for example, capture_buffer).
picam2.helpers.save Save a PIL image to file.
picam2.helpers.save_dng Save a PIL image to a DNG file.

These helpers can be accessed directly from the Picamera2 object. If we wanted to capture a single buffer and use one of these helpers to save the file, we could use:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration())
picam2.start()

(buffer,), metadata = picam2.capture_buffers(["main"])
img = picam2.helpers.make_image(buffer, picam2.camera_configuration()["main"])
picam2.helpers.save(img, metadata, "file.jpg")

Further examples are highlighted at the end of this chapter.

6.4. Capturing requests

Besides capturing images from just one of the configured streams, or the image metadata, Picamera2 makes it possible to capture an entire request. This includes the image from every stream that has been configured, and also the metadata, so that they can be used together.

Normally, when we capture arrays or images, the image data is copied so that the camera system can keep hold of all the memory buffers it was using and carry on running in just the same manner. When we capture a request, however, we have only borrowed it and all the memory buffers from the camera system, and nothing has yet been copied. When we are finished with the request it must be returned back to the camera system using the request’s release method.

Important

If an application fails to release captured requests back to the camera system, the camera system will gradually run out of buffers. It is likely to start dropping ever more camera frames, and eventually the camera system will stall completely.

Here is the basic use pattern:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.start()

request = picam2.capture_request()
request.save("main", "image.jpg")
print(request.get_metadata())  # this is the metadata for this image
request.release()

As soon as we have finished with the request, it is released back to the camera system. This example also shows how we are able to obtain the exact metadata for the captured image using the request’s get_metadata function.

Notice how we saved the image from the main stream to the file. All Picamera2 capture methods are implemented through methods in the CompletedRequest class, and once we have the request we can call them directly. The correspondence is illustrated in the table below.

Picamera2 function CompletedRequest equivalent
Picamera2.capture_file CompletedRequest.save (or CompletedRequest.save_dng for raw files)
Picamera2.capture_buffer CompletedRequest.make_buffer
Picamera2.capture_array CompletedRequest.make_array
Picamera2.capture_image CompletedRequest.make_image

From Picamera2 version 0.3.19 onwards, you can capture requests with a context manager using the captured_request() method. This releases the request back to the camera system automatically:

with picam2.captured_request() as request:
    # Do something with the request.
    print(request)
# The request is released automatically when we leave the scope of the "with".

6.4.1. Capturing requests at specific times

Sometimes there is a need to capture an image after some event has happened, perhaps a light being turned on or off. In these cases it’s important to know that no part of the image started being exposed before the event in question happened.

When we capture a request we can access its metadata, which gives us a SensorTimestamp. This is the time, measured in nanoseconds from when the system booted, that the first pixel was read out of the sensor. We therefore subtract the image exposure time to find out when the first pixel started being exposed.

The Picamera2.capture_request method makes this easy for us. By setting the flush parameter to True we can invoke exactly this behaviour—that the first pixel started being exposed no earlier than the moment we call the function. Alternatively, we can pass an explicit timestamp in nanoseconds if we have a slightly different instant in time in mind.

request = picam2.capture_request(flush=True)

is equivalent to:

import time

request = picam2.capture_request(flush=time.monotonic_ns())

6.4.2. Moving processing out of the camera thread

Normally when we use a function like Picamera2.capture_file, the processing to capture the image, compress it as (for example) a JPEG, and save it to file happens in the usual camera processing loop. While this happens, the handling of camera events is blocked and the camera system is likely to drop some frames. In many cases this is not terribly important, but there are occasions when we might prefer all the processing to happen somewhere else.

Just as an example, if we were recording a video and wanted to capture a JPEG simultaneously whilst minimising the risk of dropping any video frames, then it would be beneficial to move that processing out of the camera loop.

This is easily accomplished simply by capturing a request and calling request.save as we saw above. Camera events can still be handled in parallel (though this is somewhat at the mercy of Python’s multi-tasking abilities), and the only downside is that the camera system has to make do with one less set of buffers until that request is finally released. However, this can in turn always be mitigated by allocating one or more extra sets of buffers via the camera configuration’s buffer_count parameter.

6.4.3. Capturing synchronised still images with multiple cameras

Raspberry Pi’s libcamera implementation contains the ability to synchronise multiple cameras together using software. To summarise how this feature works:

  • One camera is a server , and broadcasts timing information onto the network. (The network address is specified in the camera tuning file; all synchronised devices must share the same value. You can have multiple servers on a network, so long as they are using different addresses.)

  • Other cameras are clients and listen out for these packets, whereupon they adjust their framerates so that, as far as possible, their frame start times match those of the server. They should all be configured to run at the same nominal fixed framerate (such as 30fps).

  • This fixed framerate should be significantly less than the maximum framerate that the clients can achieve. This is so that they can be "sped up" from time to time to catch up with the server.

  • Often there is just one client, but there can be an arbitrary number.

  • Often a client will be another camera attached to the same Pi as the server, though they can also be on different devices. In such cases, the accuracy of the synchronisation will depend on the closeness of the their respective clocks. Clocks would normally be synchronised independently of the camera system, for example using NTP or PTP.

  • The camera models do not need to be the same, though different cameras may not be able to run at exactly the same framerate, so synchronisation may not be as good.

  • All devices, server and clients, are notified through image metadata (the "SyncReady" item) when the mutually agreed moment when everything is synchronised, occurs.

  • To make a device act as the server, set the "SyncMode" control to libcamera.controls.rpi.SyncModeEnum.Server. For clients, use libcamera.controls.rpi.SyncModeEnum.Client instead.

  • On the server, you can adjust the number of frames the server will run before everything is "synchronised" by setting the "SyncFrames" control. Note that there is no back-channel from clients to the server, so clients must be started "in good time" (and we would normally recommend starting clients first as they will just wait for the server to appear).

  • Cameras should be configured with a buffer_count high enough to avoid dropping frames. In practice this will mean at least 3, but possibly more. Synchronisation still largely works when frames are being dropped, but obviously it will become difficult to match client and server frames up when some are missing (even though the frame timestamps will help here).

So to capture synchronised still images, we must start both a client and a server, and then wait until they indicate that they are synchronised. We can do this conveniently using capture_sync_request(), which waits for the first camera image where devices are synchronised, and returns it to the caller.

from libcamera import controls
from picamera2 import Picamera2

picam2 = Picamera2()
ctrls = {'FrameRate': 30.0, 'SyncMode': controls.rpi.SyncModeEnum.Server}
# preview configurations get 4 buffers by default
config = picam2.create_preview_configuration(controls=ctrls)

picam2.start(config)
req = picam2.capture_sync_request()
print("Sync error:", req.get_metadata()['SyncTimer'])

On the clients, you will need to replace controls.rpi.SyncModeEnum.Server by controls.rpi.SyncModeEnum.Client but otherwise the script is identical. Note the "SyncTimer" metadata, which (when present) informs applications how long there is to go (in microseconds) until the agreed sychronisation point. On the frame captured at that precise moment, therefore, it provides an estimate of the error between the frame start times of client and server (as shown in the example).

6.5. Asynchronous capture

Sometimes it’s convenient to be able to ask Picamera2 to capture an image (for example), but not to block your thread while this takes place. Among the advanced use cases later on we’ll see some particular examples of this, although it’s a feature that might be helpful in other circumstances too.

All the capture and switch_mode_and_capture methods take two additional arguments:

  • wait — whether to block while the operation completes.
  • signal_function — a function that will be called when the operation completes.

Both take the default value None, but can be changed resulting in the following behaviour:

  • If wait and signal_function are both None, then the function will block until the operation is complete. This is the “usual” behaviour.
  • If wait is None but a signal_function is supplied, then the function will not block, but returns immediately even though the operation is not complete. The caller should use the supplied signal_function to notify the application when the operation is complete.
  • If a signal_function is supplied and wait is not None, then the given value of wait determines whether the function blocks (it blocks if wait is true). The signal_function is still called, however.
  • You can also set wait to False and not supply a signal_function. In this case the function returns immediately, and you can block later for the operation to complete (see below).

When you call a function in the usual blocking manner, the function in question will obviously return its “normal” result. When called in a non-blocking manner, the function will return a handle to a job which is what you will need if you want to block later on for the job to complete.

Warning

The signal_function should not initiate any Picamera2 activity (by calling Picamera2 methods, for example) itself, as this is likely to result in a deadlock. Instead, it should be setting events or variables for threads to respond to.

Completing an asynchronous request

After launching an asynchronous operation as described above, you should have recorded the job handle that was returned to you. An application may then call Picamera2.wait(job) to complete the process, for example:

result = picam2.wait(job)

The wait function returns the result that would have been returned if the operation had blocked initially. You don’t have to wait for one job to complete before submitting another. They will complete in the order they were submitted.

Here’s a short example. The switch_mode_and_capture_file method captures an image to file and returns the image metadata. So we can do the following:

from picamera2 import Picamera2
import time

picam2 = Picamera2()
still_config = picam2.create_still_configuration()
picam2.configure(picam2.create_preview_configuration())
picam2.start()
time.sleep(1)
job = picam2.switch_mode_and_capture_file(still_config, "test.jpg", wait=False)

# now we can do some other stuff...
for i in range(20):
    time.sleep(0.1)
    print(i)

# finally complete the operation:
metadata = picam2.wait(job)

6.6. High-level capture API

There are some high-level image capture functions provided for those who may not want such an in-depth understanding of how Picamera2 works. These functions should not be called from the camera’s event processing thread (for example, call them directly from the Python interpreter or another non-camera thread).

6.6.1. start_and_capture_file

For simple image capture we have the Picamera2.start_and_capture_file method. This function will configure and start the camera automatically, and return once the capture is complete. It accepts the following parameters, all of which have sensible default values so the function can be called with no arguments at all:

  • name (default "image.jpg") — the filename to save the captured image to.
  • delay (default 1.0) — the number of seconds of delay before capturing the image. The value 0 (no delay) is valid.
  • preview_mode (default "preview") — the camera configuration to use for the preview phase of the operation. The default value uses Picamera2.preview_configuration, though any other configuration can be supplied. A preview phase only occurs when delay is greater than zero.
  • capture_mode (default "still") — the camera configuration to use for capturing the image. The default value uses Picamera2.still_configuration, though any other configuration can be supplied.
  • show_preview (default True) — whether to show a preview window. By default preview images are only displayed for the preview phase of the operation, unless this behaviour is overridden by the supplied camera configurations via the display parameter. If subsequent calls change this value, call Picamera2.stop_preview() in between.

Usage:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.start_and_capture_file("test.jpg")

All the usual file formats (JPEG, PNG, BMP and GIF) are supported.

6.6.2. start_and_capture_files

This function is very similar to start_and_capture_file, except that it can capture multiple images with a time delay between them. It can be called with no arguments at all, but accepts the following optional parameters:

  • name (default "image{:03d}.jpg") — the filename pattern to use for saving the captured images. Include a format directive (such as in the default name) that will be replaced by a counter, otherwise the images will overwrite one another.
  • initial_delay (default 1.0) — the number of seconds of delay before capturing the first image. The value 0 is valid.
  • preview_mode (default "preview") — the camera configuration to use for the preview phases of the operation. The default value uses Picamera2.preview_configuration, though any other configuration can be supplied. A preview phase only occurs when the corresponding delay parameter is greater than zero.
  • capture_mode (default "still") — the camera configuration to use for capturing the images. The default value uses Picamera2.still_configuration, though any other configuration can be supplied.
  • num_files (default 1) — the number of images to capture.
  • delay (default 1.0) — the number of seconds between captures for all images except the first (which is governed by initial_delay). If this has the value 0, there is no preview phase between captures.
  • show_preview (default True) — whether to show a preview window. By default, preview images are only displayed for the preview phases of the operation, unless this behaviour is overridden by the supplied camera configurations via the display parameter. If subsequent calls change this value, call Picamera2.stop_preview() in between.

Examples:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.start_and_capture_files("test{:d}.jpg", initial_delay=5, delay=5, num_files=10)

This will capture ten files named test0.jpg through test9.jpg, with a five-second delay before every capture.

To capture images back-to-back with minimum delay:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.start_and_capture_files("test{:d}.jpg", initial_delay=0, delay=0, num_files=10)

In practice, the rate of capture will be limited by the time it takes to encode and save the JPEG files. For faster capture, it might be worth saving a video in MJPEG format instead.

6.7. Further examples

Many of the examples demonstrate how to capture images. We draw the reader’s attention to just a few of them:

  • metadata_with_image.py — captures an image and also the metadata for that image.
  • capture_to_buffer.py — captures to a buffer rather than a file.
  • still_during_video.py — captures a still image while a video recording is in progress.
  • opencv_mertens_merge.py — takes several captures at different exposures, starting and stopping the camera for each capture.
  • easy_capture.py — uses the high-level API to capture images.
  • capture_dng_and_jpeg_helpers.py — uses the helpers to save a JPEG and DNG file without holding the entire CompletedRequest object.

Chapter 7. Capturing videos

In Picamera2, the process of capturing and encoding video is largely automatic. The application only has to define what encoder it wants to use to compress the image data, and how it wants to output this compressed data stream.

The mechanics of taking the camera images that arrive, forwarding them to an encoder, which in turn sends the results directly to the requested output, is entirely transparent to the user. The encoding and output all happens in a separate thread from the camera handling to minimise the risk of dropping camera frames.

Here is a first example of capturing a ten-second video:

from picamera2.encoders import H264Encoder
from picamera2 import Picamera2
import time

picam2 = Picamera2()
video_config = picam2.create_video_configuration()
picam2.configure(video_config)

encoder = H264Encoder(bitrate=10_000_000)
output = "test.h264"

picam2.start_recording(encoder, output)
time.sleep(10)
picam2.stop_recording()

In this example we use the H.264 encoder. For the output object we can just use a string for convenience; this will be interpreted as a simple output file. For configuring the camera, create_video_configuration is a good starting point, as it uses a larger buffer_count to reduce the risk of dropping frames.

We also used the convenient start_recording and stop_recording functions, which start and stop both the encoder and the camera together. Sometimes it can be useful to separate these two operations—perhaps you want to start and stop a recording multiple times while leaving the camera running throughout. For this reason, start_recording could have been replaced by:

picam2.start_encoder(encoder, output)
picam2.start()

and stop_recording by:

picam2.stop()
picam2.stop_encoder()

7.1. Encoders

Encode quality

All the video encoders can be constructed with parameters that determine the quality (amount of compression) of the output, such as the bitrate for the H.264 encoder. For those not so familiar with the details of these encoders, these parameters can also be omitted in favour of supplying a quality to the start_encoder or start_recording functions. The permitted quality parameters are:

  • Quality.VERY_LOW
  • Quality.LOW
  • Quality.MEDIUM — the default for both functions if the parameter is not specified
  • Quality.HIGH
  • Quality.VERY_HIGH

This quality parameter only has any effect if the encoder was not passed explicit codec-specific parameters. It could be used like this:

from picamera2.encoders import H264Encoder, Quality
from picamera2 import Picamera2
import time

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration())

encoder = H264Encoder()
picam2.start_recording(encoder, "test.h264", quality=Quality.HIGH)
time.sleep(10)
picam2.stop_recording()

Suitable adjustments will be made by the encoder according to the supplied quality parameter, though this is of a “best efforts” nature and somewhat subject to interpretation. Applications are recommended to choose explicit parameters for themselves if the quality parameter is not having the desired effect.

Note

On Pi 4 and earlier devices there is dedicated hardware for H.264 and MJPEG encoding. On a Pi 5, however, these codecs are implemented in software using FFmpeg libraries. The performance on a Pi 5 is similar or better, and the images are encoded with better quality. The JPEG encoder runs in software on all platforms.

Encode frame rate

Normally, the encoder of necessity runs at the same frame rate as the camera. By default, every received camera frame gets sent to the encoder. However, you can use the encoder frame_skip_count property to instead receive every n-th frame. For example, add the following code after creating the encoder in the example above to run the encode at half the rate of the camera:

encoder.frame_skip_count = 2

Audio

Audio can be encoded and added to a video recording either by using the FfmpegOutput class or by using the PyavOutput class.

When using FfmpegOutput, the audio is controlled by the FfmpegOutput object itself, so for more information please refer to the FfmpegOutput documentation.

When using PyavOutput, audio is encoded by the encoder object. Please refer to the PyavOutput documentation for more information on how to configure the encoder in this case.

7.1.1. H264Encoder

The H264Encoder class implements an H.264 encoder using the Pi’s in-built hardware, accessed through the V4L2 kernel drivers, supporting up to 1080p30. The constructor accepts the following optional parameters:

  • bitrate (default None) — the bitrate (in bits per second) to use. When None, the encoder chooses an appropriate bitrate according to the requested quality when it starts.
  • repeat (default False) — whether to repeat the stream’s sequence headers with every intra frame (I-frame). This can be useful when streaming video over a network, when the client may not receive the start of the stream where the sequence headers would normally be located.
  • iperiod (default None) — the number of frames from one I-frame to the next. The value None leaves this at the discretion of the hardware, which defaults to 60 frames.

This encoder can accept either three-channel RGB ("RGB888" or "BGR888"), four-channel RGBA ("XBGR8888" or "XRGB8888") or YUV420 ("YUV420").

7.1.2. JpegEncoder

The JpegEncoder class implements a multi-threaded software JPEG encoder, which can also be used as a motion JPEG ("MJPEG") encoder. It accepts the following optional parameters:

  • num_threads (default 4) — the number of concurrent threads to use for encoding.
  • q (default None) — the JPEG quality number. When None, the encoder chooses an appropriate value according to the requested quality when it starts.
  • colour_space (default None) — the software will select the correct colour space for the stream being encoded, so this parameter should normally be left blank.
  • colour_subsampling (default 420) — the form of YUV that the encoder will convert the RGB pixels into internally before encoding. It therefore determines whether a JPEG decoder will see a YUV420 image or something else. Valid values are 444 (YUV444), 422 (YUV422), 440 (YUV440), 420 (YUV420, the default), 411 (YUV411) or Gray (greyscale).

This encoder can accept either three-channel RGB ("RGB888" or "BGR888") or four-channel RGBA ("XBGR8888" or "XRGB8888"). Note that you cannot pass it any YUV formats.

Note

The JpegEncoder is derived from the MultiEncoder class which wraps frame-level multi-threading around an existing software encoder, and some users may find it helpful in creating their own parallelised codec implementations. There will only be a significant performance boost if Python’s GIL (Global Interpreter Lock) can be released while the encoding is happening—as is the case with the JpegEncoder as the bulk of the work happens in a call to a C library.

7.1.3. MJPEGEncoder

The MJPEGEncoder class implements an MJPEG encoder using the Raspberry Pi’s in-built hardware, accessed through the V4L2 kernel drivers. The constructor accepts the following optional parameter:

  • bitrate (default None) — the bitrate (in bits per second) to use. When None, the encoder chooses an appropriate bitrate according to the requested quality when it starts.

This encoder can accept either three-channel RGB ("RGB888" or "BGR888"), four-channel RGBA ("XBGR8888" or "XRGB8888") or YUV420 ("YUV420").

7.1.4. "Null" Encoder

The base Encoder class can be used as the “null” encoder—that is, an encoder that does nothing at all. It outputs exactly the same frames as were passed to it, without any compression or processing whatsoever. It could be used to record YUV or RGB frames (according to the output format of the stream being recorded), or even, as in the following example, the raw Bayer frames that are being output by the image sensor:

from picamera2 import Picamera2
from picamera2.encoders import Encoder
import time

picam2 = Picamera2()
config = picam2.create_video_configuration(raw={}, encode="raw")
picam2.configure(config)

encoder = Encoder()
picam2.start_recording(encoder, "test.raw")
time.sleep(5)
picam2.stop_recording()

7.2. Outputs

Picamera2 decouples encoding from delivery through outputs. You can direct encoded frames to files, sockets, in-memory buffers, and more. Outputs are created explicitly or implicitly (by passing a string to start_encoder/start_recording).

7.2.1. FileOutput

FileOutput writes frames to a destination that behaves like a file.

  • None — discard all frames.
  • str — open a regular file path.
  • File-like object — any object exposing a write method, such as io.BytesIO or a socket wrapper.
from picamera2.outputs import FileOutput

# Write to a file on disk
output = FileOutput("test.h264")

# Write to an in-memory buffer
import io
buffer = io.BytesIO()
output = FileOutput(buffer)

# Stream to a UDP socket
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
    sock.connect(("REMOTE_IP", 10_001))
    output = FileOutput(sock.makefile("wb"))

7.2.2. FfmpegOutput

FfmpegOutput launches an ffmpeg subprocess and pipes the encoded stream into it. This enables powerful container formats, streaming protocols, and muxed audio. Key arguments:

  • output_filename — an ffmpeg command fragment ("test.mp4", "test.ts", "-f mpegts udp://<ip>:<port>", etc.).
  • audio (default False) — enable audio capture via PulseAudio.
  • audio_device, audio_sync, audio_samplerate, audio_codec, audio_bitrate — optional audio tuning parameters.
from picamera2.outputs import FfmpegOutput

output = FfmpegOutput("test.mp4", audio=True)

Note

FfmpegOutput cannot receive Picamera2’s precise frame timestamps, so ffmpeg resamples them, introducing a small amount of jitter.

7.2.3. CircularOutput

CircularOutput maintains a ring buffer of encoded frames so you can retain the moments preceding an event.

  • file — optional filename/file-like object. Leave None to buffer without writing until needed.
  • buffersize — number of buffered frames (frames ≈ seconds × frame rate).
from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import CircularOutput

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration())

encoder = H264Encoder()
output = CircularOutput(buffersize=150)  # ~5 s at 30 fps

picam2.start_recording(encoder, output)

# Later, persist the buffered segment
output.fileoutput = "event.h264"
output.start()
# ... and eventually stop
output.stop()

7.2.4. PyavOutput

PyavOutput talks directly to FFmpeg libraries through PyAV, avoiding an external process and providing better timestamp fidelity. It supports audio when the encoder is configured to emit audio packets.

Constructor parameters:

  • output_filename — path or streaming URI ("test.mp4", "udp://<ip>:<port>", "pipe:1", …).
  • format — override PyAV’s autodetection ("mp4", "mpegts", "h264", …).

Extra encoder settings when targeting PyAV outputs:

encoder.audio = True
encoder.audio_input = {"file": "default", "format": "pulse"}
encoder.audio_output = {"codec_name": "aac"}
encoder.audio_sync = -100_000  # μs offset

Recording to MP4 with audio:

import time

from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import PyavOutput

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration({"size": (1280, 720), "format": "YUV420"}))

encoder = H264Encoder(bitrate=10_000_000)
encoder.audio = True
output = PyavOutput("test.mp4")

picam2.start_recording(encoder, output)
time.sleep(5)
picam2.stop_recording()

Serving an MPEG-TS stream to each connecting TCP client:

import socket
from threading import Event

from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import PyavOutput

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration({"size": (1280, 720), "format": "YUV420"}))

encoder = H264Encoder(bitrate=10_000_000)
encoder.audio = True

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(("0.0.0.0", 8888))

    while True:
        sock.listen()
        conn, _ = sock.accept()
        output = PyavOutput(f"pipe:{conn.fileno()}", format="mpegts")
        done = Event()
        output.error_callback = lambda exc: done.set()

        picam2.start_recording(encoder, output)
        done.wait()  # returns when the client disconnects
        picam2.stop_recording()

7.2.5. CircularOutput2

CircularOutput2 generalises circular buffering to any output (particularly PyAV-based ones). Configure the buffer via buffer_duration_ms and use:

  • open_output(output) — start writing buffered frames (delayed by the configured duration) to another output.
  • close_output() — stop writing to the current output; if you stop the circular output without closing, remaining buffered data is flushed automatically.
import time

from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import CircularOutput2, PyavOutput

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration({"size": (1280, 720), "format": "YUV420"}))

encoder = H264Encoder(bitrate=10_000_000)
encoder.audio = True
circular = CircularOutput2(buffer_duration_ms=5000)

picam2.start_recording(encoder, circular)
time.sleep(5)

circular.open_output(PyavOutput("start.mp4"))
time.sleep(5)
circular.close_output()

circular.open_output(PyavOutput("end.mp4"))
picam2.stop_recording()

start.mp4 captures the initial five seconds (from the buffer), while end.mp4 receives the final five seconds flushed on shutdown.

7.3. Capturing synchronised videos with multiple cameras

Libcamera can software-synchronise multiple cameras. One device acts as the server and broadcasts timing packets; others are clients that adjust their frame start times. Configure each Picamera2 instance with matching framerates and set SyncMode appropriately.

import time
from libcamera import controls

from picamera2 import Picamera2
from picamera2.encoders import H264Encoder

picam2 = Picamera2()
ctrls = {"SyncMode": controls.rpi.SyncModeEnum.Server, "FrameRate": 30}
config = picam2.create_video_configuration(controls=ctrls)
picam2.configure(config)

encoder = H264Encoder(bitrate=5_000_000)
encoder.sync_enable = True

picam2.start_recording(encoder, "server.h264")
print("Waiting for sync...")
encoder.sync.wait()  # blocks until the synchronisation moment
print("Recording has started")

time.sleep(5)
picam2.stop_recording()
print("Recording has finished")

Clients use controls.rpi.SyncModeEnum.Client and otherwise run the same script. During synchronised recording the encoder exposes an Event (encoder.sync) to signal that all devices are aligned.

7.4. High-level video recording API

The convenience function Picamera2.start_and_record_video(output, ...) configures, starts, and (optionally) stops a recording for you.

Required argument:

  • output — filename or output object. When the name ends in .mp4, a FfmpegOutput is created automatically to produce a valid MP4 container.

Optional arguments:

  • encoder — custom encoder instance (defaults to H.264 or MJPEG inferred from the filename).
  • config — explicit camera configuration; defaults to Picamera2.video_configuration.
  • qualityQuality enum used when the encoder leaves bitrate unspecified.
  • show_preview — whether to display a preview window (default False). Call stop_preview yourself if you toggle this between calls.
  • duration — seconds to record before stopping automatically (0 returns immediately; call stop_recording() later).
  • audio — set True to include audio (requires an MP4 output and default PulseAudio input).

Example recording five seconds to MP4:

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.start_and_record_video("test.mp4", duration=5, audio=True)

7.5. Further examples

  • audio_video_capture.py — records audio and video to an MP4 file.
  • capture_circular.py — writes from a circular buffer when motion is detected.
  • capture_circular_stream.py — streams while simultaneously recording buffered segments.
  • capture_mjpeg.py — captures an MJPEG stream.
  • mjpeg_server.py — serves an MJPEG preview over HTTP.

Chapter 8. Advanced topics

8.1. Display overlays

Preview windows support overlays—RGBA bitmaps composited over the live image. The alpha channel controls transparency per pixel.

Guidelines:

  • The overlay bitmap is resized to match the preview surface automatically.
  • Call set_overlay only after configuring the camera (so image dimensions are known).
  • set_overlay copies the bitmap; you may reuse or modify your source afterwards.
  • Overlays suit simple UI and annotation—not high-speed animations.
  • The preview transform (flip/rotate) does not affect overlay orientation.
import numpy as np

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration())
picam2.start(show_preview=True)

overlay = np.zeros((300, 400, 4), dtype=np.uint8)
overlay[:150, 200:] = (255, 0, 0, 64)   # semi-transparent red
overlay[150:, :200] = (0, 255, 0, 64)   # semi-transparent green
overlay[150:, 200:] = (0, 0, 255, 64)   # semi-transparent blue

picam2.set_overlay(overlay)

Load overlays created in external tools (e.g. OpenCV) with cv2.imread(path, cv2.IMREAD_UNCHANGED) to preserve the alpha channel.

Example reference scripts: overlay_gl.py, overlay_qt.py, overlay_drm.py.

8.2. The event loop

The preview subsystem supplies the event loop that feeds requests into libcamera, dequeues them, and dispatches frames to encoders and preview windows. You can hook into this loop for lightweight per-frame processing.

8.2.1. Using the event loop callbacks

Register functions that run before or after frame delivery:

  • pre_callback(request) — invoked before frames are handed to applications, encoders, and preview.
  • post_callback(request) — invoked after frames are handed to applications but before encoders/preview.

Keep callbacks fast; avoid blocking Picamera2 methods from within them to prevent deadlocks.

Example: timestamp every frame using OpenCV.

import time
import cv2

from picamera2 import Picamera2, MappedArray

picam2 = Picamera2()

colour = (0, 255, 0)
origin = (0, 30)
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 1
thickness = 2

def apply_timestamp(request):
    timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
    with MappedArray(request, "main") as m:
        cv2.putText(m.array, timestamp, origin, font, scale, colour, thickness)

picam2.pre_callback = apply_timestamp
picam2.start(show_preview=True)

Warning

Limit callback work to lightweight in-place drawing or metadata inspection. Avoid lengthy processing or additional Picamera2 calls inside the callback.

8.2.2. Dispatching tasks into the event loop

For more complex workflows you can schedule a list of functions for the event loop to execute sequentially. Each function must return (finished: bool, value). When finished, the event loop pops the function and continues with the next frame. When the list empties, the dispatch completes and Picamera2.wait(job) returns the final value.

from picamera2 import Picamera2

picam2 = Picamera2()
capture_config = picam2.create_still_configuration()
picam2.start()

def capture_and_stop(target):
    picam2.capture_file_(target)
    picam2.stop_()
    return True, None

functions = [
    lambda: picam2.switch_mode_(capture_config),
    lambda: capture_and_stop("test.jpg"),
]

job = picam2.dispatch_functions(functions, wait=True)
picam2.wait(job)

Functions suffixed with _ (e.g. switch_mode_) are explicitly safe to call from within the event loop.

8.3. Pixel formats and memory considerations

Different pixel formats vary in memory use, alignment requirements, and third-party compatibility. The table below summarises key properties at full 12 MP resolution.

Format Bits/pixel Alignment (pixels) NumPy shape Notes
XBGR8888 32 16 (height, width, 4) RGBA with fixed alpha 255, channels ordered R-G-B-A.
XRGB8888 32 16 (height, width, 4) RGBA with B-G-R ordering.
BGR888 24 32 (height, width, 3) Channels ordered R-G-B.
RGB888 24 32 (height, width, 3) Channels ordered B-G-R.
YUV420 / YVU420 12 64 (Pi 4) / 128 (Pi 5) (height * 3 // 2, width) Planar Y plus interleaved chroma at half resolution.

Additional formats (NV12, NV21, YUYV, YVYU, UYVY, VYUY) are supported for buffers but may not map cleanly into NumPy arrays.

CMA memory

Image buffers reside in CMA (Contiguous Memory Allocator) space. If CMA runs out, buffer allocation fails. Options:

  • Reduce buffer_count or choose smaller formats.
  • Increase CMA size by editing /boot/config.txt (e.g. dtoverlay=vc4-kms-v3d,cma-384).
  • Remove legacy gpu_mem settings (no longer used by libcamera/Picamera2).

Working with YUV420 images

YUV420 reduces memory usage but may require conversion for libraries expecting RGB. OpenCV can efficiently convert:

import cv2

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration({"format": "YUV420"}))
picam2.start()

yuv420 = picam2.capture_array()
rgb = cv2.cvtColor(yuv420, cv2.COLOR_YUV420p2RGB)

8.4. Buffer allocations and queues

Default buffer counts:

  • Preview: 4 buffers (smooth operation even with moderate processing).
  • Still capture: 1 buffer (minimises memory footprint; raises risk of dropped frames).
  • Video: 6 buffers (absorbs encoder latency).

If your application retains requests for extended periods, allocate extra buffers to avoid stalling the pipeline. Picamera2 itself queues the most recent frame (unless queue=False or buffer_count=1), allowing immediate responses to capture requests at the cost of returning slightly older frames.

8.5. Using the camera in Qt applications

Picamera2 ships Qt widgets for embedding camera previews in GUI apps:

  • QGlPicamera2 — hardware-accelerated (recommended when available).
  • QPicamera2 — software-rendered fallback.

Widgets accept keep_ar (preserve aspect ratio) and transform (flip/mirror) parameters, and both expose add_overlay for RGBA overlays.

A minimal application:

from PyQt5.QtWidgets import QApplication

from picamera2 import Picamera2
from picamera2.previews.qt import QGlPicamera2

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration())

app = QApplication([])
widget = QGlPicamera2(picam2, width=800, height=600, keep_ar=False)
widget.setWindowTitle("Qt Picamera2 App")

picam2.start()
widget.show()
app.exec()

Blocking calls in Qt

When Qt owns the event loop, camera operations must be non-blocking. Supply signal_function=qglpicamera2.signal_done (or equivalent) and react to done_signal to collect results.

from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

from picamera2 import Picamera2
from picamera2.previews.qt import QGlPicamera2

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration())

app = QApplication([])
widget = QGlPicamera2(picam2)
button = QPushButton("Capture JPEG")
window = QWidget()

layout = QVBoxLayout(window)
layout.addWidget(widget)
layout.addWidget(button)

picam2.start()

def on_capture_done(job):
    picam2.wait(job)
    button.setEnabled(True)

widget.done_signal.connect(on_capture_done)

def on_click():
    button.setEnabled(False)
    cfg = picam2.create_still_configuration()
    picam2.switch_mode_and_capture_file(cfg, "test.jpg", signal_function=widget.signal_done)

button.clicked.connect(on_click)

window.show()
app.exec()

Alternative widget modules are available for PyQt6 (QGl6Picamera2), PySide2 (QGlSide2Picamera2), and PySide6 (QGlSide6Picamera2).

8.6. Debug logging

Picamera2 uses Python’s logging framework. Configure logging with Picamera2.set_logging(level, output=None, msg=None).

from picamera2 import Picamera2

Picamera2.set_logging(Picamera2.DEBUG)

Note

The legacy verbose_console constructor argument is deprecated. Use set_logging instead.

Libcamera (the underlying C++ library) has its own logging controlled via environment variables:

  • LIBCAMERA_LOG_FILE — optional log destination (defaults to stderr).
  • LIBCAMERA_LOG_LEVELS — set global or per-module log levels (DEBUG, INFO, WARN, ERROR, FATAL or numeric 0–4).

Example: LIBCAMERA_LOG_LEVELS=WARN python my_script.py.

8.7. Multiple cameras

On platforms with multiple camera connectors (e.g. CM4) you can create several Picamera2 instances simultaneously. Use Picamera2.global_camera_info() to list available devices and their order (CSI cameras first, then USB).

from picamera2 import Picamera2

picam2a = Picamera2(0)
picam2b = Picamera2(1)

picam2a.start()
picam2b.start()

picam2a.capture_file("cam0.jpg")
picam2b.capture_file("cam1.jpg")

picam2a.stop()
picam2b.stop()

Each camera operates independently; there is no automatic synchronisation unless you employ the multi-camera sync mechanism described earlier. Multi-way CSI bridge boards must still present only one active camera at a time to the ISP.

8.8. Multi-processing

To bypass the Global Interpreter Lock (GIL), you can share buffers with other processes. The picamera_multiprocessing.py example demonstrates classes built on Python’s multiprocessing module that transfer buffers without copying. Key points:

  • Shared buffers can be sent to child processes created via multiprocessing; more complex IPC is needed for unrelated processes.
  • Only one process should control the camera pipeline. Child processes should consume frames, not reconfigure the camera.

Chapter 9. Application notes

This chapter answers common “how do I…” questions for specific deployment scenarios.

9.1. Streaming to a network

9.1.1. Simple streaming

Starter scripts:

  • capture_stream.py — stream to a TCP socket.
  • capture_stream_udp.py — stream via UDP.
  • pyav_stream2.py — PyAV-based TCP streaming.
  • mjpeg_server.py — MJPEG over HTTP.

9.1.2. Streaming with MediaMTX

MediaMTX can ingest Pi camera feeds and redistribute them over RTSP/WebRTC. Minimal configuration (mediamtx.yml):

paths:
  cam:
    source: rpiCamera
    rpiCameraBitrate: 10000000

Optional sourceOnDemand: yes starts/stops the camera when clients connect. Viewers can use http://<ip>:8889/cam, rtsp://<ip>:8554/cam, or WebRTC clients.

To publish a custom stream (e.g. with overlays or additional processing), replace the configuration with:

paths:
  cam:
    runOnDemand: python /home/pi/stream.py

stream.py might look like:

import time

from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import PyavOutput

picam2 = Picamera2()
config = picam2.create_video_configuration({"size": (1920, 1080), "format": "YUV420"}, controls={"FrameRate": 30})
picam2.configure(config)

encoder = H264Encoder(bitrate=10_000_000)
output = PyavOutput("rtsp://127.0.0.1:8554/cam", format="rtsp")

picam2.start_recording(encoder, output)
try:
    while True:
        time.sleep(0.5)
except KeyboardInterrupt:
    pass
finally:
    picam2.stop_recording()

Note

Increase network receive buffers to avoid packet loss between the Python process and MediaMTX:

net.core.rmem_max=1000000
net.core.rmem_default=1000000

Add these to /etc/sysctl.conf and reboot.

9.2. Output using FFmpeg

Beyond the basics, FfmpegOutput and PyavOutput can target advanced streaming scenarios.

9.2.1. HLS live stream

from picamera2.outputs import FfmpegOutput

output = FfmpegOutput(
    "-f hls -hls_time 4 -hls_list_size 5 -hls_flags delete_segments "
    "-hls_allow_cache 0 stream.m3u8"
)

Serve the generated files with a simple HTTP server, e.g. python3 -m http.server.

9.2.2. MPEG-DASH live stream

from picamera2.outputs import FfmpegOutput

output = FfmpegOutput("-f dash -window_size 5 -use_template 1 -use_timeline 1 stream.mpd")

Again, expose the directory over HTTP.

9.2.3. MPEG-2 transport stream

Stream via UDP:

from picamera2.outputs import FfmpegOutput

output = FfmpegOutput("-f mpegts udp://<ip>:<port>")

For improved timestamps, use PyAV directly:

from picamera2.outputs import PyavOutput

output = PyavOutput("udp://<ip>:<port>", format="mpegts")

Enable audio for PyAV outputs by setting encoder.audio = True.

Complete TCP-streaming example:

import socket
from threading import Event

from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import PyavOutput

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration({"size": (1280, 720), "format": "YUV420"}))

encoder = H264Encoder(bitrate=10_000_000)
encoder.audio = True

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(("0.0.0.0", 8888))

    while True:
        sock.listen()
        conn, _ = sock.accept()
        output = PyavOutput(f"pipe:{conn.fileno()}", format="mpegts")
        done = Event()
        output.error_callback = lambda exc: done.set()

        picam2.start_recording(encoder, output)
        done.wait()
        picam2.stop_recording()

9.3. Multiple outputs

An encoder can feed multiple outputs simultaneously—for example, streaming live while recording segments to disk.

import time

from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import FileOutput, FfmpegOutput

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration())

encoder = H264Encoder(repeat=True, iperiod=15)
output_stream = FfmpegOutput("-f mpegts udp://<ip>:12345")
output_file = FileOutput()
encoder.output = [output_stream, output_file]

picam2.start_encoder(encoder)
picam2.start()

time.sleep(5)

output_file.fileoutput = "clip.h264"
output_file.start()

time.sleep(5)
output_file.stop()

# Keep streaming indefinitely…
time.sleep(9999999)

9.4. Manipulating camera buffers in place

You can draw on preview frames while running lower-rate computer vision tasks, e.g. face detection:

import cv2

from picamera2 import Picamera2, MappedArray

face_detector = cv2.CascadeClassifier("/path/to/haarcascade_frontalface_default.xml")

picam2 = Picamera2()
config = picam2.create_preview_configuration(
    main={"size": (640, 480)},
    lores={"size": (320, 240), "format": "YUV420"}
)
picam2.configure(config)

main_w, main_h = picam2.stream_configuration("main")["size"]
lores_w, lores_h = picam2.stream_configuration("lores")["size"]
faces = []

def draw_faces(request):
    with MappedArray(request, "main") as m:
        for x, y, w, h in faces:
            cv2.rectangle(m.array, (x, y), (x + w, y + h), (0, 255, 0, 0), 2)

picam2.post_callback = draw_faces
picam2.start(show_preview=True)

while True:
    lores = picam2.capture_array("lores")
    grey = lores[lores_h:, :lores_w]
    raw_faces = face_detector.detectMultiScale(grey, 1.1, 3)
    scale_x = main_w / lores_w
    scale_y = main_h / lores_h
    faces = [
        (int(x * scale_x), int(y * scale_y), int(w * scale_x), int(h * scale_y))
        for x, y, w, h in raw_faces
    ]

9.5. Using Python virtual environments

Create virtual environments with system packages included so that libcamera bindings remain available:

python -m venv --system-site-packages my-env

9.6. HDR mode and the Raspberry Pi Camera Module 3 (IMX708)

The IMX708 sensor supports HDR. Enable or disable it before instantiating Picamera2:

from picamera2.devices.imx708 import IMX708

with IMX708(camera_num) as cam:
    cam.set_sensor_hdr_mode(True)  # or False

Alternatively, configure via v4l2-ctl:

v4l2-ctl --set-ctrl wide_dynamic_range=1 -d /dev/v4l-subdev0

Note

Sub-device IDs vary. Use v4l2-ctl -d /dev/v4l-subdevX -l to locate the device exposing wide_dynamic_range.

9.7. HDR mode on Raspberry Pi 5

Pi 5 adds HDR via its temporal denoise engine. Toggle with runtime controls:

import libcamera

picam2.set_controls({"HdrMode": libcamera.controls.HdrModeEnum.SingleExposure})
# Later disable with:
picam2.set_controls({"HdrMode": libcamera.controls.HdrModeEnum.Off})

Allow several frames to elapse after switching modes (set delay in switch_mode_and_capture_* calls) so temporal denoise stabilises.

9.8. Using the Hailo AI Accelerator

Install the Hailo toolchain and obtain/compile a .hef model file. Run inference as follows:

from picamera2 import Picamera2
from picamera2.devices.hailo import Hailo

with Hailo("/path/to/model.hef") as hailo:
    model_h, model_w, _ = hailo.get_input_shape()

    picam2 = Picamera2()
    config = picam2.create_preview_configuration({"size": (model_w, model_h), "format": "RGB888"})
    picam2.start(config)

    frame = picam2.capture_array()
    results = hailo.run(frame)

Consult Hailo’s documentation for model-specific post-processing.

9.9. Using the IMX500 AI Accelerator

The Sony IMX500 camera runs AI models on-sensor. After installing Sony’s software and obtaining an .rpk model:

from picamera2 import Picamera2
from picamera2.devices import IMX500

imx500 = IMX500("/path/to/model.rpk")

picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration())
picam2.start()

metadata = picam2.capture_metadata()
outputs = imx500.get_outputs(metadata)

outputs contains the network tensor; interpret it according to the deployed model.

Appendix A. Pixel and image formats

Format Bits/pixel Alignment (pixels) NumPy shape Description
XBGR8888 32 16 (height, width, 4) RGBA; pixels laid out as [R, G, B, 255].
XRGB8888 32 16 (height, width, 4) RGBA; pixels laid out as [B, G, R, 255].
BGR888 24 32 (height, width, 3) RGB; pixels as [R, G, B].
RGB888 24 32 (height, width, 3) RGB; pixels as [B, G, R].
YUV420 12 64 (Pi 4) / 128 (Pi 5) (height * 3 // 2, width) Planar Y plus interleaved U/V planes.
YVU420 12 64 / 128 (height * 3 // 2, width) Planar Y plus interleaved V/U planes.
NV12/NV21 12 32 (not mapped) Planar Y plus interleaved chroma; typically consumed by encoders.
YUYV/YVYU/UYVY/VYUY 16 32 (not mapped) Packed 4:2:2 formats; best handled by encoders or custom code.

Support summary:

Format Capture buffer capture_array Qt GL preview Qt preview DRM preview Null preview JPEG encode Video encode OpenCV support Pillow support
XBGR8888/XRGB8888 ✔ (slow) Often (requires conversion)
RGB888/BGR888 ✔ (slow)
YUV420/YVU420 Requires OpenCV Convert with OpenCV
NV12/NV21 Limited
YUYV/UYVY/YVYU/VYUY ✔ (subset) Limited

Appendix B. Camera configuration parameters

Global parameters

Parameter Values Description
use_case "preview", "still", "video" Descriptive only; indicates intent of the configuration.
transform Transform() options Apply horizontal/vertical flips (or both) to all streams.
colour_space Sycc(), Smpte170m(), Rec709() Colour space for main/lores streams; raw streams remain in sensor space.
buffer_count 1, 2, … Number of buffer sets allocated (per request).
display None, "main", "lores" Stream shown in the preview window.
encode None, "main", "lores" Default stream encoded during video recording.
sensor {"output_size": (w, h), "bit_depth": n} Explicit sensor mode selection (overrides raw stream settings).
controls Dict of runtime controls Apply standard runtime controls automatically when the configuration is activated.

Stream parameters (main, lores, raw)

Parameter Values Description
format Format string (see Appendix A) Pixel format for the stream.
size (width, height) Output resolution. Raw streams must match supported sensor modes (see libcamera-hello --list-cameras).

Appendix C. Camera controls

Enum controls use values from libcamera.controls. Example:

from libcamera import controls

picam2.set_controls({"AeMeteringMode": controls.AeMeteringModeEnum.Spot})

Some controls (e.g. AnalogueGain, ExposureTime, FrameDurationLimits, ScalerCrop) have limits tied to the current configuration; inspect picam2.camera_controls to obtain (min, max, default) tuples.

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