Andmekogu loomine jmd-imagescraper abil

Sügavõppe mudelite treenimiseks on vaja palju sildistatud pilte. Üks lihtsamaid mooduseid sellise andmekogu loomiseks on kasutada mõnd otsingumootorit.

Otsingumootoriks on DuckDuckGo, mis kasutab Bing otsingumootori koostatud indeksit. Nii, et tulemused on üsna sarnased.

Negatiivne külg on ,et leitud vasted on kõik ameerika kesksed ja ei arvesta lokaalsete eripäradega. Näiteks on siga mingi teibi bränd ja kass on mõnede kodanike nimi.

Nii et enne, kui hakkata 200 pilti allalaadima tuleks leida õiged otsingu fraasid.

1. Paigaldame vajaliku teegi Colabi või mõnda teise Jupyter märkmikku:

!pip install -q jmd_imagescraper

2. Impordime vajalikud teegid märkmikku:

from jmd_imagescraper.core import *
from pathlib import Path
from jmd_imagescraper.imagecleaner import *

3. Defineerime otsingu sõnad ja fraasid:

keywords = ['cat', 'polar bear', 'siga']

4. Loodava andmekogu asukoht:

dataset_dir = Path().cwd()/"dataset"

5. Vastavalt otsingusõnadele laeme alla pildi ja paneme kataloogotesse:

for keyword in keywords:
  keyword_dir = keyword
  search_phrase = keyword
  duckduckgo_search(dataset_dir, keyword_dir, search_phrase, max_results=10)

6. Vaatame andmekogu visuaalselt üle. Kas on pilte mida peaks eemaldama:

display_image_cleaner(dataset_dir)

Links

1. Sügavõpe (Deep leartning)

Lühi konspekt originaal fastai märkmikust 02_production.ipynb

Masinnägemine Computer vision

  • object recognition – asjade äratundmine
  • object detection – asjade tuvastamine: asukoht, nimi
  • segmentation – iga piksel kategoriseeritakse selle järgi, mis objekti osa ta on.
  • out-of-domain data – Sügavõppe mudelid ei ole üldiselt head nende piltide äratundmisel, mis erinevad oluliselt struktuuri või stiili poolest nendest piltidest millega teda on treenitud. Näiteks, kui treening andmetes polnud mustvalgeid pilte võib mudel nendega halvasti toimida. Või näiteks käsitsi joonistatud pildid
  • data augmentation – protsess, kus genereeritakse varieeruvustega uus pilte olemas olevatest andmetest. Neid näiteks pöörates, muutes heledust jne. Et vähendada vajadust käsitsi sildistada (label) paljusid andmehulki.
  • GPU on vajalik mudeli treenimiseks aga , kui mudel on valmis siis pole see enam alati vajalik.

Natural language processing (NLP)

Jupyter Notebookis abi kuvamiseks kirjuta koodo lahtrisse ??funktsiooninimi Näide: ??verify_images

Karud liikide klassifitseerimine

Seadistamine Google Colabis algab fastbooki paigaldamisega,

!pip install -Uqq fastbook
import fastbook
fastbook.setup_book()

from fastbook import *
from fastai.vision.widgets import *

1. Andmekogu loomine Bing Image Search abil

Loo tasuta Microsoft Azure konto.

Ja otsi üles võtti (Key1).

key = os.environ.get('AZURE_SEARCH_KEY', 'e04722cgfdhghdfghgfhfdhdfhdfh772461') # võti näeb u. selline välja

Vaatame kas töötab:

search_images_bing

Sooritame otsingu otsisõnaga ‘grizzly bear’. Tasuta saame korraga otsida kuni 150 pilti.

results = search_images_bing(key, 'grizzly bear')
ims = results.attrgot('contentUrl')
len(ims)

Laeme ühe pildi alla:

# Loome kataloogi image
dir = Path('image')
if not dir.exists():
    dir.mkdir()

dest = 'image/grizzly.jpg'
download_url(ims[0], dest)

Kuvame pildi:

im = Image.open(dest)
im.to_thumb(128,128)

Kui see töötab, kasutame fastai’s download_images, et allalaadida kõik URLid kõigi meie otsingu terminite kohta. Paneme terminite järgi erinevatesse kataloogidesse. Skript loob kataloogid eraldi karu liikidele.

bear_types = 'grizzly','black','teddy'
path = Path('bears')

# Kui 'bears' kataloogi ei ole
if not path.exists():
    path.mkdir()

# karu tüübid
for beartype in bear_types:
    dest = (path/beartype)
    dest.mkdir(exist_ok=True)
    dir = os.listdir(dest)
    # Kui kataloog on tühi
    if len(dir) == 0:
        results = search_images_bing(key, f'{beartype} bear')
        download_images(dest, urls=results.attrgot('contentUrl'))

# Hiljem tekib probleeme, kui png fail ei ole RGBA
for beartype in bear_types:
    dest = (path/beartype)
    # convert images to RGBA
    for image in os.listdir(dest):
        full = str(dest) +"/"+ image
        try:
          im = Image.open(full)
          # Kui on png image
          if im.format == 'PNG':
            # ja ei ole RGBA
            if im.mode != 'RGBA':
                im.convert("RGBA").save(f"{dest+image}2.png")
        except:
          print(full)
