5.4. Čitanje i pisanje piksela¶
Većina operacija na slici skriva svoj rad po pikselu unutar jednog poziva metode, gdje se petlje koje obrađuju svaki piksel izvode izvornom brzinom. Postoje, međutim, slučajevi u kojima aplikacijski kod želi izravno pristupiti jednom određenom pikselu: pročitati što se nalazi na određenoj poziciji, upisati novu vrijednost u njega, uzorkovati jednu točku za korak kalibracije ili otkloniti pogrešku u vrijednosti na poznatoj lokaciji. Modul image izlaže tu razinu pristupa kroz dva oblika adresiranja, od kojih svaki odgovara drugačijem načinu razmišljanja o tome gdje se piksel nalazi.
5.4.1. Adresiranje po koordinati¶
Najprirodniji oblik je onaj za koji je poglavlje Koordinate već razvilo rječnik: imenovati piksel njegovim Kartezijevim (x, y). get_pixel() uzima (x, y) i vraća vrijednost na toj poziciji; set_pixel() uzima isti (x, y) zajedno s vrijednošću i upisuje je.
Što ti pozivi vraćaju ili prihvaćaju ovisi o formatu slike. Slike u sivim tonovima, binarne i Bayer slike nose jednu vrijednost po pikselu – svjetlinu za sive tonove, 0 ili 1 za binarne, jedan uzorak boje kanala za Bayer – pa get_pixel() vraća jedan cijeli broj. RGB565 nosi tri kanala boje upakirana u 16 bitova, a get_pixel ih prema zadanim postavkama raspakira u n-torku (r, g, b), pri čemu je svaki kanal preslikan u raspon 0 – 255.
Zadano ponašanje može se preokrenuti na bilo kojem kraju. Prosljeđivanje rgbtuple=False metodi get_pixel na RGB565 slici vraća sirovu 16-bitnu upakiranu riječ – isti oblik koji vraća linearni indeks i učinkovit oblik kada aplikacija namjerava istu upakiranu vrijednost upisati izravno natrag. Prosljeđivanje rgbtuple=True na jednokanalnoj slici čini suprotno: pohranjena vrijednost pretvara se u n-torku RGB888 prije vraćanja, pri čemu Bayer slike prolaze kroz korak debayer u hodu. Argument postoji kako bi pozivajući kod mogao tražiti piksele u jedinstvenom prostoru boja bez obzira na to kako ih osnovna slika pohranjuje.
Komprimirane slike – JPEG i PNG – ne podržavaju get_pixel ni set_pixel. Njihovi bajtovi ne predstavljaju piksele na poznatim pozicijama, pa metode podižu pogrešku umjesto da vrate vrijednost koja ne bi imala nikakvo značenje.
U praksi obrasci izgledaju ovako:
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
Ako je traženi (x, y) izvan slike, get_pixel vraća None, a set_pixel ne radi ništa. To je oprostivo po namjeri: mnogi algoritmi prolaze blizu rubova slike i nakratko indeksiraju pozicije izvan raspona, a tiha besposlena operacija manje je ometajuća od iznimke svaki put kada se to dogodi.
5.4.2. Adresiranje po linearnom indeksu¶
Drugi oblik je adresiranje piksela prema njihovoj poziciji u osnovnom međuspremniku. Prisjetite se rasporeda međuspremnika: pikseli su pohranjeni redak po redak, prvo svi pikseli gornjeg retka, zatim svi pikseli sljedećeg retka i tako dalje sve do dna. Taj raspored znači da svaki piksel ima jedan cjelobrojni indeks koji broji od 0 u gornjem lijevom kutu i povećava se duž svakog retka redom. Piksel na koordinati (x, y) ima linearni indeks y * width + x.
Pikseli se adresiraju i Kartezijevim (x, y) i linearnim indeksom koji prolazi međuspremnik redak po redak, slijeva nadesno.¶
Modul image izlaže taj indeks kroz uobičajenu Python notaciju indeksiranja: img[i] čita piksel na linearnom indeksu i, img[i] = value upisuje jedan. Ono što oblik indeksa vraća je sirova pohranjena vrijednost za format, a ne raspakirana n-torka koju get_pixel() vraća prema zadanim postavkama. Ta razlika je važna jer ranije odabrani format određuje kako izgleda sirova vrijednost:
Pikseli sivih tonova i Bayer pikseli vraćaju se kao 8-bitni cijeli brojevi.
Pikseli RGB565 i YUV422 vraćaju se kao 16-bitni cijeli brojevi – upakirana riječ.
Binarni pikseli vraćaju se kao
0ili1.Pikseli JPEG i PNG vraćaju se kao 8-bitni cijeli brojevi, po jedan bajt komprimiranog toka odjednom. Te su vrijednosti neprozirne – one su dijelovi komprimiranog kodiranja, a ne pikseli u bilo kojem uobičajenom smislu.
Oblik indeksa odgovara kodu koji već razmišlja u terminima pomaka u međuspremniku: petlji koja prolazi svaki piksel jednom, algoritmu koji treba preskakati po jedan redak odjednom ili dijelu koda koji prevodi između rasporeda međuspremnika. Kodu koji razmišlja u terminima x i y koordinata bolje služe get_pixel i set_pixel; oba oblika adresiraju iste piksele kroz različite mentalne modele.
Image je također iterabilna. for v in img: prolazi međuspremnik u istom poretku po redcima, vraćajući sirove vrijednosti jedan piksel po jedan, a len(img) je broj piksela za nekomprimirane formate ili broj bajtova za komprimirane tokove.
5.4.3. Zašto je Python po pikselu spori put¶
Praktična napomena vrijedna iskrenosti. Prolaženje slike jedan piksel po jedan iz Pythona je sporo. Slika u sivim tonovima 320 × 240 sadrži 76.800 piksela; pozivanje get_pixel() na svakom od njih u for petlji izvodi milijune MicroPython bytecode instrukcija da bi obavilo posao koji bi ekvivalentna izvorna metoda mogla dovršiti u nekoliko stotina mikrosekundi. To nije mali faktor. To je razlika između skripte koja obrađuje sličice u stvarnom vremenu i one koja puzi znatno ispod brzine sličica kamere.
Gotovo svaka metoda na sučelju Image postoji jer postoji brža, izvorna verzija uobičajenog obrasca po pikselu. Petlja koja zbraja dvije slike postaje jedan izvorni poziv. Petlja koja izglađuje svaki piksel uprosječujući ga sa susjedima postaje druga. Petlja koja klasificira svaki piksel prema pragu postaje treća. Zadatak aplikacije, najčešće, jest prepoznati koja metoda nad cijelom slikom odgovara poslu koji bi petlja obavila i posegnuti za njom umjesto da petlju piše ručno.
Čitanje i pisanje na razini piksela i dalje su pravi alat kada ništa drugo ne odgovara – vraćanje određenog mjerenja natrag u međuspremnik, uzorkovanje jedne pozicije za korak kalibracije, otklanjanje pogreške u vrijednosti na poznatoj lokaciji. Poanta je u tome da su to spori put, koji se koristi kada metode nad cijelom slikom nemaju oblik koji aplikacija treba, a ne kao zadani način rada s pikselima.