5.4. Képpontok olvasása és írása

A kép legtöbb művelete egyetlen metódushívás mögé rejti a képpontonkénti munkát, ahol a minden képpontot érintő ciklusok natív sebességgel futnak. Vannak azonban olyan esetek, amikor az alkalmazás kódja közvetlenül szeretne elérni egy adott képpontot: hogy elolvassa, mi van egy bizonyos pozícióban, hogy új értéket írjon bele, hogy egyetlen pontot mintavételezzen egy kalibrációs lépéshez, vagy hogy egy ismert helyen lévő értéket hibakeressen. Az image modul ehhez a szintű hozzáféréshez két címzési formát kínál, amelyek mindegyike más gondolkodásmódhoz illik arról, hogy hol helyezkedik el egy képpont.

5.4.1. Címzés koordináta szerint

A legtermészetesebb forma az, amelyhez a koordináták témaköre már kialakította a szókincset: egy képpontot a derékszögű (x, y) koordinátájával nevezünk meg. A get_pixel() egy (x, y) párt vesz át, és visszaadja az adott pozícióban lévő értéket; a set_pixel() ugyanazt az (x, y) párt veszi át egy értékkel együtt, és beírja azt.

Hogy ezek a hívások mit adnak vissza vagy fogadnak el, az a kép formátumától függ. A szürkeárnyalatos, bináris és Bayer képek képpontonként egyetlen értéket hordoznak – szürkeárnyalatos esetén egy fényerőt, bináris esetén egy 0 vagy 1 értéket, Bayer esetén egyetlen színcsatorna-mintát –, így a get_pixel() egyetlen egész számot ad vissza. Az RGB565 három színcsatornát csomagol 16 bitbe, és a get_pixel alapértelmezés szerint ezeket egy (r, g, b) rendezett hármassá bontja szét, ahol minden csatorna a 0255 tartományba kerül leképezésre.

Az alapértelmezett viselkedés mindkét végén megfordítható. Az rgbtuple=False átadása a get_pixel függvénynek egy RGB565 képen visszatér a nyers 16 bites csomagolt szóhoz – ugyanahhoz a formához, amelyet a lineáris index ad vissza, és ez a hatékony forma, ha az alkalmazás ugyanazt a csomagolt értéket fogja közvetlenül visszaírni. Az rgbtuple=True átadása egy egycsatornás képen az ellenkezőjét teszi: a tárolt érték RGB888 rendezett hármassá alakul, mielőtt visszaadná, a Bayer képek pedig egy helyben végzett debayer lépésen mennek keresztül. Az argumentum azért létezik, hogy a hívó kód egységes színtérben kérhesse a képpontokat, függetlenül attól, hogy a mögöttes kép hogyan tárolja őket.

A tömörített képeket – JPEG és PNG – a get_pixel és a set_pixel nem támogatja. Bájtjaik nem ismert pozíciókban lévő képpontokat reprezentálnak, és a metódusok inkább hibát váltanak ki, semmint hogy olyan értéket adjanak vissza, amelynek semmi értelme nem lenne.

A gyakorlatban a minták így néznek ki:

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

Ha a kért (x, y) a képen kívül esik, a get_pixel None értéket ad vissza, a set_pixel pedig nem csinál semmit. Ez szándékosan megengedő: sok algoritmus a kép szélei közelében halad, és rövid időre tartományon kívüli pozíciókat indexel, és egy csendes üres művelet kevésbé zavaró, mint egy kivétel minden alkalommal, amikor ez megtörténik.

5.4.2. Címzés lineáris index szerint

A másik forma az, hogy a képpontokat a mögöttes pufferben elfoglalt pozíciójuk szerint címezzük. Idézzük fel a puffer elrendezését: a képpontok soronként vannak tárolva, először a felső sor összes képpontja, majd a következő sor összes képpontja, és így tovább egészen az aljáig. Ez az elrendezés azt jelenti, hogy minden képpontnak van egyetlen egész indexe, amely a bal felső saroknál 0 értékről indul, és soronként haladva növekszik. A (x, y) koordinátájú képpont lineáris indexe y * width + x.