fns = get_image_files(path)
fns

Kontrollime kas kõik pildid said vigadeta allalaetud:

failed = verify_images(fns)
failed

Vigaste piltide eemaldamine:

failed.map(Path.unlink);

2. DataLoaders

DataLoaders on objekt, mis hoiab andmehulka, et see oleks kättesaav treenimiseks (train) ja valideerimiseks (valid).

Peame ütlema fastai’le nelja asja:

  • Mis tüüpi andmetega me töötame
  • Kuidas saada andmete loendit
  • Kuidas neid sildistada
  • Kuidas luua valideerimis andekogu

Loob DataBlock objekti. See on mall, mille järgi hiljem loome DataLoadersi:

bears = DataBlock(
    blocks=(ImageBlock, CategoryBlock), # types for the independent and dependent variables 
    get_items=get_image_files, 
    splitter=RandomSplitter(valid_pct=0.2, seed=42),
    get_y=parent_label,
    item_tfms=Resize(128))

blocks=(ImageBlock, CategoryBlock) – loome enniku (tuple), kus määrame, mis andme tüüpe tahame sõltumatu (independent) ja sõltuva (dependent) muutuja jaoks.

Sõltumatut muutujat (independent variable) on see mida kasutame ennustamiseks. Antud juhul pildid.

Sõltuv muutuja (dependent variable) on meie sihtmärk/eesmärk/tulemus. Antud juhul piltide katekoogiad ehk karu tüübid.

get_items=get_image_files – Loob listi kõigis kataloogis olevatest piltidest.

splitter=RandomSplitter(valid_pct=0.2, seed=42) – Jagame juhuslikult andmehulha treening ja valideerimis osaks. Seeme (seed) on juhuslike numbrite genereerimise alguspunkt ja see tagab, et saame igakord sama listi.

get_y=parent_label – Siin ütleme. kuidas luua silid meie andmekogule. parent_label on funktsioon, mis tagastab kataloogi nime, milles fail on. Sõltumatut muutujat (independent variable) tähistatakse tavaliselt x’iga. Sõltuv muutuja (dependent variable) y’ga.

item_tfms=Resize(128) – Muudame kõik pildid sama suureks. Pilte ei söödeta mudeliise mitte üksikult vaid väikeste hulkadena (mini-batch). Tensor on suur grupp pilte. Item transforms on funktsioon (antud juhul resize), mis töötleb igat andme ühikut (pilti).

Siin toimub tegelik DataLoadersi loomi. Failide asukoht (path) on meil eespool defineeritud.

dls = bears.dataloaders(path)

DataLoader söödab pilte GPU’le mitmekaupa. Ühes tensortis 64 pilti.

Kuvame neist mõned:

dls.valid.show_batch(max_n=4, nrows=1)

Vaikimisi Resize kropip pildid ruutudeks. Aga nii võib mingi oluline osa pildist kadumaminna.

Teiste võimalustena saab pildid venitada/suruda ruututeks või vähendada pildid, kuni mahuvad ruudu sisse ja täita ülejäänud pildi osa nullidega (mustaga).

# Venitamine
bears = bears.new(item_tfms=Resize(128, ResizeMethod.Squish))
dls = bears.dataloaders(path)
dls.valid.show_batch(max_n=4, nrows=1)
# vähendamine ja mustaga täitmine
bears = bears.new(item_tfms=Resize(128, ResizeMethod.Pad, pad_mode='zeros'))
dls = bears.dataloaders(path)
dls.valid.show_batch(max_n=4, nrows=1)

Probleemid:

Kui venitame pilte, siis muudame nende kuju ja asjad näevad välja teistsugused, kui nad tegelikult on.

Kui kropime, siis võib mingi oluline osa infost kaduma minna.

Kui vähendame ja täidame tühjad alad mustaga siis raiskame arvutusressurssi tühjade pildi alade jaoks. Ja lisaks on tegelikud pildid veel väiksema resolutsiooniga, kui muidu, mis võib viia ebatäpsuseni.

Parem lahendus on kasutada igal epohhil juhuslikult kroppida erinev pildi osa. Selle tulemusena saab mudel paremini keskenduda erinevatele pildi osade äratundmisele. Ja see on lähemal reaalsetele andmetele, kus asjad ei ole alati pildi keskel ja võivad olla erineva suurusega.

Asendame Resize uue funktsiooniga RandomResizedCrop. Olulised osad on min_scale ja unique=True – sama pilti korratakse erinevate versioonidega. See on osa andmete täiendamise tehnikast (data augmentation).

bears = bears.new(item_tfms=RandomResizedCrop(128, min_scale=0.3))
dls = bears.dataloaders(path)
dls.train.show_batch(max_n=4, nrows=1, unique=True)

Andmete täiendamine Data Augmentation

On tehnika suurendamaks varieeruvust treening andmetes luues juhuslike erinevusi. Nii, et nad näiksid erinevad aga ei kaotaks oma sisu. Näited tehnikatest: keeramine, peegeldamine, perspektiivi muutmine ja painutamine, heleduse ja kontrasti muutmine.

