Göm menyn

Bildbehandling

I tidigare kapitel har vi behandlat enklare textbaserade program. Nu ska vi arbeta med bilder med hjälp av biblioteket OpenCV. Med detta paket ska vi kunna visa bilder och applicera diverse metoder och algoritmer för att åstadkomma de bildmanipulationer vi vill ha.

OpenCV

OpenCV är ett bildbehandlingsbibliotek och har en stor mängd funktioner som gör allt från att modifiera enstaka pixlar till att hitta ansikten i bilder. Det finns även stöd för funktioner som att läsa in bilder och visa bilder på skärmen. Nedan visas ett exempel på användning av sådana funktioner. Observera att vi här behandlar OpenCVs egna bildobjekt som vi kommer att diskutera om senare i kapitlet.

Innan man kan använda OpenCV i Python på IDA:s datorer måste man köra följande kommando i en terminal:

module load courses/TDDE23

Detta gör att man får tillgång till OpenCV för denna terminalsession. För att slippa skriva det varje gång, kan man också skriva följande kommando:

module initadd courses/TDDE23

Detta lägger till modulen för OpenCV i uppstarten. Nu när OpenCV är tillgängligt kan man använda det i Python på följande sätt:

Obs: För att följande kod ska fungera måste bilden image.jpg finnas i samma mapp som koden körs. Ladda ner en fin bild och ge den det filnament om ni vill prova koden.

import cv2

filename = "image.jpg"

# Läser in bilden i gråskala till ett bildobjekt
image = cv2.imread(filename, 0)

# Visa bildobjektet
cv2.imshow('Flowers turned grey', image)

# Spara bildobjektet
cv2.imwrite('grey_flowers.jpg', image)

# Vänta på tangent
cv2.waitKey(0)

# Stäng ner alla OpenCV-fönster
cv2.destroyAllWindows()

Denna kodsnutt har använt sig av den färgglada bilden till vänstra bilden, lästs in den som gråskala och sparats ner som grey_flowers.jpg. Om vi öppnar grey_flowers.jpg kommer den att se ut som bilden till höger.

Obs Efter att ha kört cv2.imshow(...) måste man också köra cv2.waitKey() för att faktiskt visa bilden.

Blomtransformation

För att gå mer i detalj vad varje funktion gör i exemplet ovan, ges mer noggranna beskrivningar.

Läsa in bild

imread(filename[, flag])

  • filename: Namn på filen som ska laddas
  • flag: Valfri parameter (dvs. den kan lämnas tom). Vilken färgtyp den laddade bilden ska ha. Denna parameter kan ha följande data:
    • -1: Returnera färgtyp utan stöd för transparens
    • 0: Returnera gråskala
    • 1: Returnera färgtyp i befintligt skick (med stöd för transparens)

Funktionen laddar in en bild från en viss fil och returnerar den.

Spara bild

imwrite(filename, img)

  • filename: Namn på filen ska sparas
  • img: Bildobjektet som ska sparas

Funktionen sparar bildobjektet som en bildkopia och returnerar den.

Visa bild

imshow(winname, img)

  • winname: Namn på fönstret
  • img: Bildobjektet som ska visas

Funktionen visar en bild för det givna fönstret.

Vänta på tangentbord

waitKey(delay)

  • delay: Fördröjning i millisekunder. 0 är ett specialfall och är oändligt lång fördröjning.

Funktionen väntar på ett knapptryck från tangentbordet oändligt länge (när delay <= 0) eller i delay millisekunder (om delay > 0).

Denna funktion hanterar även OpenCVs event-loop som ser till att fönster visas och uppdateras korrekt. Det krävs därför att waitKey körs någon gång efter att fönster har skapats.

Avsluta OpenCV

destroyAllWindows()

Funktionen stänger alla öppnade OpenCV-fönster.

Bilder

En bild är en samling pixlar i olika färger. Färgerna kan representeras i olika format, men det vanligaste är RGB (Red, Green, Blue). I detta format består varje pixel av tre värden, i varsin så kallad färgkanal, mellan 0 och 255. Värdet i varje kanal beskriver hur mycket rött, grönt respektive blått som ingår i pixeln.

Färg på en pixel

Bildobjekt

Så vad är detta bildobjekt som har nämnts? Om vi vill modifiera en bild vill vi ha en viss representation för hur vi ska kunna få tag på en specifik varje pixel och dess färg. OpenCV erbjuder en rad operationer som kan visa dessa egenskaper.

