WEB oldal : https://docs.opencv.org/
Hivatalos angol nyelvű elektronikus dokumentáció.
Az OpenCV Python nyelvből való használatához szükséges csomagként való előzetes telepítése.
Ha ez megtörtént, akkor a programunk elején beilleszthetjük a definíciókat az import paranccsal. Az OpenCV csomag neve például cv2, ezen keresztül érhetjük majd el a függvényeket és a definíciókat.
A csomagban definiált version nevű sztring változó az OpenCV verziószámát adja, amit a programunkban a print() függvénnyel ki is íratunk a konzolra.
Digitális képek rendszerint külső fájlból érkeznek a programjainkba, ahol feldolgozzuk, és közben megjelenítjük őket.
Paraméterként a beolvasandó fájl nevét kell megadnunk. Ez tartalmazhat akár teljes vagy relatív elérési utat is. Ha ilyet nem adunk meg, akkor a képfájlnak a programunkkal azonos könyvtárban kell elhelyezkednie.
Az elterjedt PNG és JPG fájlok kezelése támogatott. Az imread()
függvény további paraméterezésével a szürkeárnyalatos konverziónál ismerkedünk majd meg.
A beolvasás eredménye egy n-dimenziós Numpy tömb lesz. Ennek megkaphatjuk a dimenzióit a shape attribútumával.
A függvény első paramétere egy sztring, amely egyrészt az ablak fejlécében lesz látható, másrészt ez fogja egyértelműen azonosítani az ablakot.
Az OpenCV ugyanis egyszerre akár sok képablak kezelését is el tudja végezni, vagyis az azonosítás fontos.
Ebből következik, hogy nem tudunk két egyező nevű ablakot létrehozni.
Ha adott nevű ablak még nem létezik, akkor az OpenCV létrehozza és megjeleníti. Ha létezik, akkor a tartalmát cseréli le.
A második paraméter a megjelenítendő, Numpy reprezentációjú kép.
Az imshow()
függvény végrehajtása után a programunk futása rögtön folytatódik.
A program kilépésekor minden ablak eltűnik.
Emiatt szükséges, hogy egyes eredmények megjelenítése után várakozzunk a folytatásra.
Legegyszerűbben a waitKey()
függvénnyel tehetjük ezt meg. A nevéből kitalálható, hogy billentyűlenyomásra vár. Másrészt megadhatunk egy egész számértéket paraméterként, ami a maximális várakozási időt adja meg ezredmásodpercben.
Ha addig nem történik billentyűlenyomás, akkor visszatér. A waitKey() visszaadja a lenyomott billentyű kódját is.
Az egyik legegyszerűbb a kép tükrözése különféle középtengelyeire. Ezt a flip()
függvény végzi.
Ha értéke pozitív (példánkban 1), akkor a függőleges középtengelyre tükröz. 0 érték esetén a vízszintes középtengelyt használja, negatív érték esetén pedig mindkettőre tükröz.
Eredménye egy új Numpy tömb lesz.
Képet az imwrite()
függvénnyel írhatunk ki fájlba. Első paramétere a kiírandó fájl neve, akár elérési úttal, hasonlóan a beolvasáshoz. A fájl formátumát a kiterjesztés alapján határozza meg.
Célszerű png vagy jpg kiterjesztést választani. A második paraméter a kiírandó képet reprezentáló Numpy tömb.
A destroyAllWindows()
függvény az összes OpenCV képablakot bezárja.
A program legvégén ezt célszerű használnunk, hogy érvényes visszatérési kóddal térjen vissza a programunk.
## betölt külső fájlból egy képet, megjeleníti, billentyűlenyomások után több tengely szerint tükrözi,
## és fájlba menti az eredményt.
# OpenCV modul definíciók importálása
import cv2
# OpenCV verziószám kiíratása
print('OpenCV verzió:', cv2.__version__)
# Kép beolvasása fájlból
img = cv2.imread('kep_file.jpg')
# Képméret kiíratása konzolra
print(img.shape)
# Kép megjelenítése ablakban
cv2.imshow('image', img)
cv2.waitKey(0)
# Tükrözés a függőleges középtengelyre és megjelenítés
flipped = cv2.flip(img, 1)
cv2.imshow('image', flipped)
cv2.imwrite('OpenCV-logo-flipped.png', flipped)
cv2.waitKey(0)
# Tükrözés mindkét középtengelyre és megjelenítés
flipped2 = cv2.flip(img, -1)
cv2.imshow('image', flipped2)
cv2.waitKey(0)
# Összes ablak bezárása
cv2.destroyAllWindows()
Számos alacsony szintű képfeldolgozó művelet szürkeárnyalatos intenzitásképeken kerül definiálásra.
Szürkeárnyalatos fotók esetén leggyakrabban a [0, 255] intenzitástartomány használt.
A 0 érték jelenti a fekete, a 255 a fehér színt. A köztes értékek a feketéből fehérbe tartó szürke átmenetet jelentik. A fotóink, képeink rendszerint színesek, ezeket át kell alakítanunk szürkeárnyalatosra a fent említett műveletek végrehajtása előtt.
Az OpenCV többféle lehetőséget is biztosít erre.
A képmátrix típusát a fájl tartalma határozza meg. A színes képek 3 csatornás BGR mátrixként, a szürkekárnyalatosak 1 csatornás szürkeárnyalatos képként kerülnek beolvasásra.
Mindenképpen ezt használjuk, ha 4 csatornás (RGBA) képet olvasunk be!
A beolvasott képmátrix 1 csatornás szürke lesz.
Ez az alapértelmezett működés. A beolvasott képmátrix 3 csatornás BGR mátrix lesz. Ha szürkeárnyalatos a bement, akkor mindhárom csatornára a beolvasott szürkeárnyalatos értékek kerülnek beállításra.
Eltávolítja az esetleges további (például átlátszósági) csatornákat.
További paramétermegadási lehetőségeket az OpenCV dokumentációban találunk az ImreadModes
kulcsszónál.
A másik lehetőség a cvtColor() függvény használata, ami számos színtér reprezentáció közötti átalakítást képes elvégezni.
Megjegyezzük, hogy a szürkeárnyalatos fényességi érték (Y) a három színcsatorna (R, G, B) súlyozott átlagaként számítható az alábbi képlet szerint:
Y=0.299⋅R+0.587⋅G+0.114⋅B
## példaprogramban a szürkeárnyalatos konverzió használatát láthatjuk.
## kiírjuk a konzolra a Numpy képmátrixok méretét is.
# OpenCV modul definíciók importálása
import cv2
## -------------------------- V1
# Kép beolvasása fájlból szürkeárnyalatosként
imgr = cv2.imread('kep_file.jpg', cv2.IMREAD_GRAYSCALE)
# Képméret kiíratása konzolra
print(imgr.shape)
# Kép megjelenítése ablakban
cv2.imshow('image', imgr)
cv2.waitKey(0)
## -------------------------- V2
# Kép beolvasása fájlból
imgc = cv2.imread('kep_file.jpg', cv2.IMREAD_COLOR)
print(imgc.shape)
imgr2 = cv2.cvtColor(imgc, cv2.COLOR_BGR2GRAY)
print(imgr2.shape)
# Kép megjelenítése ablakban
cv2.imshow('image', imgr2)
cv2.waitKey(0)
# felszabadítás
cv2.destroyAllWindows()
A számítógépek képernyőjén megjelenő színek a vörös, zöld és kék színkomponensek intenzitásának elegyítéséből állnak elő. A fotók betöltés után jellemzően ezen a három színcsatornán tárolódnak.
Az RGB reprezentáció hasznos a digitális kijelzőn való megjelenítéshez. A színek leírására számos más színtér is létezik, amelyek bizonyos tulajdonságai jobbak az RGB-nél. Például a színkódok euklidészi távolsága (háromelemű vektorokként értelmezve őket) és a színérzet nem felel meg egymásnak az RGB színtérben, erre alkalmasabb a CIELab színtér. Nyomdai előkészítésnél a CMYK színtér használatos.
Megjegyezzük, hogy a többcsatornás színes képek esetén a csatornák jellemző sorrendje a vörös (R), zöld (G), kék (B). Az OpenCV belső reprezentációja viszont a BGR sorrendet valósítja meg!
Az OpenCV számos színtér reprezentációt ismer, és biztosítja az ezek közötti átalakításokat a cvtColor() függvénnyel. A két paramétere közül az első az átalakítandó képmátrix. A második az átalakítás módját jelenti, meghatározza, melyik színtérből melyikbe alakítunk. Néhány ilyen érték megtalálható az angol nyelvű dokumentációban.
A függvény eredményeként az átalakított képet kapjuk.
Példák színes->szürkeárnyalatos konverziós konstansokra: cv2.COLOR_BGR2GRAY, cv2.COLOR_GRAY2BGR, cv2.COLOR_RGB2GRAY, cv2.COLOR_GRAY2RGB
Általánosan a cv2.COLOR_honnan2hova alakot használhatjuk,
Akár RGB->BGR és BGR->RGB átalakításra (csatornák sorrendjének cseréje) is lehetőség van.
Eredményként az átalakított képet kapjuk.
Figyeljünk arra, hogy ha egy szürkeárnyalatos kép alakítunk "színessé", a megjelenő kép továbbra is szürkeárnyalatos lesz, csak BGR színreprezentációban.
Megjegyezzük, hogy egy többcsatornás képet színcsatornánként önálló egycsatornás képekké alakíthatjuk a cv2.split() függvénnyel.
# OpenCV2 képbeolvasás, színtér konverzió minta
# OpenCV modul definíciók importálása
import cv2
# Színes kép beolvasása fájlból
imgc = cv2.imread('kep_file.jpg', cv2.IMREAD_COLOR)
cv2.imshow('color', imgc)
# Szürkeárnyalat
imgr = cv2.cvtColor(imgc, cv2.COLOR_BGR2GRAY)
cv2.imshow('grayscale', imgr)
print('grayscale:', imgr.shape)
cv2.waitKey(0)
# Szürkéből "színes"
imgc2 = cv2.cvtColor(imgr, cv2.COLOR_GRAY2BGR)
cv2.imshow('gray2bgr', imgc2)
print('gray2bgr:', imgc2.shape)
cv2.waitKey(0)
# Áttérés Lab színtérbe
# L: szürkeárnyalat
# a, b: kromatikusok (szín információ)
imgLab = cv2.cvtColor(imgc, cv2.COLOR_BGR2Lab)
print('imgLab:', imgLab.shape)
cv2.waitKey(0)
# Színcsatornákra bontás
L, a, b = cv2.split(imgLab)
cv2.imshow('L', L)
cv2.imshow('a', a)
cv2.imshow('b', b)
cv2.waitKey(0)
# felszabadítás
cv2.destroyAllWindows()
A mátrix elemei a képpontok, vagy az angol terminológiából átvéve a pixelek. A reprezentáció fontosabb tulajdonságai az alábbiak.
A képpontokat sor (Y) és oszlop (X) indexeikkel címezhetjük.
Az origó általában a bal felső sarok. Jellemzően ez a [0, 0] indexű elem. Egyes rendszerek, például a Matlab esetén ez az [1, 1].
pixel = image[y, x]
Vagyis [sor, oszlop] sorrendben kell megadni a koordinátákat. Az eredmény skalár érték (1 csatornás kép esetén) vagy 3 elemű tömb (BGR színes kép) lesz. A 3 elemű tömböt tovább lehet indexelni, például a vörös színkomponens elérése: R = image[y, x, 2]
A Numpy tömb attribútumai információval szolgálnak többek között a mátrix dimenzióról (ndim), dimenziók szerinti méretéről (shape), és a elemtípusáról (dtype).
Megjegyezzük, hogy ez a fajta képpont elérés rendkívül lassú, nagy mennyiségű adatelérést nem így érdemes csinálni. Használhatjuk a fejlettebb Numpy manipulációkat, vagy megírhatjuk a szükséges függvényeket C++-ban.
A Numpy képmátrix reprezentációban az értékadás (az = operátorral) referencia szerint történik!
Ez azt jelenti, hogy onnantól kezdve mindkét változó ugyanarra a képmátrixra hivatkozik, az egyiken keresztüli változás a másikra is hatással van!
Ha valódi másolatot szeretnénk készíteni egy képmátrixról, akkor hívjuk meg a mátrix objektum copy() függvényét!
A referencia takarékosabb a memóriával, de például változás követés esetén muszáj lehet másolatot készíteni.
## A példánkban az image és az image_ref ugyanarra képmátrixra fog hivatkozni.
## Az image_copy egy új képmátrixot kap, az eredetivel megegyező tartalommal.
# OpenCV modul definíciók importálása
import cv2
# esemény k ezelő (egér klikkelés)
def mouse_click(event, x, y, flags, param):
# Globalis valtozo atvetele
global image
if event == cv2.EVENT_LBUTTONDOWN:
# (x, y) színérték kiírása
print('Pixel = ', image[y, x])
# Ha 3 csatornás a kép
if image.ndim == 3:
print('R = ', image[y, x, 2])
cv2.imshow('image', image)
# kép állomány kiválasztása
image = cv2.imread('kep_file.jpg', cv2.IMREAD_COLOR) ## szines
# image = cv2.imread('kep_file.jpg', cv2.IMREAD_GRAYSCALE) ## Szűrke
# kép aadatok megjelenítése
print('Kép dimenziói: ', image.ndim)
print('Kép mérete:', image.shape)
print('Kép pixeltípusa: ', image.dtype)
# Kép megjelenítése
cv2.imshow('image', image)
# a képpontok elérését mutatja be. Betöltünk egy képet, megjelenítjük ablakban,
# valamint az ablakhoz egy OpenCV egéresemény-kezelőt kapcsolunk
# Egerkezelo callback fuggveny beallitasa az ablakhoz
cv2.setMouseCallback('image', mouse_click)
## Ha az image nevű képmegjelenítő ablakban kattintunk vagy mozgunk az egérrel,
## akkor a rendszer meghívja a mouse_click() függvényünket, megfelelő paraméterezéssel.
# Kilepes billentyunyomasra
cv2.waitKey(0)
# felszabadítás
cv2.destroyAllWindows()
A Numpy lehetőséget ad a mátrixok szeletelésére, vagyis indextartományokkal megadott részeinek elérésére.
A szeletelés eredménye közvetlenül felhasználható, vagy új változóban eltárolható.
Fontos, hogy ez a részmátrix továbbra is az eredeti mátrixra hivatkozik, változtatása az eredetin is megjelenik!
Ha tényleges másolatot szeretnék, akkor használjuk a copy() függvényt!
Az indextartományok megadásánál az adott dimenzióra vonatkozó kezdő és záró koordinátát kell megadni, kettősponttal elválasztva.
Fontos! A záró koordináta érték már nem vesz részt a kivágásban! A következő példa egy 90x90 méretű eredményt ad.
cropped = image[82:172, 396:486]
Az intervallum megadásakor (a kettőspont előtt/után) a kezdő és/vagy a záró koordináta érték elhagyható. Ekkor az adott dimenzió szerinti minimális, illetve maximális koordináta értéket veszi a rendszer.
Például az alábbi értékadással ki tudjuk nullázni a cropped mátrix minden elemét. cropped[:, :] = 0
Jobbról balra haladva a teljes dimenziót felhasználó indexeket elhagyhatjuk.
Egy kisebb mátrixot be tudunk másolni egy nagyobba. A nagyobbnál indextartományokat kell megadni.
(A kisebbnél is lehet.) A két (rész)mátrix méretnek meg kell egyeznie egymással!
image[10:100, 20:110] = cropped
# OpenCV modul definíciók importálása
import cv2
image = cv2.imread('gyava_oroszlan.jpg')
# Képkivágás; sor és oszlop tartomány megadása
cropped = image[82:172, 396:486]
# Kivágott rész képbe másolása, új helyre
image[10:100, 20:110] = cropped
cv2.imshow('image', image)
cv2.imshow('cropped', cropped)
cv2.waitKey(0)
Egy többcsatornás kép szétbontható különálló intenzitás csatornákra az OpenCV split() függvényével.
Eredményképpen egy felsorolási (tuple) típusú Python objektumot kapunk, amely BGR színes kép esetén három Numpy tömböt sorol fel.
imgc = cv2.imread('GolyoAlszik_rs.jpg', cv2.IMREAD_COLOR) b, g, r = cv2.split(imgc)
Ne felejtsük el, hogy az OpenCV BGR sorrendben tárolja a színcsatornákat!
Ezután a b, g és r tömbökkel mint szürkeárnyalatos, egycsatornás képekkel dolgozhatunk tovább.
imgc2 = cv2.merge((b, g, r))
A látszat ellenére a függvénynek egy paramétere van, a csatornák felsorolása tuple objektumként, emiatt kell kettős zárójelezés, ahogyan színkódolás mutatja.
# Modul definíciók importálása
import cv2
# Kép beolvasása fájlból
imgc = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_COLOR)
cv2.imshow('color', imgc)
# Szürkeárnyalat
imgr = cv2.cvtColor(imgc, cv2.COLOR_BGR2GRAY)
cv2.imshow('grayscale', imgr)
cv2.waitKey(0)
# Színcsatornákra bontás és megjelenítés
b, g, r = cv2.split(imgc)
cv2.imshow('red', r)
cv2.imshow('green', g)
cv2.imshow('blue', b)
cv2.waitKey(0)
# Vörös csatorna nullázása és BGR kép előállítása
r[:, :] = 0
imgc2 = cv2.merge((b, g, r))
cv2.imshow('0,g,b', imgc2)
cv2.waitKey(0)
# Színcsatorna ablakok bezárása
cv2.destroyWindow('red')
cv2.destroyWindow('green')
cv2.destroyWindow('blue')
cv2.destroyWindow('0,g,b')
# Áttérés Lab színtérbe
# L: szürkeárnyalat
# a, b: kromatikusok (szín információ)
imgLab = cv2.cvtColor(imgc, cv2.COLOR_BGR2Lab)
L, a, b = cv2.split(imgLab)
cv2.imshow('L', L)
cv2.imshow('a', a)
cv2.imshow('b', b)
cv2.waitKey(0)
# felszabadítás
cv2.destroyAllWindows()
# Modul definíciók importálása
import numpy as np
import cv2
# 320x200x3 méretű Numpy tömb létrehozása RGB színes képnek
img = np.ndarray((200, 320, 3), np.uint8)
# Feltöltés 192 (világosszürke) színnel
img.fill(192)
# Kör rajzolása az (50, 100) középponttal, 40 sugárral, vörös színnel
cv2.circle(img, (50, 100), 40, (0, 0, 192), -1)
##Vonal
cv2.line(img, (55, 55), (88, 88), (20, 30, 150), 2)
## Téglalap
cv2.rectangle(img, (25, 25), (91, 91), (150, 20, 30), 2)
## Ellipszis
#cv2.ellipse(img, (77, 77), (200, 200), 10, 10, (100, 10, 10), 3)
## A főtengely szögének kezdőállása az X-tengely iránya. A pozitív körüljárási irány az óramutató járásával ellentétes.
## Az értékek fokban értelmeződnek. A kezdőszög és zárószög megadásával elliptikus ívet rajzolhatunk.
## Szöveg elhelyezése
## cv2.putText(img, 'Szoveg', (20, 20), FONT_HERSHEY_SIMPLEX, 24, (50, 150, 50), v3, vonaltipus)
# Kép megjelenítése ablakban
cv2.imshow('image', img)
cv2.waitKey(0)
# Kép mentése fájlba
cv2.imwrite('ocv_test1_out.png', img)
# Összes ablak bezárása
cv2.destroyAllWindows()
A képeink gyakran fájlből kerülnek beolvasásra, illetve az eredmények fájlba íródnak ki.
Mint láttuk a képmátrix tárolása képmátrixban, számértékekkel történik, így a fájlba írás megoldható.
Figyelnünk célszerű viszont a képi adat tárolási mennyiségigényére. Például egy 24 megapixeles (24 millió képpont érzékelésére képes szenzorral rendelkező) kamerával készült kép 3 bájtos RGB képpont-kódolást feltételezve 24 x 3 = 72 megabájtot foglal. 15 darab ilyen képpel már 1 GB felett járunk!
A fájlban történő tároláskor ezért célszerű kihasználni a képi adatban található redundanciákat. Például szöveget ábrázoló képernyőképek esetén jellemzően nagyméretű homogén, vagyis egyforma színnel rendelkező képpont területek találhatók. Futáshossz kódolással ezt tömörebben leírhatjuk.
Egy másik megközelítés esetén a mátrixban gyakrabban előforduló számértékek rövidebb (akár 1 bites), a ritkán előfordulók hosszabb (sok bitből álló) kódszavat kapjanak.
Ilyet állíthatunk elő például a Huffman kódolással. Fotók esetén az úgynevezett transzformációs kódolások a népszerűek (DCT, wavelet). Ezek nagymértékű méretcsökkenést képesek okozni, viszont ekkor a képmátrix eredeti állapota nem, csak egy közelítése állítható vissza. Ezt a megközelítést veszteséges tömörítésnek nevezik.
Pontosan visszakapjuk a képmátrix elemeinek intenzitás- vagy színértékeit. Nagyobb fájlméret. Vektoros vonalrajzokról, szövegekről, képernyőképekről készült képek, valamint orvosi képek esetén használatos. Néhány ilyen fájlformátum: PNG, RAW, TIFF, BMP, DICOM.
Kisebb-nagyobb eltérések előfordulhatnak az eredeti szín/intenzitásértékekhez képest, vagyis nem pontosan ugyanazt a színértéket kapják a kitömörítés után, mint ami eredetileg volt. Sokkal kisebb fájlméret. Állítható veszteségaránnyal dolgozhatunk. Fotók esetén javasolt.
De-facto szabvány formátum: JPEG. A legtöbb digitális kamera hardver szinten támogatja. Nyers (RAW) formátum jellemzői (veszteségmentes, profibb eszközökön)
„Előhívatlan”, nyers adat, az érzékelő által mért közvetlen számértékek. Geometriai korrekció nélkül (például a lencsetorzítás hatása látható).
Az expozíció bizonyos paraméterei utólagosan állíthatók (színhőmérséklet, világosság, ...).
absdiff(): abszolút különbség.
add(): összeadás.
addWeighted(): súlyozott összeadás.
bitwise_and(), bitwise_not(), bitwise_or(), bitwise_xor(): bitenként számolt logikai ÉS, NEM, VAGY, KIZÁRÓ VAGY.
divide(): osztás.
exp(): exponens.
log(): logaritmus.
min(), max(): képpontonkénti minimum, maximum (az eredmény képmátrix lesz).
multiply(): szorzás.
normalize(): értékkészlet normalizálás.
pow(): hatványozás.
scaleAdd(): skálázott összeadás.
subtract(): kivonás.
mean(): átlag.
meanStdDev(): átlag és szórás.
minMaxIdx(): minimum és maximum értéke és indexe.
sum(): összeg.
Maszkok segítségével meg tudjuk határozni, hogy a kép mely részletének feldolgozása fontos a számunkra.
A maszk jellemzően egy bináris vagy szürkeárnyalatos képnek tekinthető, ahol az intenzitásértékek jelzik a kapcsolódó képpontok tulajdonságait: része-e az érdekes régiónak (nullától különböző, például 1 vagy maximális érték) vagy sem (0 érték), amennyiben igen, milyen mértékben? A maszk kép mérete megegyezik a kapcsolódó kép méretével.
Maszkokat tudunk létrehozni manuális rajzolással vagy szegmentáló algoritmusokkal, amelyekkel később foglalkozunk.
Jelenleg elfogadjuk, hogy ilyenek rendelkezésre állnak, és azt vizsgáljuk, hogyan tudunk ezekkel dolgozni.
A maszkokkal való munka általános megközelítése, ha for ciklusokkal bejárjuk a teljes képet, és képpontonként ellenőrizzük a kritériumot.
Látni fogjuk, hogy ez rendkívül lassú, helyette célszerű Numpy vagy OpenCV aritmetikával dolgozni! A sebességkülönbség több százszoros is lehet!
mport cv2
import time
# Kép, maszk és maszk körvonal beolvasása.
img = cv2.imread('car_numberplate_rs.jpg', cv2.IMREAD_COLOR)
mask = cv2.imread('car_numberplate_rs_mask.png', cv2.IMREAD_COLOR)
edge = cv2.imread('car_numberplate_rs_mask_edge.png', cv2.IMREAD_GRAYSCALE)
# Képmátrix méretek ellenőrzése. Az assert után egy feltételt adhatunk meg.
# Ha az nem teljesül, akkor a program futása hibaüzenttel megszakad.
# A shape attribútum első két elemét nézzük, ami a sorok és oszlopok számát veszi csak figyelembe.
assert img.shape[0:2] == mask.shape[0:2]
assert img.shape[0:2] == edge.shape[0:2]
Kép és maszk megjelenítése.
cv2.imshow('img', img)
cv2.imshow('mask', mask)
# Színes képen a maszkon kívüli terület kinullázása és megjelenítése. Mivel a feltételezzük,
# hogy a maszk kép 0 és 255 értékeket tartalmaz, a logikai ÉS művelet a maszkon belüli,
# vagyis a 255 értékkel fedésbe kerülőket tartja meg, a többit nullázza.
masked = cv2.bitwise_and(img, mask)
cv2.imshow('masked', masked)
cv2.waitKey(0)
# Eredeti színes képen a maszkon belüli rész fehér színűre változtatása.
img_roi = img.copy()
img_roi[mask > 0] = 255
cv2.imshow('img_roi', img_roi)
cv2.waitKey(0)
# Vörös színű maszk körvonal elhelyezése az eredeti képen.
# Kettős for ciklussal bejárjuk a maszkot, a 255 értékek helyén az eredeti képmátrixban vörös színt állítunk be.
#Vegyük észre, hogy nem elegendő a vörös csatornára értéket írni,
# a másik kettő kinullázása is szükséges a korrket vörös szín előállításához!
#A ciklust időméréssel látjuk el.
img_edge = img.copy()
start_time = time.perf_counter()
for y in range(0, img.shape[0]):
for x in range(0, img.shape[1]):
if edge[y, x] > 0:
img_edge[y, x] = [0, 0, 255]
end_time = time.perf_counter()
print((end_time - start_time) * 1000.0, "ezredmásodperc.")
cv2.imshow('img_edge', img_edge)
cv2.waitKey(0)
# Vörös színű körvonal rávetítés OpenCV logikai függvényekkel, időméréssel.
# A sokkal gyorsabb működés miatt célszerű így eljárnunk! Kb. 250-szeres gyorsulás mérhető!
# A működésének a lényege, hogy az eredeti BGR képet csatornákra bontjuk, a vörös csatornába
# a VAGY művelettel beégetjük a vörös körvonalat (ahol a maszk körvonal kép 255 értékű,
# ott a vörös csatorna is ezt az értéket veszi fel, ahol a körvonal 0 értékű,
# ott megmarad az eredetileg ott szereplő érték), a másik két csatornán nullázzuk ugyanezen helyeket
# (a ~ operátor a bitenkénti negálás, vagyis ellentettre fordítás,
# ezzel ÉS-elve kapjuk a körvonal pontokban a nulla eredményt).
img_ocv_edge = img.copy()
start_time = time.perf_counter()
b, g, r = cv2.split(img_ocv_edge)
r = cv2.bitwise_or(r, edge)
g = cv2.bitwise_and(g, ~edge)
b = cv2.bitwise_and(b, ~edge)
img_ocv_edge = cv2.merge((b, g, r))
end_time = time.perf_counter()
print((end_time - start_time) * 1000.0, "ezredmásodperc.")
cv2.imshow('img_ocv_edge', img_ocv_edge)
cv2.waitKey(0)
# Program szabályos lezárása.
cv2.destroyAllWindows()
Az OpenCV a calcHist() függvénnyel biztosítja a hisztogram meghatározást.
hist = calcHist([images], [channels], mask, [histSize], [ranges])
Eredményképpen egy NumPy tömböt kapunk (hist).
Paraméterként meg kell adnunk a képmátrixo(ka)t Python tömbként felsorolva ([images]); a felhasználandó csatornák indexeit, szintén Python tömbként ([channels]); egy maszkot (mask), ha a kép csak egy részére végeznénk el a számítást (None, ha a teljes kép érdekes); a hisztogram rekeszeinek számát ([histSize]), valamint az intenzitástartományt ([ranges]). Amennyiben az intenzitástartomány bővebb, mint a megadott hisztogram rekeszek száma, akkor a függvény összevon intenzitásértékeket.
Az eredmény egy histSize méretű NumPy oszlopvektor lesz. Ezt grafikonon ábrázolhatjuk például a matplotlib csomag pyplot alcsomagjával, aminek a definícióit plt néven illesztjük be a forráskódba.
A figure() függvényével kezdhetünk új ábrát rajzolni, megadva a méreteit.
Az ábrazolandó adatot többféle módon jeleníthetjük meg.
A vlines() függvény függőleges vonallal ábrázolja az értékeket. Egycsatornás hisztogram ábrázolására általános esetben a legjobban használható. A plot() függvénnyel a függvényértékek egyenes vonallal kerülnek összekötésre. Többcsatornás hisztogram ábrázolásra megfelelő, csatornánként különböző színnel, amennyiben nincs nagy "ugrás" a szomszédos intenzitásértékek előfordulási gyakoriságában. A scatter() segítségével pontfelhőként rajzolhatjuk ki. Többcsatornás hisztogram ábrázolásoknál hasznos, ahol például valamilyen hisztogram művelet hatására több intenzitásérték is 0 gyakorisággal fordul elő, így függvényként plot()-tal nem ábrázolható szépen. A bar() oszlopdiagramot rajzol. Használata kisebb darabszámú hisztogramok esetén célszerű. Túl sűrű megjelenítésnél az oszlopok átfedik egymást. Az xlim() és ylim() az X és Y tengelyek menti ábrázolandó minimum és maximum értékeket adja meg.
A show() jeleníti meg az előkészített ábrát. A program futása felfüggesztődik, míg az ábrát be nem zárjuk.
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('img', img)
print('Min: {}'.format(np.min(img)))
print('Max: {}'.format(np.max(img)))
# 256 elemű hisztogram
hist_gray = cv2.calcHist([img], [0], None, [256], [0, 256])
print(hist_gray.shape)
plt.figure(figsize=(4,2), dpi=100)
# plt.plot(hist_gray)
# plt.scatter(np.arange(256), hist_gray, s=1)
# plt.bar(np.arange(256), np.transpose(hist_gray.astype(int))[0])
plt.vlines(np.arange(256), 0, hist_gray)
# plt.xlim([0, 255])
plt.ylim([0, np.max(hist_gray)])
plt.tight_layout(pad=0)
plt.show()
# 16 elemű hisztogram
hist_gray2 = cv2.calcHist([img], [0], None, [16], [0, 256])
print(hist_gray2.shape)
plt.figure(figsize=(4,2), dpi=100)
# plt.plot(hist_gray2)
# plt.scatter(np.arange(16), hist_gray2, s=5)
# plt.bar(np.arange(16), np.transpose(hist_gray2.astype(int))[0])
plt.vlines(np.arange(16), 0, hist_gray2)
# plt.xlim([0, 15])
plt.ylim([0, np.max(hist_gray2)])
plt.show()
# Többcsatornás képek esetén vagy egycsatornásra alakítunk, vagy csatornánként készítünk hisztogramot.
# Ezeket külön, vagy akár egy diagramban is ábrázolhatjuk, ahogyan az alábbi példában láthatjuk.
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_COLOR)
cv2.imshow('img', img)
hist_b = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_g = cv2.calcHist([img], [1], None, [256], [0, 256])
hist_r = cv2.calcHist([img], [2], None, [256], [0, 256])
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hist_gray = cv2.calcHist([img_gray], [0], None, [256], [0, 256])
plt.figure(figsize=(4,2), dpi=100)
plt.vlines(np.arange(256), 0, hist_r, color='r')
# plt.xlim([0, 255])
plt.ylim([0, np.max(hist_r)])
plt.show()
plt.figure(figsize=(4,2), dpi=100)
plt.vlines(np.arange(256), 0, hist_g, color='g')
# plt.xlim([0, 255])
plt.ylim([0, np.max(hist_g)])
plt.show()
plt.figure(figsize=(4,2), dpi=100)
plt.vlines(np.arange(256), 0, hist_b, color='b')
# plt.xlim([0, 255])
plt.ylim([0, np.max(hist_b)])
plt.show()
plt.figure(figsize=(4,2), dpi=100)
plt.plot(hist_b, color='b')
plt.plot(hist_g, color='g')
plt.plot(hist_r, color='r')
plt.plot(hist_gray, color='k')
# plt.xlim([0, 255])
plt.ylim([0, max(np.max(hist_b), np.max(hist_g), np.max(hist_r))])
plt.show()
# RGB hisztogram a 0 és 255 elemek kihagyásával
plt.figure(figsize=(4,2), dpi=100)
plt.plot(hist_b[1:255], color='b')
plt.plot(hist_g[1:255], color='g')
plt.plot(hist_r[1:255], color='r')
plt.plot(hist_gray, color='k')
# plt.xlim([0, 255])
plt.ylim([0, max(np.max(hist_b[1:255]), np.max(hist_g[1:255]), np.max(hist_r[1:255]))])
plt.show()
Él ott található a képen, ahol a lokális intenzitáskülönbség nagy.
A képfüggvény deriváltja egy adott pontban mutatja a legnagyobb csökkenés irányát, ami merőleges az élre (ha van az adott pontban), illetve a gradiensvektor nagysága jellemzi az él erősségét.
A képen éleket lehet az első- vagy másodrendű deriváltak számításával keresni. Mindkettő nagyon zajérzékeny, ezért célszerű előzőleg képsimítást végezni a zaj hatásának csökkentésére.
Mivel a digitális képek diszkrétek, ezért a deriváltszámítás is diszkrét lesz. Jellemzően X- és Y-irányú parciális deriváltakat közelítünk konvolúciós kernelekkel.
A simító és parciális deriváltat közelítő konvolúciók általában összevonhatók közös kernelbe, így egy lépésen végrehajthatók.
Egy jól használható éldetektor további feltételezésekkel is él, ilyen például a Canny detektor (simítás, deriválás, nem maximális élek elnyomása, hiszterézis küszöbölés).
import cv2
import numpy as np
img = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_GRAYSCALE)
Gx = np.array([
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]])
imgGx = cv2.filter2D(img, cv2.CV_32F, Gx)
imgGxNorm = cv2.normalize(imgGx, None, 0, 1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32FC1)
cv2.imshow('Gx', imgGxNorm)
Gy = np.array([
[1, 2, 1],
[0, 0, 0],
[-1, -2, -1]])
imgGy = cv2.filter2D(img, cv2.CV_32F, Gy)
imgGyNorm = cv2.normalize(imgGy, None, 0, 1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32FC1)
cv2.imshow('Gy', imgGyNorm)
imgGMagn = cv2.magnitude(imgGx, imgGy)
imgGMagnNorm = cv2.normalize(imgGMagn, None, 0, 1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32FC1)
cv2.imshow('Gradient magnitude', imgGMagnNorm)
cv2.waitKey(0)
cv2.destroyAllWindows()
import cv2
img = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_GRAYSCALE)
blurred = cv2.GaussianBlur(img, (5, 5), 2.0)
edges = cv2.Canny(blurred, 100, 200, None, 5, True)
cv2.imshow('Canny', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
## Bementi színes kép átméretezése megadott szorzótényezőkkel, többféle interpolációs technikával.
import cv2
par_fx = 3
par_fy = 1.5
img = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_COLOR)
res_nearest = cv2.resize(img, None, fx=par_fx, fy=par_fy, interpolation=cv2.INTER_NEAREST)
res_linear = cv2.resize(img, None, fx=par_fx, fy=par_fy, interpolation=cv2.INTER_LINEAR)
res_area = cv2.resize(img, None, fx=par_fx, fy=par_fy, interpolation=cv2.INTER_AREA)
res_cubic = cv2.resize(img, None, fx=par_fx, fy=par_fy, interpolation=cv2.INTER_CUBIC)
res_lanczos4 = cv2.resize(img, None, fx=par_fx, fy=par_fy, interpolation=cv2.INTER_LANCZOS4)
cv2.imshow('Original', img)
cv2.imshow('Resampled nearest', res_nearest)
cv2.imshow('Resampled linear', res_linear)
cv2.imshow('Resampled area', res_area)
cv2.imshow('Resampled cubic spline', res_cubic)
cv2.imshow('Resampled Lanczos', res_lanczos4)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Kép átméretezése explicit új képméret megadásával. A skálatényezők a képarányokból adódnak,
# amiket ellenőrzésképpen ki is írunk a konzolra.
# Figyeljünk arra, hogy az OpenCV és a Numpy más sor/oszlop sorrendet használ!
import cv2
img = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_COLOR)
print(img.shape)
dsize = (200, 100)
dst = cv2.resize(img, dsize, interpolation=cv2.INTER_AREA)
fx = dsize[0] / img.shape[1]
fy = dsize[1] / img.shape[0]
print('fx =', fx)
print('fy =', fy)
cv2.imshow('Original', img)
cv2.imshow('Resampled area', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Képmátrix eltolása. Az eltolás hatást manuális transzformációs mátrix létrehozással oldjuk meg.
import cv2
import numpy as np
img = cv2.imread('gyava_oroszlan.jpg', 0)
rows, cols = img.shape
M = np.float32([[1, 0, 100], [0, 1, 50]])
dst = cv2.warpAffine(img, M, (cols, rows))
cv2.imshow('img', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Kép forgatása a középpontja körül. A forgatási értéket csúszkával állíthatjuk.
import numpy as np
import cv2
def ontrackbar(x):
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), x, 1)
dst = cv2.warpAffine(img, M, (cols, rows))
cv2.imshow('image', dst)
img = cv2.imread('gyava_oroszlan.jpg', 0)
rows, cols = img.shape
cv2.namedWindow('image')
cv2.createTrackbar('R', 'image', 0, 360, ontrackbar)
ontrackbar(0)
cv2.waitKey(0)
cv2.destroyWindow('image')
Az eddigi fejezetkben olyan műveleteket ismertünk meg, amelyek képi bementből képi kimenet készítettek, beleértve a szegmentálást, az éldeketálást.
A következő lépésben képből struktúrális leírást szeretnénk kinyerni, ezzel elindulva a képtartalom értelmezésének irányába.
# Figyeljük meg, hogyan rajzolhatjuk a képre a detektált egyeneseket!
# A rho és theta paraméterek alapján meghatározásra kerül az egyenes egy pontja.
# Erről balra és jobbra olyan távolságra választunk pontokat, amelyek feltehetőleg a képmátrixon kívül esnek.
# Ezek összekötésével a kép szélei közötti egyeneseket kapunk.
import numpy as np
import cv2
src = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_GRAYSCALE)
dst = cv2.Canny(src, 50, 200, None, 3)
cdst = cv2.cvtColor(dst, cv2.COLOR_GRAY2BGR)
lines = cv2.HoughLines(dst, 1, np.pi / 180, 300, None, 0, 0)
sizemax = cdst.shape[0]
if cdst.shape[1] > sizemax:
sizemax = cdst.shape[1]
sizemax = sizemax * 2
if lines is not None:
print('Detektált egyenesek száma:', len(lines))
for i in range(0, len(lines)):
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0 = a * rho
y0 = b * rho
# Computing line endpoints outside of image matrix
pt1 = (int(x0 + sizemax * (-b)), int(y0 + sizemax * a))
pt2 = (int(x0 - sizemax * (-b)), int(y0 - sizemax * a))
cv2.line(cdst, pt1, pt2, (0, 0, 255), 3, cv2.LINE_AA)
cv2.imshow("Source", src)
cv2.imshow("Detected Lines (in red) - Standard Hough Line Transform", cdst)
cv2.waitKey(0)
cv2.destroyWindow('image')
import numpy as np
import cv2
src = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_GRAYSCALE)
dst = cv2.Canny(src, 50, 200, None, 3)
cdst = cv2.cvtColor(dst, cv2.COLOR_GRAY2BGR)
linesP = cv2.HoughLinesP(dst, 1, np.pi / 180, 50, None, 50, 10)
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
cv2.line(cdstP, (l[0], l[1]), (l[2], l[3]), (0, 0, 255), 3, cv2.LINE_AA)
cv2.imshow("Source", src)
cv2.imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP)
## kőrdetektálás
import numpy as np
import cv2
src = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_COLOR)
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
gray = cv2.medianBlur(gray, 5)
rows = gray.shape[0]
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, rows / 16,
param1=200, param2=20,
minRadius=10, maxRadius=60)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
center = (i[0], i[1])
print(center, i[2])
# circle center
cv2.circle(src, center, 1, (255, 0, 0), 3)
# circle outline
radius = i[2]
cv2.circle(src, center, radius, (0, 0, 255), 3)
cv2.imshow("detected circles", src)
## kontúrkeresést más paraméterezéssel, és más bementi képeken is!
import cv2
im = cv2.imread('gyava_oroszlan.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
print('Hierarchia:')
print(hierarchy)
print('Kontúrok száma:', len(contours))
for cntrIdx in range(0, len(contours)):
print(contours[cntrIdx].shape)
cv2.drawContours(im, contours, -1, (0, 255, 0), 3)
cv2.imshow('Contours', im)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Kép betöltése és megjelenítése adott nevű ablakban. Billentyű lenyomás után az ablak eltűnik.
img = cv2.imread('gyava_oroszlan.jpg')
cv2.imshow('imshow', img)
cv2.waitKey(0)
cv2.destroyWindow('imshow')
#Megjeleníthetünk egy üres ablakot is, amelyben majd később jelenítünk meg tartalmat.
cv2.namedWindow('namedWindow')
cv2.waitKey(0)
cv2.imshow('namedWindow', img)
#Megváltoztatjuk az ablak fejléc szövegét és új helyre mozgatjuk.
cv2.setWindowTitle('namedWindow', 'namedWindow title')
cv2.moveWindow('namedWindow', 200, 300)
#Téglalap alakú terület kijelölése és eredményének konzolra írása.
print('Válassz célterületet! Bal egérgomb + mozgatás, majd SPACE vagy ENTER. Megszakítás a c billentyűvel!')
cv2.setWindowTitle('namedWindow', 'Valassz celteruletet!')
roi = cv2.selectROI('namedWindow', img)
print(roi)
#Minden ablak bezárása.
cv2.destroyAllWindows()
# Az összetettebb példaprogramunk egy képet tölt be, és készít róla másolatot.
# A bal egérgomb lenyomásakori koordinátát feljegyezzük, ez lesz a vonalunk kezdőpontja.
# Lenyomott bal egérgomb melletti egér mozgás a vonal végpontját adja.
# A vonalat be is rajzoljuk a képmátrixba és frissítjük a kijelzőt.
# A bal egégomb felengedésekor befejezzük a vonal rajzolását.
# A vörös színű programsorok mutatják az egérkezeléssel kapcsolatos utasításokat.
import cv2
start_x = start_y = -1
button_down = False
def mouse_event(event, x, y, flags, param):
global start_x, start_y, button_down, im
print('x, y:', x, y)
if event == cv2.EVENT_LBUTTONDOWN:
start_x = x
start_y = y
button_down = True
if event == cv2.EVENT_MOUSEMOVE:
if button_down:
im = im_orig.copy()
cv2.line(im, (start_x, start_y), (x, y), (0, 0, 255), 3)
cv2.imshow('image', im)
if event == cv2.EVENT_LBUTTONUP:
start_x = start_y = -1
button_down = False
im = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_COLOR)
im_orig = im.copy()
cv2.imshow('image', im)
cv2.setMouseCallback('image', mouse_event)
cv2.waitKey(0)
cv2.destroyAllWindows()
# A példaprogram két csúszkás paraméterbeállítót helyez el az ablak felső részében.
# Mindkettő önálló kezelőfüggvényt kap. A második esetben azt mutatjuk be,
# hogyan lehet az OpenCV csúszka erős megszorításai mellett [-0.5, 0.5] közötti paraméterértékeket kapni,
# 0.01 léptékkel. Ebben az esetben célszerű a felhasználót tájékoztatni a ténylegesen használt paraméter értékről,
# például a konzolra írással!
# Egy billentyű lenyomása után a param2 csúszka minimális, maximális és aktuális értékei programozottan megváltoznak.
# Ez látható a csúszka beosztásán is.
import numpy as np
import cv2
def onParam1Trackbar(x):
print("=============================")
print('param1 csúszka pozíció:', x)
def onParam2Trackbar(x):
print("=============================")
print('param2 csúszka pozíció:', x)
param = (x - 50) / 100
print('Átalakított paraméter érték: {}'.format(param) )
im = np.ndarray((200, 640, 3), np.uint8)
im.fill(192)
cv2.imshow('window', im)
cv2.createTrackbar('param1', 'window', 5, 10, onParam1Trackbar)
cv2.createTrackbar('param2', 'window', 50, 100, onParam2Trackbar)
cv2.waitKey(0)
cv2.setTrackbarMin('param2', 'window', 20)
cv2.setTrackbarMax('param2', 'window', 50)
cv2.setTrackbarPos('param2', 'window', 30)
pos = cv2.getTrackbarPos('param2', 'window')
print('Trackbar pozíció:', pos)
cv2.waitKey(0)
cv2.destroyAllWindows()
# A keresőtáblát grafikonon is ábrázolhatjuk.
# Ehhez könnyen használható eszközt biztosít a MatPlotLib csomag pyplot eszköze.
# A példaprogram vörössel jelölt részei jelzik a szükséges hívásokat.
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Grafikon beállítások
fig = plt.figure(figsize=(4, 4), dpi=100)
ax = fig.add_subplot(111)
ax.set_title('Inverzió LUT diagram')
fig.canvas.set_window_title('Inverzió LUT diagram')
plt.xlabel('Eredeti intenzitásérték')
plt.ylabel('Pont operáció eredménye')
plt.xlim([0, 255])
plt.ylim([0, 255])
im = cv2.imread('gyava_oroszlan.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('Eredeti', im)
# Alappontok [0, 255] között, 8 bites, előjel nélküli egész számként
x = np.arange(0, 256, 1, np.uint8)
# Inverz készítés keresőtáblával
lut = np.arange(0, 256, 1, np.uint8)
lut = 255 - lut
im_inv = cv2.LUT(im, lut, None)
cv2.imshow('LUT', im_inv)
# Diagram rajzolás
plt.plot(x, x, 'g--', label='Eredeti')
plt.plot(x, lut, 'r-', label='Inverzió')
plt.legend()
# plt.savefig('03_01_c_inverse_lut_diagram.png', bbox_inches='tight')
plt.show()