bears = bears.new(item_tfms=Resize(128), batch_tfms=aug_transforms(mult=2))
dls = bears.dataloaders(path)
dls.train.show_batch(max_n=8, nrows=2, unique=True)

2. DataLoaders

DataLoaders on objekt, mis hoiab andmehulka, et see oleks kättesaav treenimiseks (train) ja valideerimiseks (valid).

Peame ütlema fastai’le nelja asja:

  • Mis tüüpi andmetega me töötame
  • Kuidas saada andmete loendit
  • Kuidas neid sildistada
  • Kuidas luua valideerimis andekogu

Loob DataBlock objekti. See on mall, mille järgi hiljem loome DataLoadersi:

bears = DataBlock(
    blocks=(ImageBlock, CategoryBlock), # types for the independent and dependent variables 
    get_items=get_image_files, 
    splitter=RandomSplitter(valid_pct=0.2, seed=42),
    get_y=parent_label,
    item_tfms=Resize(128))

blocks=(ImageBlock, CategoryBlock) – loome enniku (tuple), kus määrame, mis andme tüüpe tahame sõltumatu (independent) ja sõltuva (dependent) muutuja jaoks.

Sõltumatut muutujat (independent variable) on see mida kasutame ennustamiseks. Antud juhul pildid.

Sõltuv muutuja (dependent variable) on meie sihtmärk/eesmärk/tulemud. Antud juhul piltide katekoogiad ehk karu tüübid.

get_items=get_image_files – Loob listi kõigis kataloogis olevatest piltidest.

splitter=RandomSplitter(valid_pct=0.2, seed=42) – Jagame juhuslikult andmehulha treening ja valideerimis osaks. Seeme (seed) on juhuslike numbrite genereerimise alguspunkt ja see tagab, et saame igakord sama listi.

get_y=parent_label – Siin ütleme. kuidas luua silid meie andmekogule. parent_label on funktsioon, mis tagastab kataloogi nime, milles fail on. Sõltumatut muutujat (independent variable) tähistatakse tavaliselt x’iga. Sõltuv muutuja (dependent variable) y’ga.

item_tfms=Resize(128) – Muudame kõik pildid sama suureks. Pilte ei söödeta mudeliise mitte üksikult vaid väikeste hulkadena (mini-batch). Tensor on suur grupp pilte. Item transforms on funktsioon (antud juhul resize), mis töötleb igat andme ühikut (pilti).

Siin toimub tegelik DataLoadersi loomi. Failide asukoht (path) on meil eespool defineeritud.

dls = bears.dataloaders(path)

DataLoader söödab pilte GPU’le mitmekaupa. Ühes tensortis 64 pilti.

Kuvame neist mõned:

Vaikimisi Resize kropip pildid ruutudeks. Aga nii võib mingi oluline osa pildist kadumaminna.

Teiste võimalustena saab pildid venitada/suruda ruututeks või vähendada pildid, kuni mahuvad ruudu sisse ja täita ülejäänud pildi osa nullidega (mustaga).

# Venitamine
bears = bears.new(item_tfms=Resize(128, ResizeMethod.Squish))
dls = bears.dataloaders(path)
dls.valid.show_batch(max_n=4, nrows=1)
# vähendamine ja mustaga täitmine
bears = bears.new(item_tfms=Resize(128, ResizeMethod.Pad, pad_mode='zeros'))
dls = bears.dataloaders(path)
dls.valid.show_batch(max_n=4, nrows=1)

Probleemid:

Kui venitame pilte, siis muudame nende kuju ja asjad näevad välja teistsugused, kui nad tegelikult on.

Kui kropime, siis võib mingi oluline osa infost kaduma minna.

Kui vähedame ja täidame tühjad alad mustaga siis raiskame arvutusressurssi tühjade pildi alade jaoks. Ja lisaks on tegelikud pildid veel väiksema resolutsiooniga, kui muidu, mis võib viia ebatäpsuseni.

Parem lahendus on kasutada igal epohhil juhuslikult kroppida erinev pildi osa. Selle tulemusena saab mudel paremini keskenduda erinevatele pildi osade äratundmisele. Ja see on lähemal reaalsetele andmetele, kus asjad ei ole alati pildi keskel ja võivad olla erineva suurusega.

Asendame Resize uue funtksiooniga RandomResizedCrop. Olulised osad on min_scale ja unique=True – sama pilti korratakse erinevate versioonidega. See on osa andmete täiendamise tehnikast (data augmentation).

bears = bears.new(item_tfms=RandomResizedCrop(128, min_scale=0.3))
dls = bears.dataloaders(path)
dls.train.show_batch(max_n=4, nrows=1, unique=True)

Andmete täiendamine Data Augmentation

On tehnika suurendamaks varieeruvust treening andmetes luues juhuslike erinevusi. Nii, et nad näiksid erinevad aga ei kaotaks oma sisu. Näited tehnikatest: keeramine, peegeldamine, perspektiivi muutmine ja painutamine, heleduse ja kontrasti muutmine.

bears = bears.new(item_tfms=Resize(128), batch_tfms=aug_transforms(mult=2))
dls = bears.dataloaders(path)
dls.train.show_batch(max_n=8, nrows=2, unique=True)

3. Treenimine

