Bullseye and Cameras in Python
After all the excitement has passed about all the features added to the new Raspberry Pi OS “Bullseye”; we started to notice quite a big feature that we had lost. Even if it is only temporarily.
OS releases from Bullseye onward will no longer support the older camera system and applications. There is no Python interface yet. An alternative to the old Picamera, imaginatively named Picamera2, is in development. Raspberry Pi OS Buster is still available to download if you’re not ready to use Bullseye. But What about those of us who do not want to wait?

For bigger Python applications we still need to wait and see what Picamera2 will bring us. But for simple small applications like a photo booth or timelapse cameras, we still have an alternative.
What will you need?
Raspberry Pi (any)
Raspberry Pi camera (any)
Camera cable, For a Raspberry Pi Zero you will need an adapter cable if you are not using a camera specific to the Zero.
SD card with the latest Bullseye OS and power supply
Or you can simply get one of our great essentials kits!

In this post we will assume that you have already set up your Raspberry Pi. If you need assistance, simply have a look at our blog post on how to do this. For more on connecting and installing the camera have a look at the documentation.
When running a Raspberry Pi OS based on Bullseye, the 5 basic libcamera-apps are already installed. In this case, official Raspberry Pi cameras will also be detected and enabled automatically.
You can check that everything is working by entering:
libcamera-hello
You should see a camera preview window for about 5 seconds.
Users running Buster will need to install one of the libcamera-apps packages first; and then configure their /boot/config.txt file with the appropriate overlay for the connected camera. Be aware that libcamera and the legacy raspicam stack cannot operate at the same time; to return to the legacy stack after using libcamera you will need to comment out the dtoverlay change you made. And reboot the system.
If you have used a camera on Raspberry Pi in the past you should be familiar with opening the Raspberry Pi interfaces either by raspi-config or the GUI; and enabling the camera interface. Bullseye users no longer need to enable the camera as there is new camera detection built in. To disable this you need to edit the following line in /boot/config.txt
camera_auto_detect=1
libcamera shell commands has been provided to start testing cameras on Raspberry Pis and to take photos and videos. See the Raspberry Pi Documentation for full details on this. So why not use these commands within Python? Can it really be that simple?
Let’s code
This is the just a short example of how to use the shell commands provided to use your camera from within Python. I will not be going through all the options available to you.
Create a new python script
nano test-cam.py
Then you can start editing this script and add some code
import os
os.system('libcamera-hello')
This will open a brief preview of the camera. Now that we know our camera works, how about taking a photo?
We still import our os, but now instead of using libcamera-apps, we will use libcamera-jpeg.
import os
os.system('libcamera-jpeg -o test.jpg')
Fantastic. Now open your file and locate the “test.jpg” photo you have just taken. Next I will look at taking timelapse photos.
import os
from time import sleep
for i in range(10):
os.system(f'libcamera-jpeg --ev 0.5 -t 200 -o test{i}.jpg')
sleep(5)
We have now imported the sleep function to add a delay between photos. By adding the photo commands in a Python loop we can take a few photos instead of just one. Or you could use the trusted “while True” to make an endless loop. For time convenience I chose a for loop, and set it to loop 10 times. This will give me 10 photos to work with. We declared the loop; used system to tell python to run a shell command; and provided a string of the command we want to run. Here I made use of f-strings, introduced in Python 3.6 to format the string being sent to system. This is for me the easiest way for formatting the string and adding a variable, without the use of concatenation. The added the delay in seconds, in my case 5 seconds.
What about the options that have changed in this photos? I found that my photos were a little bit dark; so I made use of a few of the options libcamera provides us to brighten the image a little bit. Raspberry Pi’s AEC/AGC algorithm allows applications to specify exposure compensation, that is, the ability to make images darker or brighter by a given number of stops, as follows
libcamera-jpeg --ev -0.5 -o darker.jpg
libcamera-jpeg --ev 0 -o normal.jpg
libcamera-jpeg --ev 0.5 -o brighter.jpg
Further remarks on Digital Gain
Digital gain is applied by the ISP (the Image Signal Processor), not by the sensor. The digital gain will always be very close to 1.0 unless:
- The total gain requested (either by the
--gain
option, or by the exposure profile in the camera tuning) exceeds that which can be applied as analogue gain within the sensor. Only the extra gain required will be applied as digital gain. - One of the colour gains is less than 1 (note that colour gains are applied as digital gain too). In this case the advertised digital gain will settle to 1 / min(red_gain, blue_gain). This actually means that one of the colour channels – just not the green one – is having unity digital gain applied to it.
- The AEC/AGC is changing. When the AEC/AGC is moving the digital gain will typically vary to some extent to try and smooth out any fluctuations, but it will quickly settle back to its “normal” value.
When using the libcamera-jpeg function the default preview lasts for about 5 seconds, I used the -t option to alter the length of time the preview shows. This option is measured in milliseconds. The preview window can be suppressed entirely with the -n (–nopreview) option.