5.4. Pikseleiden lukeminen ja kirjoittaminen

Useimmat kuvalle tehtävät operaatiot kätkevät pikselikohtaisen työnsä yhden metodikutsun sisään, missä jokaista pikseliä koskettavat silmukat suoritetaan natiivinopeudella. On kuitenkin tapauksia, joissa sovelluskoodi haluaa koskettaa yhtä tiettyä pikseliä suoraan: lukeakseen mitä tietyssä kohdassa on, kirjoittaakseen siihen uuden arvon, ottaakseen näytteen yhdestä pisteestä kalibrointivaihetta varten tai vianjäljittääkseen arvon tunnetussa sijainnissa. image-moduuli tarjoaa tämän tason pääsyn kahdella osoitusmuodolla, joista kumpikin sopii eri tapaan ajatella, missä pikseli sijaitsee.

5.4.1. Osoittaminen koordinaatilla

Luontevin muoto on se, jolle Coordinates jo kehitti sanaston: pikseli nimetään sen karteesisilla (x, y) -koordinaateilla. get_pixel() ottaa (x, y) ja palauttaa kyseisen kohdan arvon; set_pixel() ottaa saman (x, y) -arvon yhdessä arvon kanssa ja kirjoittaa sen.

Mitä nämä kutsut palauttavat tai hyväksyvät, riippuu kuvan muodosta. Harmaasävy-, binääri- ja Bayer-kuvat sisältävät yhden arvon pikseliä kohti – harmaasävyssä kirkkauden, binäärissä arvon 0 tai 1 ja Bayer-kuvassa yhden värikanavan näytteen – joten get_pixel() palauttaa yhden kokonaisluvun. RGB565 sisältää kolme värikanavaa pakattuna 16 bittiin, ja get_pixel purkaa ne oletuksena (r, g, b) -tupleksi, jossa jokainen kanava on kuvattu väliin 0255.

Oletustoiminnan voi kääntää kummassakin päässä. Antamalla rgbtuple=False get_pixel-kutsulle RGB565-kuvalle palautetaan raaka 16-bittinen pakattu sana – sama muoto, jonka lineaarinen indeksi palauttaa, ja tehokas muoto silloin, kun sovellus aikoo kirjoittaa saman pakatun arvon suoraan takaisin. Antamalla rgbtuple=True yksikanavaiselle kuvalle tehdään päinvastoin: tallennettu arvo muunnetaan RGB888-tupleksi ennen palauttamista, ja Bayer-kuvat käyvät läpi paikan päällä tehtävän debayer-vaiheen. Argumentti on olemassa, jotta kutsuva koodi voi pyytää pikseleitä yhtenäisessä väriavaruudessa riippumatta siitä, miten taustalla oleva kuva ne tallentaa.

get_pixel tai set_pixel eivät tue pakattuja kuvia – JPEG ja PNG. Niiden tavut eivät edusta pikseleitä tunnetuissa kohdissa, ja metodit nostavat virheen sen sijaan, että palauttaisivat arvon, joka ei tarkoittaisi mitään.

Käytännössä mallit näyttävät tältä:

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

Jos pyydetty (x, y) on kuvan ulkopuolella, get_pixel palauttaa None eikä set_pixel tee mitään. Tämä on tarkoituksella anteeksiantavaa: monet algoritmit kulkevat lähellä kuvan reunoja ja indeksoivat hetkellisesti alueen ulkopuolisia kohtia, ja hiljainen tyhjä toiminta on vähemmän häiritsevää kuin poikkeus joka kerta.

5.4.2. Osoittaminen lineaarisella indeksillä

Toinen muoto on osoittaa pikseleitä niiden sijainnin perusteella taustalla olevassa puskurissa. Muista puskurin asettelu: pikselit tallennetaan rivi riviltä, ensin kaikki ylimmän rivin pikselit, sitten kaikki seuraavan rivin pikselit ja niin edelleen alas pohjaan asti. Tämä järjestely tarkoittaa, että jokaisella pikselillä on yksi kokonaislukuindeksi, joka laskee arvosta 0 vasemmasta yläkulmasta ja kasvaa kunkin rivin edetessä. Koordinaatissa (x, y) olevalla pikselillä on lineaarinen indeksi y * width + x.