Meil ei ole palju andmeid. 150 pilti igast karu liigist. Seega kasutame RandomResizedCrop pildi suurusega 224px, mis standart suurus ja kasutame ka aug_transforms:

bears = bears.new(
    item_tfms=RandomResizedCrop(224, min_scale=0.5),
    batch_tfms=aug_transforms())
dls = bears.dataloaders(path)

Alustame treenimisega ja häälestame (fine_tune):

learn = cnn_learner(dls, resnet18, metrics=error_rate)
learn.fine_tune(4)

Vaatame milliste piltidega on mudelil kõige rohkem raskusi. Visualiseerimiseks kasutame confusion matrix

interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix()

Read esindavad kõiki black, grizzly ja teddy karusid meie andmehulgas.

Tulbad näitavad mida mudel ennustas.

Diagonaal maatriksil näitab õigeid ennustusi ja mis ei ole diagonaalil näitab ebaõigeid klassi määramisi.

Loss on number, mis on suurem, kui mudel eksib (eriti kui ta on kindel oma ebaõigetes vastustes) või kui mudelil on õigus aga puudub kindlus, et on õige.

plot_top_losses näitab kõigesuurema loss arvuga pilte.

interp.plot_top_losses(6, nrows=2)

Võib ette tulla valesti sildistatud pilte

4. Andmete korrastamine/puhastamine

Fastai sisaldab graafilist liidest (GUI) ImageClassifierCleaner andmete puhastamiseks. Kus saab valitud pilte kas kustutada või muuta kategooriat.

cleaner = ImageClassifierCleaner(learn)
cleaner

Et kustutada (unlink) valitud pilte:

for idx in cleaner.delete(): cleaner.fns[idx].unlink()

Et liigutada valitud pilte teise katekooriasse:

for idx,cat in cleaner.change(): shutil.move(str(cleaner.fns[idx]), path/cat)

Seejärel treenime uuesti puhastatud andmetega.

5. Mudeli eksportimine

Valmis mudel koosneb kahest osast:

  1. Arhitektuur
  2. Treenitud parameetrid

Mudeli salvestamiseks failiks “export.pkl”:

learn.export()

Kontrollime, kas fail on olemas:

path = Path()
path.ls(file_exts='.pkl')

6. Järeldamin (inference)

Kui me enam ei tree mudelit ja kasutame seda ennustuste (predictions) saamiseks kutsutakse seda järeldamiseks (inference).

Et kasutada eksporditud mudelit järelduste tegemiseks tuleb see kõigepealt sisse laadida:

learn_inf = load_learner(path/'export.pkl')

Et teada saada mida mudel arvab mingist pildist tuleb see talle, ette anda.

pilt = ('image/grizzly.jpg')
learn_inf.predict(pilt)

Näeme kolme asja: ennustatud kategooriat, ennustatud kategooria indeksit, ja iga kategooria tõenäosust.

Et näha, mis kategooriad on mudelis:

learn_inf.dls.vocab

7. Veebirakendus

Hobiprojektide jaoks on lihtsaim ja tasuta viis luua veebirakendus kasutada Binderit.

  1. Lae märkmikk GitHubi
  2. Lisa GitHuvi koodivaramu URL Binder’isse
  3. File valik rippmenüüs muuda URLiks
  4. URL kirjuta /voila/render/name.ipynb, kus name on sinu märkmiku nimi
  5. click clickboard copy URL
  6. Launch

Siin on näidisrakenduse kood: github.com/fastai/bear_voila

Selleks vajame:

  • IPython widgets (ipywidgets)
  • Voilà (Ei tööta Google Colabis!)

Voila on nagu Jupyteri märkmikud aga peidab ära kõik sisend koodilahtrid (shells) ja näitab ainult nende väljundit + markdowni lahtreid. Veebirakenduse nägemiseks muuta brauseri URLis “notebooks” -> “voila/render”

Valmis mudel on funktsioon mida saab välja kutsuda (pred,pred_idx,probs = learn.predict(img)) igas pythoni koodis.

Kõige tavalisem värv pildil

On juhtumid, kus meid ei huvita üksikute pikslite värv. Vaid tahame üldist summeeritud keskmist. Näiteks põllumajanduses saab värvipõhjal hinnata puu või köögivilja küpsusastet.

Alustuseks laeme vajalikud teegid:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
import PIL
from skimage import io
%matplotlib inline

#Kõik pildid on siin kataloogis
kataloog = 'https://raw.githubusercontent.com/taunoe/jupyter-notebooks/main/Pildi-anal%C3%BC%C3%BCs/images/'

Teeme funktsiooni, mis aitab näidata kahte pilti kõrvuti.

def show_img_compar(pilt_1, pilt_2 ):
    f, ax = plt.subplots(1, 2, figsize=(10,10))
    ax[0].imshow(pilt_1)
    ax[1].imshow(pilt_2)
    ax[0].axis('on')  # Kuva koordinaatteljestik
    ax[1].axis('off') # Peida koordinaatteljestik
    f.tight_layout()
    plt.show()

Valime pildid:

