Je to právě týden, co se nadace Raspberry Pi pochlubila třetí generace kamerky pro svůj stejnojmenný minipočítač. Camera Module 3 pořídíte na českém RPishopu od 695 korun a jeho hlavním lákadlem je 12MP snímací čip Sony IM708 s autofokusem!
Dnes si kamerku vyzkoušíme v praxi, napíšeme si totiž jednoduchý program v Pythonu pro elektronické ovládání ostření pomocí knihovny Picamera2 (PDF dokumentace) a GPIO Zero.
Do hry zapojíme rotační enkodér, jehož natáčením do stran budeme čočku posouvat k čipu, nebo naopak od něj, a tím velmi jemně přeostřovat na dálku, nebo naopak na nejbližší možný předmět ve vzdálenosti zhruba 10 centimetrů.
Podívejte se na video, co si dneska postavíme a naprogramujeme:
Novou kamerku ovládá Libcamera API
Než se ale vrhneme na náš slíbený projekt, nejprve malá rekapitulace pro nováčky, kteří s kamerou na Raspberry Pi pracují poprvé, anebo výhradně na starších verzích operačního systému Raspbian, respektive Raspberry Pi OS.
Na sklonku roku 2021 se v Raspberry Pi OS vše změnilo a s příchodem verze „Bullseye“ používá operační systém ve výchozím stavu k přístupu ke kamerám novou vrstvu Libcamera. Nadějný open-source framework pro Linux, Android a ChromeOS slibuje jednotné API pro práci s připojenými fotoaparáty.
Součástí balíku je i sada textových programů pro snadnou automatizaci, z nichž ty nejdůležitější najdete i v Raspberry Pi OS:
Ostříme z příkazové řádky
Jelikož vrstva Libcamera podporuje prakticky cokoliv, co může umět každý snímací čip – včetně ostření –, po připojení nového Camera Module 3 není třeba instalovat žádný další software.
Takže kdybychom chtěli uložit z příkazové řádky Raspberry Pi pořídit snímek fotka.jpg v plné automatice včetně zaostření, stačí prostě stejně jako u starších kamer zavolat:
libcamera-still -o fotka.jpg

