Opi matematiikkaa ohjelmoimalla digitaalista kuvankäsittelyä, osa 2/2
Samu Huovinen ja Matti Heikkinen
Pythonissa on monta eri tapaa käsitellä kuvia. Yksi niistä on opencv:n affine transform, jolla kuvia voi operoida lineaarisilla kuvauksilla.
Koska myös kuvia itsessään voi käsitellä matriiseina (pikseliruudukkona), niihin voi soveltaa matriisien laskusääntöjä. Tässä hyödynnetään yhteen- ja kertolaskua. Lisäksi pikseleitä voi käsitellä yksitellen ja iteroimalla.
Tässä on 8×8 pikselin kuva:
Sen voi ajatella myös 8×8 matriisina:
Tai RGB-arvoina:
Tai kolmena eri matriisina, jotka kuvaavat eri RGB-väriarvot:
R:
B:
G:
Näissä matriiseissa on esitetty kuvan jokaisen pikselin RGB-arvot, mutta ei pikselin xy-koordinaatteja eli sijaintia valokuvan tulosteessa. Tämä huomioidaan OpenCV-python kirjaston warpAffine-funktiossa.
Kuvan lukeminen ja tulostamien
Huomaa, että kuvan origo on vasemmassa yläkulmassa. Kuvassa x-akseli on vaaka-akseli, ja y-akseli on pystyakseli. Alla olevasta kuvasta numeroinnista näet akselien positiiviset suunnat.
– otetaan käyttöön kirjastot numpy ja cv2
import numpy as np
import cv2
import matplotlib.pyplot as plt
kuppi = plt.imread('kuppi_2048.png')
height, width, channels = kuppi.shape
plt.imshow(kuppi)
Käytetään esimerkkikuvana 2048×2048 kuvaa kahvikupista. Origo on tässä vasemmassa yläkulmassa. Tämä täytyy ottaa huomioon lineaarisia kuvauksia käytettäessä. Nyt väriarvoina käytetään desimaalilukuja 0 – 1, jossa 0 on musta ja 1 valkoinen.
kuppi = plt.imread('kuppi_2048.png')
plt.imshow(
2 * kuppi
)
kuppi = plt.imread('kuppi_2048.png')
plt.imshow(
kuppi + 0.4
)
Kuvia voi myös laskea yhteen matriisien yhteenlaskulla.
omenat = plt.imread('omenoita.png')
plt.imshow(
(kuppi + omenat)
)
plt.imshow(
(kuppi + omenat) / 2
)
Valmiiden kirjastojen käyttö valokuvan kierrossa keskipisteen suhteen
– getRotationMatrix2D funktion käyttöä
– Huomaa, että Opencv:ssä kiertomatriisi M on 2×3 matriisi
– Koodissa ensin muodostetaan muunnosmatiisi M ja sen jälkeen operoidaan tämä kierto alkuperäiselle valokuvalle.
#Opencv warpAffine ja getRotationMatrix2D funktioiden käyttö kuvan kierrossa kuvan keskipisteen suhteen
# valitse kulma
img = plt.imread('kuppi_2048.png')
angle = -45
print(height)
print(width)
#Muunnosmatriisin M laskeminen, kierto keskipisteen suhteen
M = cv2.getRotationMatrix2D((height/2,width/2),angle,1)
print(M)
#Suoritetaan affiinimuunnos kuvalle img matriisilla M, muunnetun kuvan leveys ja korkeus parametreina
result_img = cv2.warpAffine(img, M, (width, height))
# Kuvan piirto
plt.imshow(result_img)
2D Siirto eli translaatio T
3×3 Siirtomatriisi matematiikassa
1/4-siirtomatriisin toteutus OpencV 2×3 matriisina T:
missä x= width = kuvan leveys ja y = height eli kuvan korkeus
2D Affiinimuunnos = Translaatio + Lineearinen muunnos
1/4-siirto eli translaatio T ja 0.5-skaalaus S matematiikassa
3×3 Siirtomatriisi ja skaalausmatriisi matematiikassa
ja
eli T ja S molemmat yhdessä:
1/4-siirtomatriisin ja skaalausmatriisin (s=0.5) toteutus OpencV 2×3 matriisina M:
missä x= width = kuvan leveys ja y = height eli kuvan korkeus
# Kuvan 3x2-siirtomatriisi T , 1/4 siirto, jolloin osa kuvasta siirtyy ulos alkuperäisestä width,height-avaruudesta
T = np.float32(np.array([[1, 0, width/4],[0, 1, height/4]]))
print(T)
#Affiinimuunnos eli suoritetaan kuvan siirto
result_img = cv2.warpAffine(img, T, (height ,width))
plt.imshow(result_img)
# Kuva skaalaus ja siirto, kuva pysyy alkuperäisessä kuva-avaruudessa
#Skaalauskerroin
scale = 0.5
# Kuvan skaalaus ja 3x2-siirtomatriisi
M = np.float32(np.array([[scale, 0, width/4],[0, scale, height/4]]))
print(T)
#Suoritetaan Affiinimuunnos T
result_img = cv2.warpAffine(img, M, (height ,width))
plt.imshow(result_img)
2D Affiinimuunnos = skaalaus, kierto ja translaatio
1/4-siirto eli translaatio T ja 0.5-skaalaus S sekä kierto matematiikassa
3×3 Siirtomatriisi ja skaalausmatriisi matematiikassa
ja
kierto
1/4-siirtomatriisin , skaalausmatriisin (s=0.5) kierron R toteutus OpencV 2×3 matriisina M:
Alla olevassa koodissa muuonnosmatriisi T on muodostettu siten, että T:n 2 ensimmäistä pystyriviä suorittavat skaalauksen ja kierron alkuperäisen origon (vasen yläkulma) suhteen. Voit testata tätä laittamalla T:n 3.pystyvektorin kertoimien arvoksi nolla. Tämän jälkeen skaalatun ja kierretyn kuvan keskipiste siirretään takaisin alkuperäisen kuvan keskipisteeseen, mikä on T:n 3.pystyrivi.
angle = 45*np.pi/180
scale = 0.5
a = scale*np.cos(angle)
b = scale*np.sin(angle)
print(a-b, a+b)
T = np.float32(np.array([[a, -b, width/2*(1-(a-b))],
[b, a, height/2*(1-(a+b))]]))
result_img = cv2.warpAffine(img, T, (height ,width))
plt.imshow(result_img)
Iteroiminen
Koodissa käydään silmukoilla jokainen pikseli erikseen läpi ja 2-kertaistaan R-kanavan väriarvo.
for y in range(height):
for x in range(width):
r = omenat[y][x][0]
g = omenat[y][x][1]
b = omenat[y][x][2]
omenat[y][x] = (r * 2, g, b)
plt.imshow(omenat)
Mustavalkokuvat
Värikuvan voi muuttaa mustavalkokuvaksi muuttamalla matriisin muotoa. Yksinkertainen kaksiulotteinen matriisi on mustavalkokuva. Tälläisessä matriisissa pikseliä kuvaa vain yksi luku, sen kirkkaus. Kirkkauden voi laskea, kun tiedetään rgb-arvot.
kuppi = plt.imread('kuppi_2048.png')
for y in range(height):
for x in range(width):
r = kuppi[y][x][0]
g = kuppi[y][x][1]
b = kuppi[y][x][2]
kuppi[y][x] = 0.333 * r + 0.333 * g + 0.333 * b
plt.imshow(kuppi)
Yleensä pelkkä keskiarvon laskeminen ei riitä realistisen kuvan tuottamiseen. Tämän takia käytetään gammakorjattuja kertoimia:
kuppi = plt.imread('kuppi_2048.png')
for y in range(height):
for x in range(width):
r = kuppi[y][x][0]
g = kuppi[y][x][1]
b = kuppi[y][x][2]
kuppi[y][x] = 0.299 * r + 0.587 * g + 0.114 * b
plt.imshow(kuppi)
Kertoimien taustalla on ihmisnäön herkkyys eri väreille.