#pilt_1 = cv.imread(kataloog + 'tamm.jpg') # annab errori
pilt_1 = io.imread(kataloog + 'tamm.jpg')
#pilt_1 = cv.cvtColor(pilt_1, cv.COLOR_BGR2RGB) # reastab BGR kihid ümber RGBks
pilt_2 = io.imread(kataloog + 'sinie.jpg')
#pilt_2 = cv.cvtColor(pilt_2, cv.COLOR_BGR2RGB)

Teeme pilte väiksemaks:

dim = (500, 300)
# Pildid väiksemaks
pilt_1 = cv.resize(pilt_1, dim, interpolation = cv.INTER_AREA)
pilt_2 = cv.resize(pilt_2, dim, interpolation = cv.INTER_AREA)

Proovime, kas piltide kuvamine töötab:

Meetod 1 – keskmine pikslite väärtus

Kõige lihtsam meetod on leida pikslite keskmised väärtused. Kasutades teegist numpy average funktsiooni leidmaks keskmise piksli väärtus.

Selline meetod võib anda ebatäpseid tulemusi. Eriti, kui pildi pinnal on suuri kontrasti (heledate ja tumedate alade) erinevusi. Tamme tekstuuri puhul on aga tulemus üsna usutav.

img_temp = pilt_1.copy()
img_temp[:,:,0], img_temp[:,:,1], img_temp[:,:,2] = np.average(pilt_1, axis=(0,1))
show_img_compar(pilt_1, img_temp)
img_temp = pilt_2.copy()
img_temp[:,:,0], img_temp[:,:,1], img_temp[:,:,2] = np.average(pilt_2, axis=(0,1))
show_img_compar(pilt_2, img_temp)
pilt_3 = io.imread(kataloog + 'muster.jpg') # impordime pildi
pilt_3 = cv.resize(pilt_3, dim, interpolation = cv.INTER_AREA) # muudame suurust
img_temp = pilt_3.copy() # teeme koopia
img_temp[:,:,0], img_temp[:,:,1], img_temp[:,:,2] = np.average(pilt_3, axis=(0,1)) # arvutame keskmise
show_img_compar(pilt_3, img_temp) # kuvame tulemused

Meetod 2 – levinuima värviga pikslid

Teine meetod on natuke täpsem, kui esimene. Loeme iga piksli väärtuse esinemise sagedust.

img_temp = pilt_3.copy()
unique, counts = np.unique(img_temp.reshape(-1, 3), axis=0, return_counts=True)
img_temp[:,:,0], img_temp[:,:,1], img_temp[:,:,2] = unique[np.argmax(counts)]
show_img_compar(pilt_3, img_temp)
Selle pildi puhul on tausta hall kõige levinum värv ja tulemus ei ole see mida ootasime.
img_temp_2 = pilt_2.copy()
unique, counts = np.unique(img_temp_2.reshape(-1, 3), axis=0, return_counts=True)
img_temp_2[:,:,0], img_temp_2[:,:,1], img_temp_2[:,:,2] = unique[np.argmax(counts)]
show_img_compar(pilt_2, img_temp_2)

Meetod 3 – levinumad värvi grupid pildil

K-keskmiste klasteradmine – jagame pikslid värvi läheduse järgi klastritesse. Ja vaatame, mis on keskmine värv klastrites.

from sklearn.cluster import KMeans

clt = KMeans(n_clusters=5) # Klastrite arv

Funktsioon värvipaletti koostamiseks.

def palette(clusters):
    width=300
    height=50
    palette = np.zeros((height, width, 3), np.uint8)
    steps = width/clusters.cluster_centers_.shape[0]
    for idx, centers in enumerate(clusters.cluster_centers_): 
        palette[:, int(idx*steps):(int((idx+1)*steps)), :] = centers
    return palette
clt_1 = clt.fit(pilt_3.reshape(-1, 3))
show_img_compar(pilt_3, palette(clt_1))
clt_2 = clt.fit(pilt_2.reshape(-1, 3))
show_img_compar(pilt_2, palette(clt_2))

Meetod 4 – levinumad värvi grupid proportsionaalselt

Sisuliselt sama, mis eelmine aga leitud värve kuvab proportsionaalselt selle levikuga. Kui mingit värvi on rohkem, siis selle ristkülik on ka suurem ja vastupidi.

Abifunktsioon värvipaletti kuvamiseks:

from collections import Counter

def palette_perc(k_cluster):
    width = 300
    height = 50
    palette = np.zeros((height, width, 3), np.uint8)
    
    n_pixels = len(k_cluster.labels_)
    counter = Counter(k_cluster.labels_) # count how many pixels per cluster
    perc = {}
    for i in counter:
        perc[i] = np.round(counter[i]/n_pixels, 2)
    perc = dict(sorted(perc.items()))
    
    #for logging purposes
    #print(perc)
    #print(k_cluster.cluster_centers_)
    
    step = 0
    
    for idx, centers in enumerate(k_cluster.cluster_centers_): 
        palette[:, step:int(step + perc[idx]*width+1), :] = centers
        step += int(perc[idx]*width+1)
        
    return palette
