Pojďme programovat elektroniku | Python | Raspberry Pi

Nová špičková kamera pro Raspberry Pi: Fotíme a ostříme v Pythonu

  • Nadace Raspberry Pi minulý týden představila novou kameru
  • Camera Module 3 má autofokus a 12 megapixelů
  • Dnes zkusíme elektronicky ovládat ostření z Pythonu

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
d318c626-55d8-489b-b354-104503143faf
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
3a4b92f1-7b23-4a92-829e-9f6490e6166d
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
31f30f25-662b-4626-8025-a38a060dc851
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.

cfd7e799-9eb5-4a55-a518-833ac581564c
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.

752ce9b4-8373-4423-b19d-28ba3a328475
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.

db98d64f-dc1a-4002-b0a4-9ee02ec7bb074eced83a-8e85-4b6d-bec8-b53648d4caa5fa71d289-6a1e-444e-a7de-f815a727c636
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ů.

f0853737-abbc-4887-bdba-5691cd04cf74
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):

5b3a3ace-0bac-4ff6-b160-3956fed1615f
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()
Diskuze (11) Další článek: Nejlepší aerodynamiku má čínský Aion Hyper GT. Vzduch prořezává lépe než Tesla nebo dosud rekordní Mercedes

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,