OpenCVs bildobjekt har egenskaper som rad, kolumn, färgkanaler och antalet pixlar. shape returnerar en tupel med antalet rader, kolumner och färgkanaler (om bilden har färg, annars inkluderas inte antalet färgkanaler) bilden har.

>>> import cv2
>>> image = cv2.imread('pink_flowers.jpg')

# Returnera tupel på formen (antal rader, antal kolumner, antal färgkanaler)
>>> image.shape
(427, 640, 3)

# Returnera antalet pixlar bilden har
>>> image.size
819840

För att få tag på en särskild pixel är det nyttigt att förstå sig på olika färgrymder.

Färgrymder

På datorn sparas bilder vanligtvis i färgrymden RGB (Red, Green, Blue).

RGB Exempel RGB Geometrisk

För att få tillgång till en pixels olika färgvärden i OpenCV använder vi följande indexering. Observera att vi indexerar OpenCVs egna datastruktur, och inte listor. Mer om det här nämns senare i detta kapitel (delen om NumPy och arrayer).

# Skriv ut färgvärdena för pixel på koordinaten (100, 100)
>>> pixel = image[100, 100]
>>> print(pixel)
[179 166 158]

# Skriv ut alla färger på rad 100
>>> row = image[100]
>>> print(row)
[[255 255 255]
 [255 255 255]
 [255 255 255]
 ... 
 [255 255 255]
 [255 255 255]
 [255 255 255]]

Observera att OpenCV använder sig av färgrymden BGR (Blue, Green, Red) och inte RGB som vanligtvis används. Om vi vill bara få tag på en särskild färgvärde utökas föregående uttryck med ett argument med indexet som korresponderar det färgvärdet.

# Få tillgång till bara det blå värdet
>>> blue = image[100, 100, 0]
>>> print(blue)
179

# Få tillgång till bara det röda värdet
>>> red = image[100, 100, 2]
>>> print(red)
158

En annan förekommande färgrymd är CMYK (Cyan, Magenta, Yellow, Key) som skrivare använder sig av för att färga utskrivna dokument. Om man kombinerar cyan, magenta och gult får man en mörkgrå ton. Därför brukar man komplettera det svarta med Key-färgen får att få en skarp svarthet i våra dokument.

CMYK

Inom bildbehandling används ofta formatet HSV (hue, saturation, value) eftersom det har en starkare koppling till hur färgen ser ut än till exempel RGB.

  • Hue ligger mellan 0 och 360 grader och beskriver färgens nyans. hue = 0 (eller 360) är en röd pixel, medan hue = 180 är cyan. I OpenCV representeras hue som ett tal mellan 0 och 255 där 0 motsvarar 0 grader och 255 motsvarar 360 grader.
  • Saturation beskriver färgens mättnad, alltså hur färgstark pixeln är. I OpenCV är saturation = 0 en färglös pixel och saturation = 255 ger en färgstark pixel.
  • Value beskriver ljusstyrkan i pixeln. För OpenCV används value = 0 för att få en helt svart värde, medan value = 255 inte har någon svarthet.

HSV Exempel HSV Geometrisk

För att konvertera mellan olika färgrymder använder OpenCV sig av följande funktion.

Konvertera färgrymd

cvtColor(img, colorspace_fn)

  • img: Bildobjektet som ska konverteras
  • colorspace_fn: Funktion som konverterar från en viss färgrymd till en annan. Syntaxen för konvertering är cv2.COLOR_[SRC]2[DST] där SRC är den färgrymd img befinner sig i just nu och DST är den färgrymd vi vill konvertera den till. Exempel på giltiga alternativ är cv2.COLOR_RGB2GRAY och cv2.COLOR_HSV2BGR.

Funktionen konverterar bildobjektet img till den nya färgrymden med hjälp konverteringsfunktionen colorspace_fn och returnerar bildobjektet.

NumPy

Bilder kan innehålla mycket data och därför använder OpenCV sig av ett annat bibliotek, NumPy, för att behandla sin bilddata. NumPy är ett paket som har stöd för en rad effektiva numeriska operationer. En datastruktur som vanligtvis förekommer i OpenCV är Numpys arrayer. Denna datastruktur kan ses på ett och annat sätt som Pythons listor. Ibland behöver OpenCV arrayer, och inte listor, för att utföra sina operationer. Därför kan det förekomma att man behöver konvertera sina Python-listor till arrayer.

>> import numpy

# Initialisera en 2D-lista
>> kernel = [[0, 3, 255], [0, 32, 23], [0, 0, 0]]
            