clt_1 = clt.fit(pilt_3.reshape(-1, 3))
show_img_compar(pilt_3, palette_perc(clt_1))
clt_2 = clt.fit(pilt_2.reshape(-1, 3))
show_img_compar(pilt_2, palette_perc(clt_2))
pilt_4 = io.imread(kataloog + 'klaster1.jpg') # Impordime pildi
pilt_4 = cv.resize(pilt_4, dim, interpolation = cv.INTER_AREA) # Pilt väiksemaks
clt_4 = clt.fit(pilt_4.reshape(-1, 3))
show_img_compar(pilt_4, palette_perc(clt_4))
pilt_5 = io.imread(kataloog + 'wermo1.png') # Impordime pildi
#pilt_5 = cv.resize(pilt_5, dim, interpolation = cv.INTER_AREA) # Pilt väiksemaks
clt_5 = clt.fit(pilt_5.reshape(-1, 3))
show_img_compar(pilt_5, palette_perc(clt_5))
pilt_7 = io.imread(kataloog + 'kevad.jpg') # Impordime pildi
#pilt_7 = cv.resize(pilt_6, (500, 500) , interpolation = cv.INTER_AREA) # Pilt väiksemaks
clt_7 = clt.fit(pilt_7.reshape(-1, 3))
show_img_compar(pilt_7, palette_perc(clt_7))

Lingid:

Sissejuhatus pildiliste andmete töötlusse ja analüüsi 2.

Maskid

Maskid on selleks, et mingi osa pildist kinni katta.

pilt = imageio.imread(kataloog + 'image.jpg')

# seperate the row and column values  
total_row , total_col , layers = pilt.shape  
''' Create vector.
Ogrid is a compact method of creating a multidimensional
ndarray operations in single lines.     
for ex:     
>>> ogrid[0:5,0:5]     
output: [array([[0],
                [1],
                [2],
                [3],
                [4]]),
         array([[0, 1, 2, 3, 4]])]  
''' 
x , y = np.ogrid[:total_row , :total_col]  
# get the center values of the image 
cen_x , cen_y = total_row/2 , total_col/2  
'''    
 Measure distance value from center to each border pixel.
 To make it easy, we can think it's like, we draw a line from center-
 to each edge pixel value --> s**2 = (Y-y)**2 + (X-x)**2  
''' 
distance_from_the_center = np.sqrt((x-cen_x)**2 + (y-cen_y)**2)

radius = (total_row/2)  # Arvutame raadiuse
# Using logical operator '>'  
# logical operator to do this task which will return as a value
# of True for all the index according to the given condition. 
ymmargune_pilt = distance_from_the_center > radius  
 
pilt[ymmargune_pilt] = 0 # maski värv 0-255
#plt.figure(figsize = (5,5)) 
plt.imshow(pilt)  
plt.show()

Filtreerimine

Näiteks filtreerime välja pikslid mille väärtus on suurem, kui 200. Ja värvime need mustaks (anname väärtuse 0).

pilt = imageio.imread(kataloog +'image.jpg') 
valitud_pikslid = pilt > 200 #  

pilt[valitud_pikslid] = 0 # uue väärtuse andmine
plt.imshow(pilt) 
plt.show()

Maskide ja filtreerimise näide

nimi = 'katsekeha1.jpg'
pilt = imageio.imread(kataloog + nimi)

plt.title('Originaal pilt') 
plt.imshow(pilt)
plt.show()

Kuva ainult punased pikslid mille väärtus on väiksem, kui 235:

pilt = imageio.imread(kataloog + nimi)
red_mask = pilt[:, :, 0] > 235
pilt[red_mask] = 0 # Maski toon: 0 must, 255 valge
plt.imshow(pilt)

Ainult rohelised pikslid mille väärtus väiksem, kui 220:

pilt = imageio.imread(kataloog + nimi)
green_mask = pilt[:, :, 1] > 225
pilt[green_mask] = 0 # Maski toon: 0 must, 255 valge
plt.imshow(pilt)

Ainult sinised pikslid, mille väärtus väiksem, kui 200:

pilt = imageio.imread(kataloog + nimi)
blue_mask = pilt[:, :, 2] > 210
pilt[blue_mask] = 0
plt.imshow(pilt)

Pilt, kus on kõik kolm maski ühendatud:

pilt = imageio.imread(kataloog + nimi)
final_mask = np.logical_and(red_mask, green_mask, blue_mask)
pilt[final_mask] = 0 # Maski toon: 0 must, 255 valge
plt.imshow(pilt)

Histogram

Histogrammi saame kasutada, et näidada mingi värvitooni esinemissagedust pildil.

  • Histogram näitab värvitooni intensiivsuse jaotumist pildil.
  • Histogram näitab, kui palju mingi intensiivsusega piksleid on.
import cv2
import numpy as np
from matplotlib import pyplot as plt

kataloog = 'https://raw.githubusercontent.com/taunoe/jupyter-notebooks/main/Pildi-anal%C3%BC%C3%BCs/images/'
hall_img = imageio.imread(kataloog + 'image_hall.jpg')

hist,bins = np.histogram(hall_img, 256,[0,256])
plt.hist(hall_img.ravel(),256,[0,256])
plt.title('Hall toonides pildi histogramm')
plt.ylabel('Pikslite hulk')
plt.xlabel('Värvi väärtus')
plt.show()

Värvilise pildi RGB toonide histogram:

img = imageio.imread(kataloog + 'image.jpg')
plt.imshow(img)
plt.show()

color = ('b','g','r')
for channel,col in enumerate(color):
    histr = cv2.calcHist([img],[channel],None,[256],[0,256])
    plt.plot(histr,color = col)
    plt.xlim([0,256])
plt.title('RGB histogramm')
plt.ylabel('Pikslite hulk')
plt.xlabel('Värvi väärtus')
plt.show()

Lingid

Sissejuhatus pildiliste andmete töötlusse ja analüüsi 1.

Antud materjali koostamise eesmärgiks on leida viise, kuidas hinnata objektiivselt pilte ja neil olevat informatsiooni. Et tulemused oleksid mõõdetavad, võrreldavad ja neid saaks teostada automaatselt.

Töövahendite tutvustus

Jupyter märkmik

Jupyter notebook on veebipõhine interaktiivne keskkond, kus saab vaheldumisi kirjutada teksti ja käivitatavaid koodi (python) lahtreid. Et midagi arvutada, töödelda andmeid, kuvada graafikuid jne. Selle kasutamine on väga levinud andmeteadustes, masinõppes ja hariduses.

Colab on Google poolt majutatav Jupyter notebook.

Python

Python on üldotstarbeline programmeerimiskeel. Pythonit peetakse küllaltki lihtsaks keeleks, milles tavaliselt alustatakse programmeerimise õppimist. Kasutamise eeliseks on ka see, et on juba loodud väga palju valmis mooduleid ehk teeked (ingl. library) erinevate probleemide lahendamiseks. Näiteks Matplotlib graafikute loomiseks. NumPy suurte andmemassiivide töötlemiseks. PyTorch masinõppe jaoks. Tkinter graafiliste kasutajaliideste loomiseks ja palju muid.

Markdown

Teksti lahtrites olevat sisu saab kirjutada märgenduskeel Markdown abil. Selle abil saab lihtalt ja kiiresti kirjutada rikalikult vormindatud teksti.

Näiteks pealkirja loomiseks:

Mis on pilt?

Mis on pilt arvuti jaoks? Kuidas on pilt salvestatud arvutis? Kuidas arvutid näevad värve?

Nagu nimigi ütleb, arvutid arvutavad. Ja arvutada saab ainult numbritega. Kõik need YouTube videod, ajalehe artiklid ja kassi pildid on salvestatud arvutisse mingil kujul numbritena.

Kui me suurendame pilti sisse siis näeme, et see koosneb pisikestest ruutudest – pikslitest. Pilti võib vaadata, kui suurt mosaiiki, mis on kokku laotud pisikestest klotsidest. (Pilt 1)

Pikslid

Igas pikslis on kolm numbrit, mis kirjeldavad kolme värvi: punast, rohelist ja sinist. Neid kutsutakse RGB värvideks (Red, Green, Blue). Nendest piisab luua kõik värvitoonid, mida me ekraanil näeme. Iga number on salvestatud 8-bitise arvuna. See tähendab selle väärtus on 0 kuni 255-ni. Kokku 256 erinevat heleduse astet, kus 0 on must ja 255 on valge. Seda võib vaadata nii, et meil on kolme värvi tuled ja me saame muuta iga tule heledust. See number ütleb, kui intensiivne, mingi toon on. Kokku saab moodustada nii 16’777’216 värvitooni (256 * 256 * 256). (Pilt 2)

Värvijaotus halltoonides, punastes, rohelistes ja sinistes kanalites. Vasakul küljel on väärtus 0, paremal 255.

Nüüd võime pilti vaadata, kui ühte suurt Exceli tabelit, kus iga lahter on üks piksel. Tabeli laius ja kõrgus on pildi laius ja kõrgus. Meil on kahedimensiooniline tabel. Kuna ühes lahtris saame hoida ainult ühte arvu. Siis paneme RGB väärtused erinevatesse tabelitesse. Nii, et meil on üks tabeli kiht kus on punane (R), teine kus roheline (G) ja kolmandal sinine (B). Siia võib lisada ka neljanda läbipaistvuse (A), kui meil oleks png fail, mitte jpg. Neid nimetatakse kanaliteks ja mõelda võib neist, kui pildi kolmandast mõõtmest. (Pilt 3)

RGB kanalid

Neid numbreid saame me analüüsida ja teha nendega erinevaid arvutusi.

Näited

Impordime vajalikud teegid (Ing. Library).

import imageio                  # Piltide lugemiseks ja muutmiseks
import matplotlib.pyplot as plt # Skeemide koostamiseks
%matplotlib inline

Kuna kõik mu pildid on selles kataloogis salvestan selle eraldi muutujasse.

#Kõik pildid on siin kataloogis
kataloog = 'https://raw.githubusercontent.com/taunoe/jupyter-notebooks/main/Pildi-anal%C3%BC%C3%BCs/images/'

Kuvame pildi.

pilt = imageio.imread(kataloog + 'image.jpg')
plt.figure(figsize = (5,5))
plt.imshow(pilt)

Pildi info

Loeme põhilised andmed pildi kohta: pildi laius, kõrgus ja kihtide või mõõtmete arv. Kui on RGB pilt siis on 3 (kõrgus, laius, sügavus: RGB) mõõdet. Aga kui halltoonides pilt siis 2 (kõrgus, laius).