Automatické ostření ve výchozím stavu (ostré objety vyplňující většinu scény a rozmazaný model Petřína v popředí)
Na rozdíl od běžného softwaru pro ovládání třeba webkamery na vašem laptopu ale máte k dispozici samozřejmě hromadu dalších parametrů pro plnou automatizaci.
Dejme tomu, že bychom ostření stále přenechali na automatu, ale s důrazem na ty nejbližší objekty (zhruba ve vzdálenosti 10 cm od čočky. V takovém případě příkaz lehce upravíme a napíšeme:
libcamera-still –autofocus-range macro -o fotka.jpg

Automatické ostření s důrazem na nejbližší objekty (ostrý model Petřína)
Do třetice bychom mohli namísto automatického zaostření nastavit vzdálenost čočky ručně. Nejprve přepneme pomocí parametru –autofocus-mode ostření na manuál a parametr –lens-position pomůže se samotným posunem čočky pomocí aktuátoru.
Pracuje s rozsahem 0,0-10,0, přičemž 0 představuje zaostření na nekonečno a 10 na nejbližší možný objekt ve vzdálenosti zhruba 0,1 m. Z toho plyne, že k přepočtu na metry můžete použít formulku 1/hodnota.
Kdybychom chtěli zaostřit na vzdálenost 10 centimetrů, zavoláme příkaz:
libcamera-still –autofocus-mode manual –lens-position 10 -o fotka.jpg

Ruční zaostření zhruba na 10 centimetrů
Pokud jsme se nepochopili, tak ještě jednou: Zadali jsme číslo 10, přepočet na metry je 1/číslo, takže 1/10 = 0,1, což dělá 10 centimetrů.
Program Libcamera-still nabízí hromadu dalších parametrů ostření včetně rychlosti, velikosti nebo třeba polohy ostřící oblasti (třeba pro případy, kdy chceme nechat vše na automatiku, ale s prioritou ostření na objekt v levém horním rohu).
Picamera2 zpřístupní Libcameru v Pythonu
Pokud bychom chtěli ovládat kameru ještě podrobněji, můžeme se pustit do pročítání dokumentace k aplikačnímu rozhraní Libcamera a napsat si vlastní C/C++ program. Pro rychlé experimenty v Pythonu ale máme na Malině k dispozici také populární a už v úvodu zmíněnou knihovnu Picamera2.

Náš program v Pythonu níže zobrazí rychlý náhled z kamery
Dvojka na konci upozorňuje, že se jedná o druhou generaci knihovny, která už staví právě na vrstvě Libcamera a není kompatibilní se starými verzemi operačního systému a stejně tak kamerami.
Práce s knihovnou Picamera2 pro Python je velmi jednoduchá a v dokumentaci (PDF) najdete hromadu stručných příkladů prakticky pro všechny situace. Pojďme si tedy krok za krokem postavit náš ruční elektronický mechanizmus.

K Raspberry Pi připojíme tlačítko s rotačním enkodérem, jehož otáčením ručně přeostříme. Všimněte si, že náhled je nyní zaostřený na cívku s pájkou v popředí
Kdyby měl minipočítač Raspberry Pi A/D převodník a analogový vstup, mohli bychom k desce připojit třeba jednoduchý otočný potenciometr a jeho natáčením do stran ovládat opět nastavení ostřící čočky v rozsahu 0,0-10,0.
Tlačítko s rotačním enkodérem
Malina nicméně analogový vstup nemá, a tak namísto poťáku sáhneme po kolíkovém ovladači s rotačním enkodérem a tlačítkem. Najdete ho v každém obchodě pro elektrokutily, a pokud máte v dílně 3D tiskárnu od Průši, úplně stejný kolíkový enkodér slouží jako základní ovládací tlačítko, jehož otáčením procházíte menu a stisknutím potvrdíte volbu.



Prototypovací modul tlačítka s rotačním enkodérem a prakticky identická součástka na 3D tiskárně Original Prusa i3 MK3S+
Úplně stejně to uděláme i v našem experimentu. Otáčením do jedné ze stran budeme navyšovat, nebo naopak snižovat ostřící hodnotu, no a stisknutím kolíku dáme knihovně Picamera2 povel k tomu, aby uložila fotografii v plném rozlišení a ve formátu JPEG.
Jak funguje rotační enkodér
Otáčející se hřídel ovladače s rotačním enkodérem spíná dva obvody, na kterých se tak mění logické stavy 0 a 1. Jejich časovou posloupností můžeme zjistit, jakým směrem otáčíme hřídel a o kolik kroků.

Konstrukce enkodéru s diskem s otvory
Jedna z konstrukcí enkodéru na schématu výše: Když se vodiče CLK a DT dostanou nad otvor, bude na nich logická 1. Když budou klouzat po disku, bude na nich logická 0. Podle posloupnosti pulzů snadno určíme směr otáčení a ze střídání 0 a 1 pak kroky enkodéru.
Kolíkovému ovladači s rotačním enkodérem se podrobně věnuji v únorovém vydání časopisu Computer. Oživíme jej v Arduinu a vysvětlíme si i princip funkce analogového potenciometru a děliče napětí.
GPIO Zero detekuje otáčení enkodéru
Samotný rotační enkodér oživíme pomocí další předinstalované knihovny GPIO Zero, která už podle svého názvu pracuje s digitálními signály na hlavní liště pinů desky Raspberry Pi. Pro ovládání enkodéru, který připojíme na piny 13 (DT) a 19 (CLK) minipočítače, slouží třída RotaryEncoder.
Na třídu pak skrze její metody when_rotated_clockwise a when_rotated_counter_clockwise napojíme funkce, které třída vyvolá, jakmile otočíme hřídelí o krok ve směru hodinových ručiček, nebo proti.
Úryvek kódu pro registraci události na rotačním enkodéru:
from gpiozero import RotaryEncoder
rotor = RotaryEncoder(19, 13)
rotor.when_rotated_clockwise = ostreniDoBlizka
rotor.when_rotated_counter_clockwise = ostreniDoDalky
Pokud se tak stane, v každé z těchto funkcí podle směru otáčení přičteme, nebo naopak odečteme konstantu 0,1 k výchozí ostřící hodnotě. Ta tedy bude lineárně klesat, nebo růst o krok 0,1 a v kódu zároveň zajistíme, aby se měnila jen v rozsahu 0,0-10,0.
Úryvek kódu pro funkce, které otáčením zvýší, nebo sníží pomocnou proměnnou pro polohu ostřící čočky:
def ostreniDoBlizka():
global poloha
poloha += 0.1
def ostreniDoDalky():
global poloha
poloha -= 0.1
A také detekuje stisk jeho tlačítka
Enkodér funguje zároveň jako tlačítko, které je připojené na jeho signál SW. Propojíme jej s pinem 26 na desce Raspberry Pi (piny 13, 19 a 26 jsou všechny vedle sebe) a z knihovny GPIO Zero povoláme do boje další třídu Button.
Úryvek kódu pro registraci stisku tlačítka:
from gpiozero import Button
spoust = Button(26)
spoust.when_pressed = ulozitSnimek
Podobně jako enkodér má Button k dispozici několik událostních metod, přičemž nás zajímá when_pressed. Asi netřeba napovídat, že ji třída tlačítka zpracuje v okamžiku, kdy stiskneme kolík rotačního enkodéru.
Picamera2 posune ostřící čočku a pořídí snímek
Na řadu konečně přichází knihovna Picamera2. Nejprve nastartujeme kameru v náhledovém režimu. Obraz bude mít nízké rozlišení, ale na oplátku můžeme na desktopu Raspberry Pi OS spustit okno s náhledem s vysokou obnovovací frekvencí, ve kterém okamžitě uvidíme, jak se nám obraz přeostřuje podle otáčení enkodéru.
Úryvek kódu pro start kamery v náhledovém režimu:
from picamera2 import Picamera2, Preview
kamera = Picamera2()
config_nahled = kamera.create_preview_configuration()
config_fotoaparat = kamera.create_still_configuration()
kamera.configure(config_nahled)
kamera.start_preview(Preview.QTGL, x=50, y=50, width=800, height=600)
kamera.start()
K ručnímu nastavení ostřící hodnoty z rozsahu 0,0-10,0 slouží metoda set_controls, pomocí které můžeme nastavit libovolný parametr kamery, který podporuje vrstva Libcamera. V našem případě to bude parametr AfMode, tedy režim ostření, který nastavíme podobně jako u textového programu libcamera-still na manuál, a zároveň parametr LensPosition, který už očekává numerickou hodnotu 0,0-10,0.
Úryvek kódu pro ruční nastavení polohy ostřící čočky:
kamera.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": poloha})
K uložení snímku do souboru pak použijeme metodu capture_file. Ovšem pozor! Kameru jsme spustili v rychlém náhledovém režimu, takže hotový JPEG by měl také náhledové rozlišení.
My jej chceme v plné kvalitě, a tak pomocí metody switch_mode přepneme kameru do režimu plnohodnotného fotoaparátu, pořídíme snímek a zase se přepneme zpět do nenáročného náhledového režimu. Během pořizování snímku se proto náhled z kamery pozastaví.
Úryvek kódu pro změnu režimu na fotoaparát, pořízení fotky a návrat do náhledového režimu:
kamera.switch_mode(config_fotoaparat)
kamera.capture_file("fotka.jpg")
kamera.switch_mode(config_nahled)
A takto vypadá zachycený výsledek v JPG po ručním doostření na nejbližší možný bod (cca 10cm):

Snímek z kamery po elektronickém ručním doostření
Picamera2 toho zvládne více
A to je celé. Dnes jsme si vyzkoušeli práci s novým kamerovým modulem Raspberry Pi Camera Module 3 v knihovně Picamera2 pro Python, ve které jsme ručně nastavovali ostřící hodnotu odpovídající vzdálenosti čočky od snímacího čipu.
Picamera2 toho ale umí samozřejmě mnohem vice včetně zachytávání videa a zachytávání surových nekomprimovaných framů pro další zpracování třeba pomocí neuronové sítě nebo oblíbené knihovny pro počítačové vidění OpenCV. To si ale ukážeme zase někdy příště.
Kompletní zdrojový kód programu v Pythonu:
# Knihovna pro asynchronni praci s nekonecnou smyckou programu
import asyncio
# Knihovna pro praci s GPIO
from gpiozero import RotaryEncoder, Button, TonalBuzzer
# Knihovna pro praci s kamerou skrze vrstvu Libcamera
from picamera2 import Picamera2, Preview
from libcamera import controls
# Knihovna pro zjisteni akutalniho data a casu,
# ktere pouzijeme v nazvu souboru
from datetime import datetime
# Tuto funkci zavolame, kdyz budeme na rotacnim enkoderu tocit jednimm serem
# Navysime ostrici cislo (0-10) a nastavime polohu ostrici cocky
def ostreniDoBlizka():
global poloha
poloha += 0.1
if poloha > 10:
poloha = 10
kamera.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": poloha})
print(f"Poloha cocky {poloha}")
# Analogicka funkce pro otaceni opacnym smerem, kdy ostrici cislo snizime
def ostreniDoDalky():
global poloha
poloha -= 0.1
if poloha < 0:
poloha = 0
kamera.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": poloha})
print(f"Poloha cocky {poloha}")
# Tuto funkci zavolame pri stisku tlacitka
def ulozitSnimek():
print("Menim rezim snimani na fotoaparat")
bzucak.play("A4")
kamera.switch_mode(config_fotoaparat)
soubor = f"foto_{datetime.today().strftime('%y%m%d%H%M%S')}.jpg"
kamera.capture_file(soubor)
print("Soubor ulozen")
print("Menim rezim snimani na rychly nahled")
kamera.switch_mode(config_nahled)
bzucak.stop()
# Funkce setup se zpracuje na zacatku podobne jako v Arduinu
async def setup():
# Registrace udalosti pro rotacni enkoder
rotor.when_rotated_clockwise = ostreniDoBlizka
rotor.when_rotated_counter_clockwise = ostreniDoDalky
# Registrace udalosti stisku pro tlacitko
spoust.when_pressed = ulozitSnimek
# Vychozi konfigurace kamery v nahledovem rezimu
kamera.configure(config_nahled)
# Zobrazeni okna s nahledem z kamery
kamera.start_preview(Preview.QTGL, x=50, y=50, width=800, height=600)
kamera.start()
# Udaje v ramu nahledoveho okna
kamera.title_fields = ["LensPosition", "ExposureTime", "FrameDuration", "Lux"]
# Uvidni vypis informaci z kamery pro posledni snimek
metadata = kamera.capture_metadata()
for polozka in metadata:
print(f"{polozka}: {metadata[polozka]}")
poloha = metadata["LensPosition"]
# Nekonecna a neblokujici smycka loop podobne jako v Arduinu
# Drzi nam program vzhuru
async def loop():
while True:
# Tady muze byt libovolny neblokujici kod
# Za 0.5 sekund zopakujeme prubeh smycky
await asyncio.sleep(.5)
# Inicializace programu
# Nastavime kameru, enkoder, tlacitko
# a spustime neblokujici smycku pomoci vestavene knihovny Asyncio
try:
poloha = 0
kamera = Picamera2()
config_nahled = kamera.create_preview_configuration()
config_fotoaparat = kamera.create_still_configuration()
rotor = RotaryEncoder(19, 13)
spoust = Button(26)
bzucak = TonalBuzzer(21)
asyncio.run(setup())
asyncio.run(loop())
# Program ciste ukoncime stisknutim CTRL-C v terminalu
except KeyboardInterrupt:
print("\nUkoncuji kameru")
try:
kamera.stop_preview()
except:
pass
kamera.stop()