5.4. Čtení a zápis pixelů¶
Většina operací nad obrazem skrývá svou práci s jednotlivými pixely uvnitř jediného volání metody, kde smyčky procházející každý pixel běží nativní rychlostí. Existují však případy, kdy aplikační kód potřebuje přistoupit přímo k jednomu konkrétnímu pixelu: přečíst, co se nachází na určité pozici, zapsat do něj novou hodnotu, navzorkovat jediný bod pro kalibrační krok nebo odladit hodnotu na známém místě. Modul image poskytuje tuto úroveň přístupu prostřednictvím dvou forem adresování, z nichž každá odpovídá jinému způsobu uvažování o tom, kde pixel leží.
5.4.1. Adresování souřadnicí¶
Nejpřirozenější formou je ta, pro kterou si kapitola Coordinates již vybudovala slovník: pojmenovat pixel jeho kartézskými souřadnicemi (x, y). get_pixel() přijímá (x, y) a vrací hodnotu na dané pozici; set_pixel() přijímá stejné (x, y) spolu s hodnotou a zapíše ji.
Co tato volání vracejí nebo přijímají, závisí na formátu obrazu. Obrazy ve stupních šedi, binární a Bayer nesou jedinou hodnotu na pixel – jas u stupňů šedi, 0 nebo 1 u binárních, jeden vzorek barevného kanálu u Bayer – takže get_pixel() vrací jediné celé číslo. RGB565 nese tři barevné kanály zabalené do 16 bitů a get_pixel je ve výchozím nastavení rozbalí do n-tice (r, g, b), přičemž každý kanál je namapován do rozsahu 0 – 255.
Výchozí chování lze na obou koncích obrátit. Předání rgbtuple=False metodě get_pixel u obrazu RGB565 se vrátí k surovému 16bitovému zabalenému slovu – ke stejné formě, jakou vrací lineární index, a k efektivní formě, když aplikace hodlá tutéž zabalenou hodnotu rovnou zapsat zpět. Předání rgbtuple=True u jednokanálového obrazu dělá opak: uložená hodnota je před vrácením převedena na n-tici RGB888, přičemž obrazy Bayer projdou krokem debayer na místě. Tento argument existuje proto, aby si volající kód mohl vyžádat pixely v jednotném barevném prostoru bez ohledu na to, jak je podkladový obraz ukládá.
Komprimované obrazy – JPEG a PNG – nejsou metodami get_pixel ani set_pixel podporovány. Jejich bajty nepředstavují pixely na známých pozicích, a tyto metody vyvolají chybu místo toho, aby vrátily hodnotu, která by neměla žádný význam.
V praxi vypadají tyto vzory takto:
v = img.get_pixel(40, 30) # grayscale: int 0..255
img.set_pixel(40, 30, 255) # write white
r, g, b = img.get_pixel(40, 30) # RGB565: defaults to (r, g, b) tuple
img.set_pixel(40, 30, (255, 0, 0)) # write red
Pokud je požadované (x, y) mimo obraz, get_pixel vrátí None a set_pixel neudělá nic. To je záměrně shovívavé: mnoho algoritmů prochází těsně podél okrajů obrazu a krátce indexuje pozice mimo rozsah, a tichá prázdná operace je méně rušivá než výjimka pokaždé, když k tomu dojde.
5.4.2. Adresování lineárním indexem¶
Druhou formou je adresovat pixely podle jejich pozice v podkladovém bufferu. Připomeňme si rozvržení bufferu: pixely jsou uloženy řádek po řádku, nejprve všechny pixely horního řádku, poté všechny pixely následujícího řádku a tak dále až dolů. Toto uspořádání znamená, že každý pixel má jediný celočíselný index počítaný od 0 v levém horním rohu a zvyšující se postupně podél každého řádku. Pixel na souřadnici (x, y) má lineární index y * width + x.
Pixely jsou adresovány jak kartézskými souřadnicemi (x, y), tak lineárním indexem, který prochází buffer řádek po řádku, zleva doprava.¶
Modul image vystavuje tento index prostřednictvím běžné indexovací notace Pythonu: img[i] čte pixel na lineárním indexu i, img[i] = value jej zapisuje. Indexová forma vrací surovou uloženou hodnotu daného formátu, nikoli rozbalenou n-tici, kterou ve výchozím nastavení vrací get_pixel(). Toto rozlišení je důležité, protože dříve zvolený formát rozhoduje o tom, jak surová hodnota vypadá:
Pixely ve stupních šedi a Bayer se vracejí jako 8bitová celá čísla.
Pixely RGB565 a YUV422 se vracejí jako 16bitová celá čísla – zabalené slovo.
Binární pixely se vracejí jako
0nebo1.Pixely JPEG a PNG se vracejí jako 8bitová celá čísla, vždy jeden bajt komprimovaného proudu. Tyto hodnoty jsou neprůhledné – jsou kousky komprimovaného kódování, nikoli pixely v jakémkoli obvyklém smyslu.
Indexová forma se hodí kódu, který už uvažuje v pojmech offsetů v bufferu: smyčce procházející každý pixel jednou, algoritmu, který potřebuje skákat vždy o jeden řádek, nebo kódu překládajícímu mezi rozvrženími bufferu. Kódu, který uvažuje v pojmech souřadnic x a y, lépe poslouží get_pixel a set_pixel; obě formy adresují tytéž pixely prostřednictvím odlišných myšlenkových modelů.
Image je také iterovatelný. for v in img: prochází buffer ve stejném pořadí podle řádků, vydávaje surové hodnoty vždy po jednom pixelu, a len(img) je počet pixelů u nekomprimovaných formátů nebo počet bajtů u komprimovaných proudů.
5.4.3. Proč je práce s jednotlivými pixely v Pythonu pomalá cesta¶
Praktická poznámka, k níž je třeba se upřímně přiznat. Procházení obrazu po jednotlivých pixelech z Pythonu je pomalé. Obraz ve stupních šedi 320 × 240 obsahuje 76 800 pixelů; volání get_pixel() na každém z nich ve smyčce for spustí miliony bytecode instrukcí MicroPythonu, aby vykonalo práci, kterou by ekvivalentní nativní metoda zvládla za několik set mikrosekund. To není malý faktor. Je to rozdíl mezi skriptem, který zpracovává snímky v reálném čase, a tím, který se plouží hluboko pod snímkovou frekvencí kamery.
Téměř každá metoda na rozhraní Image existuje proto, že existuje rychlejší, nativní verze běžného vzoru práce s jednotlivými pixely. Ze smyčky, která sčítá dva obrazy dohromady, se stává jediné nativní volání. Ze smyčky, která vyhlazuje každý pixel jeho zprůměrováním se sousedy, se stává další. Ze smyčky, která klasifikuje každý pixel oproti prahu, se stává třetí. Úkolem aplikace je většinou rozpoznat, která metoda nad celým obrazem odpovídá práci, kterou by smyčka vykonala, a sáhnout po ní místo ručního psaní smyčky.
Čtení a zápis na úrovni pixelu jsou stále tím správným nástrojem, když nic jiného nevyhovuje – vepsání konkrétního měření zpět do bufferu, navzorkování jedné pozice pro kalibrační krok, odladění hodnoty na známém místě. Jde o to, že jsou pomalou cestou, používanou tehdy, když metody nad celým obrazem nemají formu, kterou aplikace potřebuje, nikoli jako výchozí způsob práce s pixely.