# Initialisera en 3x3 lista som en 3x3 array
>> array = numpy.array(kernel)

# Skriv ut arrayen
>>> array
array([[0,  3, 255],
       [0, 32,  23],
       [0,  0,   0]])
       
>>> print(array)
[[0,  3, 255],
 [0, 32,  23],
 [0,  0,   0]]

Observera att array givet i exemplet ovan inte är en lista, utan Numpys egen array.

Det kan även förekomma att vi vill initialisera en tom array (en array fyllda med nollor) eller modifiera ett visst element i en array.

import numpy
height = 10
width = 10
amt_channels = 3

# Initialisera en 10x10 array och fyllda med nollor
array = numpy.zeros((height, width, amt_channels), numpy.uint8)

pixel = (255, 255, 0)
# Sätt färg på elementet i indexet (5, 5) i arrayen
array[5, 5, 0] = pixel[0]
array[5, 5, 1] = pixel[1]
array[5, 5, 2] = pixel[2]

Numpy-array vs Lista

I den här kursen kommer vi inte att använda Numpys arrayer eller andra OpenCV-funktioner så mycket. Vi kommer istället att gå över till python-listor för att öva på det. I verkliga applikationer är det dock mycket bättre att använda Numpy-arrayer för stora beräkningar eftersom de är implementerade i C och optimerade för numeriska beräkningar och därför mycket snabbare än python-listor.

Modifiera bilder

I OpenCV kan man manipulera bilder genom att filtrera dem. Att filtrera en bild innebär att man beräknar varje pixels värde med hjälp av en funktion. Funktionerna kan vara omvandling till gråskala, bildskärpning (eng. sharpen) eller negera bildens färger.

Modifikationer av en bild kan användas med OpenCVs filterfunktion filter2d. Denna funktion sveper igenom alla pixlar och för varje pixel konstruera en array med värden från den sökta pixelvärdet och dess omgivna pixelvärden. I bilden nedan visar hur en 3x3-array skulle vara konstruerad för en viss pixel (den i position (0,0)) när filter2d går igenom pixlarna.

Hitta kernel i bild

Men vad gör man med denna array? För att förklara vad man gör med den konstruerade arrayen är det enklare att hoppa rakt in i ett exempel. Om vi skulle jämna ut (eng. blur, smooth) en bild konceptuellt skulle vi för varje pixel ta lika stor mängd färg från alla närliggande pixlar och ta genomsnittet på det.

Kernel Exempel

K är hur vi matematiskt noterar en 2d-array och är också den array OpenCV är intresserad av för att utföra sin filtrering. Alla tal i K behöver inte nödvändigtvis vara 1/N. För andra funktioner, som inte jämnar ut bilden, fördelas talen i K på ett helt annat sätt. Det viktiga är att summan av alla tal i K adderas till 1 (eller nära 1). Om talen i K skulle adderas till mer än 1, blir den filtrerade bilden ljusare. Om det skulle bli mindre än 1, blir bilden mörkare. Om det adderas till mindre än 0, blir bilden svart. K kan anta alla höjder större än 0 och bredder större än 0. Det är alltid arraypositionen i mitten som beräknas. Om antalet element i K skulle vara jämnt i någon dimension expanderar filter2d den dimensionen med nollor. En 2x2-array skulle expanderas till en 3x3-array med hjälp av utfyllda nollor.

Expansion av jämn kernel

Hur använder man sig av denna array K för att filtrera i vår pythonkod? Mata in arrayen i filterfunktionen tillsammans med bilden som ska filtreras. I detta fall använder vi smooth-arrayen som vi har fått fram.

import numpy
import cv2

image = cv2.imread('cont_tracks.jpg')

# Initialisera den array vi vill filtrera med
kernel = numpy.array([
    [1/9, 1/9, 1/9], 
    [1/9, 1/9, 1/9], 
    [1/9, 1/9, 1/9]])

# Filtrera vår bild
new_image = cv2.filter2D(image, -1, kernel)

cv2.imshow('Smoothed tracks', new_image)

cv2.waitKey()

Genom att applicera filter2D får vi ett lyckat resultat.

Bandtransformation

Bildkällor

https://en.wikipedia.org/wiki/RGB_color_model

https://en.wikipedia.org/wiki/CMYK_color_model

https://en.wikipedia.org/wiki/HSL_and_HSV


Sidansvarig: Peter Dalenius
Senast uppdaterad: 2023-08-09