Egy 4-szer 3-as cellarács. Minden cella egy nagy lineáris indexet hordoz, amely a bal felső 0-tól a jobb alsó 11-ig tart, plusz egy kis (x, y) hármast alatta. Az oszlopok x egyenlő 0, 1, 2, 3 felirattal vannak ellátva a tetején; a sorok y egyenlő 0, 1, 2 felirattal a bal szélen. Egy felirat alatta megadja az összefüggést: a lineáris index egyenlő y szorozva a szélességgel plusz x.

A képpontok címezhetők mind derékszögű (x, y) koordinátával, mind pedig egy lineáris indexszel, amely soronként, balról jobbra járja be a puffert.

Az image modul ezt az indexet a szokásos Python indexelő jelöléssel teszi elérhetővé: az img[i] az i lineáris indexű képpontot olvassa, az img[i] = value pedig ír egyet. Amit az indexes forma visszaad, az a formátum nyers tárolt értéke, nem pedig a kibontott rendezett hármas, amelyet a get_pixel() alapértelmezés szerint visszaad. Ez a különbség azért számít, mert a korábban választott formátum dönti el, hogy a nyers érték hogyan néz ki:

  • A szürkeárnyalatos és Bayer képpontok 8 bites egész számokként térnek vissza.

  • Az RGB565 és YUV422 képpontok 16 bites egész számokként térnek vissza – a csomagolt szóként.

  • A bináris képpontok 0 vagy 1 értékként térnek vissza.

  • A JPEG és PNG képpontok 8 bites egész számokként térnek vissza, a tömörített adatfolyam egy-egy bájtjaként. Ezek az értékek átláthatatlanok – egy tömörített kódolás darabjai, nem pedig képpontok bármilyen szokásos értelemben.

Az indexes forma olyan kódhoz illik, amely már puffereltolások szerint gondolkodik: egy ciklus, amely minden képpontot egyszer bejár, egy algoritmus, amelynek soronként kell ugrania, vagy egy kódrészlet, amely pufferelrendezések között fordít. Az x és y koordinátákban gondolkodó kódot jobban kiszolgálja a get_pixel és a set_pixel; a két forma ugyanazokat a képpontokat címzi különböző gondolati modelleken keresztül.

Az Image iterálható is. A for v in img: ugyanabban a sorfolytonos sorrendben járja be a puffert, egyszerre egy képpont nyers értékét adva vissza, a len(img) pedig a képpontok száma tömörítetlen formátumok esetén, vagy a bájtok száma tömörített adatfolyamok esetén.

5.4.3. Miért lassú út a képpontonkénti Python

Egy gyakorlati megjegyzés, amellyel érdemes őszintének lenni. Egy kép képpontonkénti bejárása Pythonból lassú. Egy 320 × 240-es szürkeárnyalatos kép 76 800 képpontot tartalmaz; a get_pixel() meghívása mindegyikükre egy for ciklusban több millió MicroPython bájtkód-utasítást futtat le olyan munkához, amelyet egy ezzel egyenértékű natív metódus néhány száz mikroszekundum alatt elvégezne. Ez nem kis tényező. Ez a különbség egy olyan szkript között, amely valós időben dolgozza fel a képkockákat, és egy másik között, amely jóval a kamera képkockasebessége alatt vánszorog.

Az Image felületén szinte minden metódus azért létezik, mert van egy gyorsabb, natív változata egy gyakori képpontonkénti mintának. Egy ciklus, amely két képet ad össze, egyetlen natív hívássá válik. Egy ciklus, amely minden képpontot simít azzal, hogy a szomszédaival átlagolja, egy másikká. Egy ciklus, amely minden képpontot egy küszöbértékhez képest osztályoz, egy harmadikká. Az alkalmazás feladata legtöbbször az, hogy felismerje, melyik teljes képes metódus felel meg annak a munkának, amelyet a ciklus végzett volna, és inkább azt használja, ahelyett hogy kézzel írná meg a ciklust.

A képpont szintű olvasás és írás továbbra is a megfelelő eszköz, amikor semmi más nem illik – egy adott mérés visszaillesztése a pufferbe, egyetlen pozíció mintavételezése egy kalibrációs lépéshez, egy ismert helyen lévő érték hibakeresése. A lényeg az, hogy ezek a lassú út, amelyet akkor használunk, amikor a teljes képes metódusoknak nincs meg az a formája, amelyre az alkalmazásnak szüksége van, nem pedig a képpontokon való műveletvégzés alapértelmezett módjaként.