4 kertaa 3 -ruudukko soluja. Jokaisessa solussa on suuri lineaarinen indeksi 0:sta vasemmassa yläkulmassa 11:een oikeassa alakulmassa, sekä pieni (x, y) -tuple alapuolella. Sarakkeet on merkitty x yhtä kuin 0, 1, 2, 3 yläreunaa pitkin; rivit on merkitty y yhtä kuin 0, 1, 2 vasenta reunaa pitkin. Alapuolen kuvateksti antaa suhteen: lineaarinen indeksi yhtä kuin y kertaa leveys plus x.

Pikseleitä osoitetaan sekä karteesisilla (x, y) -koordinaateilla että lineaarisella indeksillä, joka kulkee puskuria rivi riviltä, vasemmalta oikealle.

image-moduuli tarjoaa tämän indeksin tavallisen Python-alaindeksimerkinnän kautta: img[i] lukee pikselin lineaarisessa indeksissä i, img[i] = value kirjoittaa sellaisen. Indeksimuoto palauttaa muodon raa’an tallennetun arvon, ei purettua tuplea, jonka get_pixel() palauttaa oletuksena. Tällä erolla on merkitystä, koska aiemmin valittu muoto määrää, miltä raaka arvo näyttää:

  • Harmaasävy- ja Bayer-pikselit palautuvat 8-bittisinä kokonaislukuina.

  • RGB565- ja YUV422-pikselit palautuvat 16-bittisinä kokonaislukuina – pakattuna sanana.

  • Binääripikselit palautuvat arvoina 0 tai 1.

  • JPEG- ja PNG-pikselit palautuvat 8-bittisinä kokonaislukuina, yksi tavu kerrallaan pakattua virtaa. Nämä arvot ovat läpinäkymättömiä – ne ovat paloja pakatusta koodauksesta eivätkä pikseleitä missään tavanomaisessa merkityksessä.

Indeksimuoto sopii koodille, joka jo ajattelee puskurin siirtymien kannalta: silmukalle, joka kulkee jokaisen pikselin läpi kerran, algoritmille, jonka täytyy hypätä rivi kerrallaan, tai koodille, joka kääntää puskuriasettelujen välillä. Koodille, joka ajattelee x- ja y-koordinaattien kannalta, sopivat paremmin get_pixel ja set_pixel; nämä kaksi muotoa osoittavat samoja pikseleitä eri ajattelumallien kautta.

Image on myös iteroitavissa. for v in img: kulkee puskurin läpi samassa rivijärjestyksessä tuottaen raaka-arvot pikseli kerrallaan, ja len(img) on pikselimäärä pakkaamattomissa muodoissa tai tavumäärä pakatuissa virroissa.

5.4.3. Miksi pikselikohtainen Python on hidas tie

Käytännön huomio, joka kannattaa myöntää rehellisesti. Kuvan läpikäyminen yksi pikseli kerrallaan Pythonista on hidasta. 320 × 240 -harmaasävykuva sisältää 76 800 pikseliä; get_pixel() -kutsu jokaiselle niistä for-silmukassa suorittaa miljoonia MicroPython-tavukoodikäskyjä tehdäkseen työn, jonka vastaava natiivimetodi voisi saada valmiiksi muutamassa sadassa mikrosekunnissa. Se ei ole pieni kerroin. Se on ero skriptin, joka käsittelee kehyksiä reaaliajassa, ja sellaisen välillä, joka ryömii hyvin alle kameran kehysnopeuden.

Lähes jokainen Image -pinnan metodi on olemassa, koska jollekin yleiselle pikselikohtaiselle mallille on olemassa nopeampi natiiviversio. Silmukka, joka laskee kaksi kuvaa yhteen, muuttuu yhdeksi natiivikutsuksi. Silmukka, joka tasoittaa jokaisen pikselin keskiarvoistamalla sen naapureidensa kanssa, muuttuu toiseksi. Silmukka, joka luokittelee jokaisen pikselin kynnysarvoa vasten, muuttuu kolmanneksi. Sovelluksen tehtävä on suurimman osan ajasta tunnistaa, mikä koko kuvan metodi vastaa silmukan tekemää työtä, ja tarttua siihen sen sijaan, että kirjoittaisi silmukan käsin.

Pikselitason luku ja kirjoitus ovat edelleen oikea työkalu silloin, kun mikään muu ei sovi – tietyn mittauksen paikkaaminen takaisin puskuriin, yhden kohdan näytteistäminen kalibrointivaihetta varten tai arvon vianjäljitys tunnetussa sijainnissa. Pointti on, että ne ovat hidas tie, jota käytetään silloin, kun koko kuvan metodeilla ei ole sovelluksen tarvitsemaa muotoa, eikä oletustapana operoida pikseleitä.