print('Pildi kuju (Shape): {}'.format(pilt.shape)) 
print('Pildi kõrgus: {}px'.format(pilt.shape[0])) 
print('Pildi laius: {}px'.format(pilt.shape[1]))
print('Mõõtmete (dimensioonide) arv: {}'.format(pilt.ndim))

Kui on halltoonides (Grayscale) pilt siis saame sellised andmed:

pilt_hall = imageio.imread(kataloog + 'image_hall.jpg')
print('Pildi kuju (Shape): {}'.format(pilt_hall.shape)) 
print('Pildi kõrgus: {}px'.format(pilt_hall.shape[0])) 
print('Pildi laius: {}px'.format(pilt_hall.shape[1]))
print('Mõõtmete arv: {}'.format(pilt_hall.ndim))

Järgmine pilt sisaldab ka läbipaistvuse (Alpha) kanalit.

pilt_alpha = imageio.imread(kataloog + 'image_alpha.png')
print('Pildi kuju (Shape): {}'.format(pilt_alpha.shape)) 
print('Pildi kõrgus: {}px'.format(pilt_alpha.shape[0])) 
print('Pildi laius: {}px'.format(pilt_alpha.shape[1])) 
print('Mõõtmete arv: {}'.format(pilt_alpha.ndim))

Pildi suuruse arvutamine:

print('Pildi suurus on {}'.format(pilt.size))

Suurima ja vähima RGB väärtuse leidmine:

print('Suurim RGB väärtus sellel pildil: {}'.format(pilt.max()))
print('Väikseim RGB väärtus sellel pildil: {}'.format(pilt.min()))

Ühe konkreetse piksli väärtuste vaatamine: piksel real (y-telg) 100 ja tulbas (x-telg) 50:

y = 100
x = 50
print('R kanal: {}'.format(pilt[ y, x, 0]))
print('G kanal: {}'.format(pilt[ y, x, 1]))
print('B kanal: {}'.format(pilt[ y, x, 2]))

Kanalite vaatamine

Vaatame igat pildi RGB kanalit eraldi:’

plt.title('R kanal') 
plt.ylabel('Kõrgus {}'.format(pilt.shape[0])) 
plt.xlabel('Laius {}'.format(pilt.shape[1])) 
plt.imshow(pilt[ : , : , 0])
plt.show()
R kanal
R kanal
plt.title('G kanal') 
plt.ylabel('Kõrgus {}'.format(pilt.shape[0])) 
plt.xlabel('Laius {}'.format(pilt.shape[1])) 
plt.imshow(pilt[ : , : , 1])
plt.show()
plt.title('B kanal') 
plt.ylabel('Kõrgus {}'.format(pilt.shape[0])) 
plt.xlabel('Laius {}'.format(pilt.shape[1])) 
plt.imshow(pilt[ : , : , 2])
plt.show()

Pikslite manipuleerimine

Muudame pildi mingis osas kanalite intensiivsust: Punasel (R) kanalil read 50 kuni 250. Muudame intensiivsuse maksimaalseks (255).

temp_1 = pilt.copy() # teeme pildist koopia
temp_1[50:250 , : , 0] = 255 # full intensity to those pixel's R channel 
plt.figure( figsize = (5,5)) 
plt.imshow(temp_1) 
plt.show()

Rohelisel (G) kanalil, read 250 kuni 450.

temp_1[250:450 , : , 1] = 255 # full intensity to those pixel's G channel 
plt.figure( figsize = (5,5)) 
plt.imshow(temp_1) 
plt.show()

Sinisel kanalil, read 450 kuni 600.

temp_1[450:600 , : , 2] = 255 # full intensity to those pixel's B channel 
plt.figure( figsize = (5,5)) 
plt.imshow(temp_1) 
plt.show()

RGB kanalid erinevatele piltidele

Impordime uue teegi numpy.

import numpy as np

fig, ax = plt.subplots(nrows = 1, ncols=3, figsize=(15,5))  
for c, ax in zip(range(3), ax):     
     # create zero matrix        
     split_img = np.zeros(pilt.shape, dtype="uint8") 
     # 'dtype' by default: 'numpy.float64'  # assing each channel      
     split_img[ :, :, c] = pilt[ :, :, c] # display each channel     
     ax.imshow(split_img)

Halltoonid

Halltoonides pilt on 2 mõõtmeline massiiv (Ing. array). Et viia pilti halltoonidesse peame kuidagi kokku liitma praegu kolmel erineval kihil oleva pildi info. Üks põhjus miks kasutatakse andmetöötluses halltoonides pilte on, et vähendad töödeldavate andmete hulka (1/3 võrreldes täis RGB pildiga). Üks võimalik valem on selleks:

Y′=0.299R+0.587G+0.114B

pilt = imageio.imread(kataloog +'image.jpg') 
gray = lambda rgb : np.dot(rgb[... , :3] , [0.299 , 0.587, 0.114])
gray = gray(pilt)
#plt.figure( figsize = (5,5))  
plt.imshow(gray, cmap = plt.get_cmap(name = 'gray')) 
plt.show()

Lingid