Long-exposure photography in another way

The traditional way to make long-exposure photography is to take photos with a long-duration shutter speed to sharply capture the stationary elements of images while blurring, smearing, or obscuring the moving elements. Long-exposure photography captures one element that conventional photography does not: an extended period of time. (1)

One day I watched Scott Manley video where he showed how he used video to produce long-exposure rocket launch photos. So I experimented with the code and random videos that I already had on my phone. Video is a sequence of photos and when we take the lightest part of every frame and merge it together into one image we get a result that is very similar to the traditional long-exposure technique.

One of my favourite photographers who use long-exposure photography and produces photos that look like paintings is Chris Friel. His works inspired me to make my MOVE photo series. Although I could not discover his technique secrets.

But I think using videos and OpenCV is a new way for me to experiment with ideas. For example, videos let you combine not only the lightest areas but also the darkest areas from frames. What is impossible with the traditional way.

One thing that I quickly learned is that the video should be short or your final image is completely white or black.

It’s definitely fun to make them and the result is often unpredictable.

Some quick experiments:

Gode on GitHub.

  1. Long-exposure photography
  2. https://taunoerik.art/project-type/move/
  3. https://www.cfriel.com/home
  4. https://opencv.org/
  5. https://github.com/taunoe/Valgusmaal

Continuous image capture from webcam

As part of an online course, I currently take “Computer Vision with Embedded Machine Learning” where is need to collect images to make the dataset. I modified the original code so I can use my regular Linux box and webcam, not Raspberry Pi. The programme counts down and saves images every 7 seconds. That is enough time to change the object position. It also crops and resizes images to 96×96 px size as needed.

This is my setup:

My setup

Example images from created dataset:

Dataset

And my code:

#!/usr/bin/env python3
"""
Image Capture

Displays image preview on screen.
Counts down and saves image 96*96px.
Restarts count down.
To exit press "q".

06.09.2021 Tauno Erik
"""

import cv2
import numpy as np

# Settings
file_num = 0
save_path = "./"      # Save images to current directory
file_suffix = ".png"  # Extension for image file
SECONDS_TO_COUNTDOWN = 7


def file_exists(filepath):
    """
    Returns true if file exists, false otherwise.
    """
    try:
        f = open(filepath, 'r')
        exists = True
        f.close()
    except:
        exists = False
    return exists

def get_filepath():
    """
    Returns the next available full path to image file
    """
    global file_num
    # Loop through possible file numbers to see if that file already exists
    filepath = save_path + str(file_num) + file_suffix
    while file_exists(filepath):
        file_num += 1
        filepath = save_path + str(file_num) + file_suffix

    return filepath

def main():
    countdown = SECONDS_TO_COUNTDOWN

    # Figure out the name of the output image filename
    filepath = get_filepath()

    cam = cv2.VideoCapture(0)

    # Set smaller resolution
    #cam.set(cv2.CAP_PROP_FRAME_WIDTH, 160) # 640
    #cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 120) # 480

    # Initial countdown timestamp
    countdown_timestamp = cv2.getTickCount()

    while cam.isOpened():
        # Read camera
        ret, frame = cam.read()

        # Get timestamp for calculating actual framerate
        timestamp = cv2.getTickCount()

        # Each second, decrement countdown
        if (timestamp - countdown_timestamp) / cv2.getTickFrequency() > 1.0:
            countdown_timestamp = cv2.getTickCount()
            countdown -= 1
            
            # When countdown reaches 0, break out of loop to save image
            if countdown <= 0:
                # Get new image file name
                filepath = get_filepath()
                # Save image
                cv2.imwrite(filepath, resized)
                # Start new count down
                countdown = SECONDS_TO_COUNTDOWN
                #break

        # Frame resolution
        frame_height = frame.shape[0]
        frame_width = frame.shape[1]

        # Crop center of image
        new_size = 98
        start_y = int(frame_height/2 - new_size/2)
        end_y = int(frame_height/2 + new_size/2)
        start_x = int(frame_width/2 - new_size/2)
        end_x = int(frame_width/2 + new_size/2)
        # Crop
        cropped = frame[start_y:end_y, start_x:end_x]

        # Rezise to 96*96
        resized = cv2.resize(cropped, (96,96), interpolation=cv2.INTER_CUBIC)

        # Put text only on copied image
        copy = resized.copy()
        # Draw countdown on image
        cv2.putText(copy, 
                    str(countdown),
                    (round(resized.shape[1] / 2) - 15, round(resized.shape[0] / 2)+10),
                    cv2.FONT_HERSHEY_PLAIN,
                    4,
                    (255, 255, 255))

        # Display raw camera image
        cv2.imshow('Kaamera', copy)

        # Press 'q' to exit
        if cv2.waitKey(10) == ord('q'):
            break

    # Clean up
    cam.release()
    cv2.destroyAllWindows()

if __name__ == "__main__":
    print('To exit press "q"')
    main()

Original code: https://github.com/ShawnHymel/computer-vision-with-embedded-machine-learning/blob/master/1.1.3%20-%20Data%20Collection/Raspberry%20Pi/pi-cam-capture.py

3. Sügavõpe: andmestikud, kadu, õpisamm ja mudeli parandamine

Minu Colab märkmikk. Fastai originaal Goodle Colab märkmikk.

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

from fastbook import *
from fastai.vision.all import *

Andmestiku uurimine

Andmestiku The Oxford-IIIT Pet Dataset veebileht.

path = untar_data(URLs.PETS) # Andmestik

Kuidas on andmestik (datset) organiseeritud?

Path.BASE_PATH = path

Et näha, mis andmestikus on:

path.ls()

Andmestik koosneb kahes kataloogist: images ja annotations. Veebileht ütleb, et märkuste (annotations) kataloog sisaldab, infot kus lemmikloomad asuvad, mitte seda mis nad. Kui meid asukoht ei huvita siis võime seda kataloogi praegu eirata.

Vaatame piltide kataloogi (images/):

(path/"images").ls()

Kuvatakse pilte arv (#7393) ja algust failide nimekirjast. Näeme, et failinimi koosneb tõu nimest numbrist ja faililaiendist. Koerte tõud on algavad väikse tähega ja kassid suure tähega.

Valime ühe faili:

fname = (path/"images").ls()[4]
fname

Regulaaravaldised

Kasutame regulaaravaldisi (regular expressions), et failinimest eralda ainult tõu nimi.

re.findall(r'(.+)_\d+.jpg$', fname.name)

Kasutame regulaaravaldist, et märgistada (label) kogu andmestik (dataset).

pets = DataBlock(blocks = (ImageBlock, CategoryBlock),
                 get_items=get_image_files, 
                 splitter=RandomSplitter(seed=42),
                 get_y=using_attr(RegexLabeller(r'(.+)_\d+.jpg$'), 'name'),
                 item_tfms=Resize(460),
                 batch_tfms=aug_transforms(size=224, min_scale=0.75))

dls = pets.dataloaders(path/"images")

Eelnev suuruste muutmine (Presizing)

DataBlock koodis on:

item_tfms=Resize(460)

batch_tfms=aug_transforms(size=224, min_scale=0.75)

Oleks hea, kui meie pildid oleks kõik ühesuurused.

Esialgu jätta pildid suhteliselt suureks (st suuremaks, kui treenimiseks kasutatavd). Et näiteks pilte keerates ei jääks hiljem nurkatessse tühjad pikslid.

item_tfms – kärpida (crop) pildid kas täis laiuses või kõrguses. Treening andmestiku valitakse kärpimise asukoht juhuslikult. Valiteerimisandmestiku alati keskmine ruut.

batch_tfms – juhuslik kärpimine ja andmete täiendamine (augmentations). GPU töötleb pilte väikeste hulkadena (batch).

Näide tulemust kui, andmestiku täiendada fastai (vasakul) ja traditsioonilisel meetodil (paremal).

#caption A comparison of fastai's data augmentation strategy (left) and the traditional approach (right).
dblock1 = DataBlock(blocks=(ImageBlock(), CategoryBlock()),
                   get_y=parent_label,
                   item_tfms=Resize(460))
# Place an image in the 'images/grizzly.jpg' subfolder where this notebook is located before running this
dls1 = dblock1.dataloaders([fname]*100, bs=8) #fname = pildi faili nimi
dls1.train.get_idxs = lambda: Inf.ones
x,y = dls1.valid.one_batch()
_,axs = subplots(1, 2)

x1 = TensorImage(x.clone())
x1 = x1.affine_coord(sz=224)
x1 = x1.rotate(draw=30, p=1.)
x1 = x1.zoom(draw=1.2, p=1.)
x1 = x1.warp(draw_x=-0.2, draw_y=0.2, p=1.)

tfms = setup_aug_tfms([Rotate(draw=30, p=1, size=224), Zoom(draw=1.2, p=1., size=224),
                       Warp(draw_x=-0.2, draw_y=0.2, p=1., size=224)])
x = Pipeline(tfms)(x)
#x.affine_coord(coord_tfm=coord_tfm, sz=size, mode=mode, pad_mode=pad_mode)
TensorImage(x[0]).show(ctx=axs[0])
TensorImage(x1[0]).show(ctx=axs[1]);

DataBlock‘i kontrollimine

Et olla kindel et andmestik on õieti märgistatud (label).

dls.show_batch(nrows=1, ncols=3)

summary meetod kuvab palju informatsiooni. Ja annab teada, kui oleme teinud mingi vea: näiteks unustanud teha pildid ühe suuruseks (Resize).

pets1 = DataBlock(blocks = (ImageBlock, CategoryBlock),
                 get_items=get_image_files, 
                 splitter=RandomSplitter(seed=42),
                 get_y=using_attr(RegexLabeller(r'(.+)_\d+.jpg$'), 'name'))
pets1.summary(path/"images")

Kui tundub, et kõik on hästi. Teeme katsetamiseks esialgu lihtsa mudeli, et näha kas kõik töötab. Kui treenimi võtab hästi kaua aega siis pole käitusaja tüüp GPU peal.

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(2) # 2 epohhi

Cross-Entropy Loss

Aktiveerimine ja märgistus

one_batchkuvad ühe ühiku tegelike andmeid DataLoader’i partiist (mini-batch). Tagastab sõltumatud (independent) ja sõltuvad (dependent) muutujad. Vastavalt x ja y.

x,y = dls.one_batch()

y # dependent variable

Partii (batch) suurus on 64: tensoris on 64 rida. Iga rida on number 0 ja 36 vahel. Numbrid tähistavad tõugu. Siin andmestikus on 37 tõugu.

Learner.get_preds – tagastab ennustused (predictions) ja eesmärgid (targets).

preds,_ = learn.get_preds(dl=[(x,y)])
preds[0]

len(preds[0]),preds[0].sum()

Softmax

Kasutame softmax aktiveerimisfunktsiooni viimasel kihil, et olla kindel aktiveerimine on 0 ja 1 vahel. Ja summa oleks kokku 1.

Softmax on sarnane sigmoidfunktsiooniga.

Kasutame sigmoidi siis, kui meil on ainult kaks kategooriat: näiteks kass või koer. Kui on rohkem, kui 2 kategooriat kasutame softmaxi.

Kuvame sigmoidi:

plot_function(torch.sigmoid, min=-4,max=4)

Log tõenäosus (Log Likelihood)

plot_function(torch.log, min=0,max=4)

Kui esmalt võtame softmax ja siis log likelihood sellest siis seda kobinatsiooni nimetatakse cross-entropy loss.

Mudeli tõlgendamine (interpretation)

Kuvame kogu andmestiku kohta confusion matrixi:

interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix(figsize=(12,12), dpi=60)

Et näha ainult neid millega kõige rohkem probleeme on:

interp.most_confused(min_val=5)

Mudeli täiustamine

transfer learning – siirdeõpe, ülekandeõpe

Õpisamm (Learning Rate)

On oluline leida õige õpisamm. Kui on liiga lühike (low) on vaja palju epohhe mudeli treenimiseks. See raiskab aega ja võib toimuda ülesobitumine.

Proovime pikka (high) sammuga treenida:

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(1, base_lr=0.1)

Ei tundu hea. Optimeerija sammus õiges suunas aga astus üle minimaalse kao (loss) punktist.

Kuidas leida õiget õpisammu?

learning rate finder

learn = cnn_learner(dls, resnet34, metrics=error_rate)
suggestion = learn.lr_find() # tundub see f on muutunud võrreldes raamatuga
suggestion

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(2, base_lr=0.0010) # raamat soovitab 3e-3

Ülekandeõpe (Transfer Learning)

Konvolutsioonilised närvivõrgud koosnevad paljudest lineaarsetest kihtidest mille vahel on mittelineaarsed aktiveerimis funktsioonid. Lõppus on ka lineaarne ja aktiveerimis funktsioon nagu softmax.

Eeltreenitud mudelite puhul eemaldame viimase, mis sisaldab kategooriaid milleks see mudel algselt oli treenitud. Ja asendame uuega, kus on õige arv väljundeid. Eelnevad kihid on juba treenitud äratundma üldiseid piltide osi. Viimane kiht tegeleb konkreetselt meie spetsiifilise ülesandega.

Sellel uuel kihil on juhuslikud parameetrid/kaalud (weights). Ja esmalt me treenime ainult seda kihti ühe korra st. muudame selle kihi kaale. Kõikide teiste kihtide kaalud ei muutu. Ja seejärel treenime kogu mudelit.

Katsetame seda manuaalselt:

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fit_one_cycle(3, 0.0010) # viimane kiht

Leiame uue õpisamu

learn.unfreeze()

sug = learn.lr_find()
sug

Treenime uuesti:

learn.fit_one_cycle(6, lr_max=0.0001)

Diskrimineerivad õpisammud

Discriminative Learning Rates

Esimesed kihid (layers) mudelis lihtsaid asju nagu serva äratundmine, heleduse/tumeduse üleminekud, jooned jne. Tagumised kihid õpivad juba konkreetsemaid asju ära tundma nagu silm, päikseloojang jne. Seega vajadus ümberõppida on suurem viimastel kihtidel. Esimestel kihtidel peaks õpisamm (learning rate) olema väiksem (lower) ja suurem (higher) viimastel kihtidel.

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fit_one_cycle(3, 0.0010)
learn.unfreeze()
learn.fit_one_cycle(12, lr_max=slice(1e-5,1e-3))

Kuvame graaviku treening ja valideerimiskaost (loss).

learn.recorder.plot_loss()

Punkt, kust valideerimis andmed hakkavad halvenemea aga treening andmed paranevad on koht, kus toimub ülesobitumine (over fit).

mixed-precision training – võimaluse korral vähem täpsete arvude kasutamine (Pooltäpsed ujukomaarvud – fp16) treeningu ajal. Peaaegu kõigil uutel NVIDA GPUdel on spetsiaalne tensor cores, mis võimaldab treenimisaega kiirendada 2-3 korda.

from fastai.callback.fp16 import *

learn = cnn_learner(dls, resnet50, metrics=error_rate).to_fp16()
learn.fine_tune(6, freeze_epochs=3)

2. MNIST ja sügavõpe

Minu Google Colab märkmik aga parem vaata fastai 04_mnist_basic.

Üks tuntumaid andmestike (dataset) on MNIST, mis sisaldab käsitsikirjutatud numbreid. Seda kasutati ühes esimeses praktilises käsitsikirjutatud numbrijärjestuste äratundmise süsteemis Lenet-5 aastal 1998.

Laemealla MNIST_SAMPLE andmestiku. See sisaldab ainult numbreid 3 ja 7!

path = untar_data(URLs.MNIST_SAMPLE)

Path.BASE_PATH = path

Kuvame selle kataloogi (path) sisu:

path.ls()

Masinõppe andmestikud (datasets) on tavaliselt jaotatud eraldi kataloogideks: treeningandmestik (training set) ja valideerimisandmestik (validation set).

Vaatame, mis on treeningandmestikus:

(path/'train').ls()

Näeme, et on eraldi kataloogid 3 ja 7. Need on märgendid (labels) (või eesmärgid (targets)) selles andmestikus.

Vaatame veel sügavamale:

threes = (path/'train'/'3').ls().sorted()
sevens = (path/'train'/'7').ls().sorted()
threes

Näeme, et need kataloogid sisaldavad pilte numbritest.

Kuvame ühe neist piltidest:

im3_path = threes[5]
im3 = Image.open(im3_path)
im3

Arvutis on kõik esindatud numbritena. Et näha numbreid (piksleid) millest see pilt koosneb peame selle muutma Numpy array-ks või PyTorch tensor-iks.

NumPy array näeb välja selline:

array(im3)[4:10,4:10]

4:10 tähendab, et näitab ridu 4 kuni 10 ja sama ka tulpade kohta. Kuni 10 tähendab, et kuvab kohani kohani kust hakkab rida 10 st viimane rida mida näeme on 9.

PyTorch tensor näeb välja selline:

tensor(im3)[4:10,4:10]

Saame kuvada veel täpsemini pilti ja pikslite väärtusi:

im3_t = tensor(im3)
df = pd.DataFrame(im3_t[4:15,4:22])
df.style.set_properties(**{'font-size':'6pt'}).background_gradient('Greys')
Andmestik koosneb piltidest 28*28 pikslit. Kokku 784px üks pilt.

Andmestik koosneb piltidest 28*28 pikslit. Kokku 784px üks pilt.

Meetod 1: Pikslite sarnasus

Keskmine pikslite väärtus igas gruppis.

Loome tensori, mis sisaldab kõiki kolmede ja seitsmete pilte. Kasutades selleks list comprehension. Selle tulemusena saame listi, kus on kõik pildid.

new_list = [f(o) for o in a_list if o>0]

Tagastab iga elemendi listist a_list, mis on suurem, kui 0. Lastes selle enne läbi funtsioonist f(o). if o>0 on valikuline filter.

new_list = [f(o) for o in a_list]

seven_tensors = [tensor(Image.open(o)) for o in sevens]
three_tensors = [tensor(Image.open(o)) for o in threes]
len(three_tensors),len(seven_tensors)

Kontrollime, kuvades ühe pildi tensorist:

show_image(three_tensors[1]);

Kombineerime kõik pildi kokku üheks kolme dimensiooniliseks tensoriks. Seda kutsutakse rank-3 tensor. Kasutame selleks PyTorch funktsiooni stack.

Kuna hiljem on vaja arvutada keskmiseid (mean) peame muutma väärtused ujukomaarvudeks (float).

Kui pildid on esitatud ujukomaarvudena (float) on pikslite väärtused 0 ja 1 vahel. Selleks jagame piksli väärtuse 255.

stacked_sevens = torch.stack(seven_tensors).float()/255
stacked_threes = torch.stack(three_tensors).float()/255
stacked_threes.shape

shape – on tensori dimensioonide mõõtmed. 6161 sügavus (pilti), laius-kõrgus on 28×28.

rank – on tensori dimensioonide arv rank = len(stacked_threes.shape) või rank = stacked_threes.ndim

len(stacked_threes.shape) # Esimene võimalus
stacked_threes.ndim       # Teine võimalus

Arvutame nüüd keskmise (mean) pikslite väärtuse iga numbri puhul. Saame ideaalse kolme, mis esindab kõiki kolmesid.

mean3 = stacked_threes.mean(0)
show_image(mean3);
mean7 = stacked_sevens.mean(0)
show_image(mean7);

Number on must seal, kus kõik pildid nõustuvad, et peaks olema must ja udusem ja heledam seal, kus erinevad pildid on erineval arvamusel.

a_3 = stacked_threes[1]
show_image(a_3);

Et mõõta, kui sarnased (lähedal) või erinevad (kaugel) pildid on, on kaks võimalust:

  • Kahe pildi erinevuste absoluutväärtuste keskmised. mean absolute difference or L1 norm
  • Erinevuste ruudu (teem kõik positiivseks) keskmine ja seejärel ruutjuur. Root mean squared error (RMSE) or L2 norm
dist_3_abs = (a_3 - mean3).abs().mean()
dist_3_sqr = ((a_3 - mean3)**2).mean().sqrt()
dist_3_abs,dist_3_sqr
dist_7_abs = (a_3 - mean7).abs().mean()
dist_7_sqr = ((a_3 - mean7)**2).mean().sqrt()
dist_7_abs,dist_7_sqr

Mõlemal juhul on distants meie 3 ja ideaalse 3 vahel väiksem, kui distants ideaalse 7ni. Seega meie lihtne mudel annab õige vastuse.

PyTorch pakub juba mõlemat neist juba kahjufunktsioonina (loss functions).

Need on juba imporditud fastai’ga. Aga muul juhul import torch.nn.functional as F

F.l1_loss(a_3.float(),mean7), F.mse_loss(a_3,mean7).sqrt()

mse – mean squared error

l1 – mean absolute value (in math it’s called the L1 norm).

L1 loss is just equal to (a-b).abs().mean(), where a and b are tensors.

NumPy array ja PyTorch tensorid

NumPy on kõige levinum teek teaduslike arvutuste jaoks. NumPy arrayd ja PyTorch tsensorid on väga sarnased aga NumPy ei toeta GPU kasutamist, mis on sügavõppes väga levinud.

Python ise on väga aegalane võrreldes kompileeritavate keeltega (c/c++, rust jne). Kõik kiired osad Pythonis, NumPy või PyTorchis on tegelikul kirjutatud C-keels ja neil on Pythoni liides, et oleks lihtne kasutada.

NumPy array on mitmedimensiooniline ühetüübiliste andmete tabel.

Array ja tensori loomine:

data = [[1,2,3],[4,5,6]]
arr = array (data) # numpy
tns = tensor(data) # pytorch

# Kuvame
arr
tns

Rea valimine:

tns[1] # Lugemine 0ist

Tulba valimine:

tns[:,1]

Mingi osa valimine:

tns[1,1:3]

Kasutada operaatoreid +, -, *, /:

tns+1

Tensoril on tüüp:

tns.type()

Muudab automaatselt tsensori tüüpi.

tns*1.5 # into to float

Mõõdik (Metric)

Loome tensori valideerimisandmestikust

valid_3_tens = torch.stack([tensor(Image.open(o)) 
                            for o in (path/'valid'/'3').ls()])
valid_3_tens = valid_3_tens.float()/255

valid_7_tens = torch.stack([tensor(Image.open(o)) 
                            for o in (path/'valid'/'7').ls()])
valid_7_tens = valid_7_tens.float()/255

valid_3_tens.shape,valid_7_tens.shape # Kontrollime tensori kuju

Lihtne funktsioon, mis arvutav kahe pildi kauguse (erinevuse):

mean3 on meie ideaalne 3 a_3 on suvaline 3

def mnist_distance(a,b):
  # abs - absolute values
  # -1 viimane elememt, -2 eelviimane element
  return (a-b).abs().mean((-1,-2)) # valime 2 viimast Dimensiooni laius/kõrgus

mnist_distance(a_3, mean3)

Kui tahame korraga arvuta kõigi valideerimisanmestikus olevate pilti kaugust saame terve tensori korraga lasta funktsioonist läbi: broadcasting !

valid_3_dist = mnist_distance(valid_3_tens, mean3) # broadcasting!
valid_3_dist, valid_3_dist.shape
Kas on 3 või ei ole. Kui tundmatu pildi (x) kaugus ideaalsest 3 on väiksem, kui kaugus ideaalset 7 siis on ta 3.
def is_3(x):
  return mnist_distance(x,mean3) < mnist_distance(x,mean7)

Katsetame. Kui muudame True ujukomaks saame 1.0 ja False 0.0

is_3(a_3), is_3(a_3).float() # True ja 1. (== True)
is_3(valid_3_tens)

Nüüd arvutame kõigi 3 ja 7 täpsuse

accuracy_3s =      is_3(valid_3_tens).float() .mean()
accuracy_7s = (1 - is_3(valid_7_tens).float()).mean()

accuracy_3s,accuracy_7s,(accuracy_3s+accuracy_7s)/2

Meetod 2. Stochastic Gradient Descent (SGD)

Sügavõppe mudeli treenimise ettapid:

  1. Initsialiseeri kaalud (weights)
  2. Kasuta neid kaalusid, et ennustada (predict) kas pilt on 3 või 7.
  3. Ennustuste abil arvuta, kui hea mudel on (loss).
  4. Arvuta gradient, mis mõõdab iga kaalu kohta, kuidas selle kaalu muutus muudab mudeli headust (loss).
  5. Step Muuda kõiki kaalusid selle arvutuse põhjal.
  6. Tagasi punkti 2. ja korda.
  7. Korda niikaua, kui mudel on piisavalt hea.

Illustreerime lihtsa näidisega

Ütleme, et see on meie kaofunktsioon (loss function):

def f(x): return x**2
plot_function(f, 'x', 'x**2')

Valime juhusliku väärtuse kaalule (parameter) ja arvutame kao (loss).

plot_function(f, 'x', 'x**2')
plt.scatter(-1.5, f(-1.5), color='red');

Nüüd vaatame, mis juhtub, kui suurendame või vähendame natuke kaalu.

Gradient’i arvutamine

Valime tensori väärtuse mille gradienti soovime:

xt = tensor(3.).requires_grad_()

Arvutame funktisooni:

yt = f(xt)
yt

Arvutame gradiendi:

yt.backward()

backward – backpropagation


Vaatame gradienti:

xt.grad
xt = tensor([3.,4.,10.]).requires_grad_()
xt

Kordame vektor argumendiga

xt = tensor([3.,4.,10.]).requires_grad_()
xt

Lisame funktsiooni summa, et saaks võtta vektori (rnk-1 tensor) ja tagasta skaalari (rank-0 tensor):

def f(x): return (x**2).sum()

yt = f(xt)
yt

Meie gradient on 2*xt

yt.backward()
xt.grad

Gradiendid ütlevad meile ainult funktsiooni kallaku. Kui kallak on väga suur on meil vaja teha rohkem kohandusi. Kui väike, väike võib see tähendada, et oleme optimaalse lähedal.

Otsus, kuidas muuta parameetreid gradiendi põhjal on oluline osa õppimis protsessis. Peaaegu kõik lähenemised algavad põhiideest gradienti korrutamiseks mõne väikse arvuga, mida nimetetakse õpimääraks (LR learning rate) On tihti number 0.001 ja 0.1 vahel.

w -= gradient(w) * lr

This is known as stepping your parameters, using an optimizer step.

Kui valida liiga väike õppemäär (learning rate) võib see tähendada väga paljude sammude (steps) tegmist. Liiga suur õppemäär võib tähenda kao (loss) suurenemist.

SGD näide

time = torch.arange(0,20).float();
time

speed = torch.randn(20)*3 + 0.75*(time-9.5)**2 + 1
plt.scatter(time,speed);
def f(t, params):
    a,b,c = params
    return a*(t**2) + (b*t) + c
def mse(preds, targets):
  return ((preds-targets)**2).mean().sqrt()

1. Parameetrite initsialiseerimine

Juhuslike väärtustega. Gradientide jälgimine

params = torch.randn(3).requires_grad_()

orig_params = params.clone()

2. Ennustuse arvutamine

preds = f(time, params)

Funktsioon kui lähedal, meie ennustused on:

def show_preds(preds, ax=None):
    if ax is None: ax=plt.subplots()[1]
    ax.scatter(time, speed)
    ax.scatter(time, to_np(preds), color='red') # ennustused on punased
    ax.set_ylim(-300,100)

show_preds(preds)

3. Kao arvutamine

loss = mse(preds, speed)
loss

4. Gradiendi arvutamine

ehk arvutada parameetrite muutmise vajadus.

loss.backward()
params.grad

params.grad * 1e-5 # Learning rate = 0.00001

params

5. Parameetrite ehk kaalude (weights) sammuvõrra suurendamine

lr = 1e-5
params.data -= lr * params.grad.data
params.grad = None

Vaatma kas kadu (loss) on paranenud:

preds = f(time,params)
mse(preds, speed)

show_preds(preds)

Peame kordama seda mitu korda. Et oleks lihtsam teeme funktsiooni:

def apply_step(params, prn=True):
    preds = f(time, params)
    loss = mse(preds, speed)
    loss.backward()
    params.data -= lr * params.grad.data
    params.grad = None
    if prn: print(loss.item())
    return preds

6. Korda

for i in range(10): apply_step(params)
params = orig_params.detach().requires_grad_()

Kadu (loss) väheneb.

_,axs = plt.subplots(1,4,figsize=(12,3))

for ax in axs:
  show_preds(apply_step(params, False), ax)

plt.tight_layout()

7. Stop

Pärast 10 epohhi oleme otsustanud lõpetada.

Kokkuvõte

Parameetrid (kaalud – weights) võivad algul on juhuslikud, kui treenime nullist. Või tulla eeltreenitud mudelist (transfer learning). Mõlemal juhul peab mudel õppima, et leida paremaid parameetreid (weights).

Võrdleme mudelite väljundeid eesmärgiga (targets) kasutades kaofuntktsiooni (loss), mis peab olema võimalikult väike. Meil on märgendatud andmed seega teame millised meie eesmärgid välja näevad.

Parameetrite muutmiseks arvutame gradiendi.

MNISTi kaofunktsioon (Loss Function)

Kõik pildid ühte tensorisse (independent variable) ja muudame nad maatriksite nimekirjast (list of matrices, a rank-3 tensor) vektorite nimekirjaks (list of vectors, a rank-2 tensor). Tähistame X-iga.

train_x = torch.cat([stacked_threes, stacked_sevens]).view(-1, 28*28)

Märgitame kõik pildid. 1 tähistab kolme ja 0 tähistab seitset.

train_y = tensor([1]*len(threes) + [0]*len(sevens)).unsqueeze(1)
train_x.shape,train_y.shape

Indekseerimisel PyTorchis peab andemkogu tagastame tuple (x, y)

dset = list(zip(train_x,train_y))
x,y = dset[0]
x.shape,y

valid_x = torch.cat([valid_3_tens, valid_7_tens]).view(-1, 28*28)
valid_y = tensor([1]*len(valid_3_tens) + [0]*len(valid_7_tens)).unsqueeze(1)
valid_dset = list(zip(valid_x,valid_y))

Initsialiseerimine

def init_params(size, std=1.0):
  return (torch.randn(size)*std).requires_grad_()

weights = init_params((28*28,1))

bias = init_params(1)

Kaalud (weight) ja kallutatus (bias) moodustavad kokku parameetri.


Ühe pildi ennustus:

(train_x[0]*weights.T).sum() + bias

Korrutame maatriksid omavahel:

def linear1(xb):
  return xb@weights + bias
  
preds = linear1(train_x)
preds

Kontrollime täpsust

corrects = (preds>0.5).float() == train_y
corrects

corrects.float().mean().item()

preds = linear1(train_x)
((preds>0.0).float() == train_y).float().mean().item()

Kaofunktsioon

trgts  = tensor([1,0,1])
prds   = tensor([0.9, 0.4, 0.2])

def mnist_loss(predictions, targets):
    return torch.where(targets==1, 1-predictions, predictions).mean()

torch.where(trgts==1, 1-prds, prds)

mnist_loss(prds,trgts)

mnist_loss(tensor([0.9, 0.4, 0.8]),trgts)

Sigmoid

Sigmoidfunktsiooni väljund on alati 0 ja 1 vahel.

Näidis: (PuTorchil on oma seega pole tegelikult vaja)

def sigmoid(x):
  return 1/(1+torch.exp(-x))

plot_function(torch.sigmoid, title='Sigmoid', min=-4, max=4)

Uuendame mnist_loss funktsiooni

def mnist_loss(predictions, targets):
    predictions = predictions.sigmoid()
    return torch.where(targets==1, 1-predictions, predictions).mean()

Initsialiseerime uuesti:

weights = init_params((28*28,1))
bias = init_params(1)

Loome DataLoaderi andmestikust (dataset)

dl = DataLoader(dset, batch_size=256)
xb,yb = first(dl)
xb.shape,yb.shape

Valideerimisandmekogu DataLoader

valid_dl = DataLoader(valid_dset, batch_size=256)

Mini-batch suurusega 4

batch = train_x[:4]
batch.shape

preds = linear1(batch)
preds

loss = mnist_loss(preds, train_y[:4])
loss

Arvutame gradiendi

loss.backward()
weights.grad.shape,weights.grad.mean(),bias.grad

Paneme selle kõik funktsiooni

def calc_grad(xb, yb, model):
    preds = model(xb)
    loss = mnist_loss(preds, yb)
    loss.backward()

testime seda

calc_grad(batch, train_y[:4], linear1)
weights.grad.mean(),bias.grad

# set the current gradients to 0 first:
weights.grad.zero_()
bias.grad.zero_();
def train_epoch(model, lr, params):
    for xb,yb in dl:
        calc_grad(xb, yb, model)
        for p in params:
            p.data -= p.grad*lr
            p.grad.zero_()

def batch_accuracy(xb, yb):
    preds = xb.sigmoid()
    correct = (preds>0.5) == yb
    return correct.float().mean()

batch_accuracy(linear1(batch), train_y[:4])
def validate_epoch(model):
    accs = [batch_accuracy(model(xb), yb) for xb,yb in valid_dl]
    return round(torch.stack(accs).mean().item(), 4)

validate_epoch(linear1)

Treenime 1 epohhi

lr = 1.
params = weights,bias
train_epoch(linear1, lr, params)
validate_epoch(linear1)

ja veel mõned korrad

for i in range(20):
    train_epoch(linear1, lr, params)
    print(validate_epoch(linear1), end=' ')

Optimeerija loomine

Ssendame meie linera1 funktsiooni PyTorchi nn.Linear mooduliga. nn.Linear ühendab endas init_params ja linear.

linear_model = nn.Linear(28*28,1)

w,b = linear_model.parameters()
w.shape,b.shape

Teeme optimeerija

class BasicOptim:
    def __init__(self,params,lr): self.params,self.lr = list(params),lr

    def step(self, *args, **kwargs):
        for p in self.params: p.data -= p.grad.data * self.lr

    def zero_grad(self, *args, **kwargs):
        for p in self.params: p.grad = None

opt = BasicOptim(linear_model.parameters(), lr)

def train_epoch(model):
    for xb,yb in dl:
        calc_grad(xb, yb, model)
        opt.step()
        opt.zero_grad()

# Valideerimine
validate_epoch(linear_model)

def train_model(model, epochs):
    for i in range(epochs):
        train_epoch(model)
        print(validate_epoch(model), end=' ')

train_model(linear_model, 20)

Fastai SGD class teeb sama, mis meie BasicOtim

linear_model = nn.Linear(28*28,1)
opt = SGD(linear_model.parameters(), lr)
train_model(linear_model, 20)

fastai on ka Learner-fit mida saame kasutada train_model asemel

dls = DataLoaders(dl, valid_dl)

learn = Learner(dls, nn.Linear(28*28,1), opt_func=SGD,
                loss_func=mnist_loss, metrics=batch_accuracy)

learn.fit(10, lr=lr)

Mittelineaarsuse lisamine

Lihtne närvivõrk:

def simple_net(xb): 
    res = xb@w1 + b1
    res = res.max(tensor(0.0))
    res = res@w2 + b2
    return res

w1 = init_params((28*28,30)) # weight tensor
b1 = init_params(30)         # bias tensor
w2 = init_params((30,1))
b2 = init_params(1)

w1’l on 30 väljundi aktiveerimist. st. w2’l peab olema 30 sisendi aktiveerimist.

Esimene kiht saab konstrueerida 30 erinevat omadust (feature), mis on kõik mingite pikslite segu. 30 asemel võib olla ükskõik mis teine number.


Funktsioon res.max(tensor(0.0)) on ReLU – rectified linear unit. PyTorch’is on see F.relu. Asendab iga negatiivse numbri nulligaga (0).

plot_function(F.relu)
# module
simple_net = nn.Sequential(
    nn.Linear(28*28,30),  # Linear layer
    nn.ReLU(),            # Nonlinear layer - activation function
    nn.Linear(30,1)       # Linear layer
)

learn = Learner(dls, simple_net, opt_func=SGD,
                loss_func=mnist_loss, metrics=batch_accuracy)

learn.fit(40, 0.1)

plt.plot(L(learn.recorder.values).itemgot(2));
learn.recorder.values[-1][2]

Testime 18 kihilist mudelit:

dls = ImageDataLoaders.from_folder(path) # /root/.fastai/data/mnist_sample
learn = cnn_learner(dls, resnet18, pretrained=False,
                    loss_func=F.cross_entropy, metrics=accuracy)
learn.fit_one_cycle(1, 0.1)

Lingid:

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

0. Sügavõpe (Deep learning)

Märkmed fast.ai kursusest enda jaoks. Ei ole ülevaatlik ega põhjalik kõikides osades.

Originaal märkmik koos inglisekeelsete põhjalike selgitavate tekstiga: 01.intro.ipynb

  • Vajalik on NVIDIA graafikakaart (GPU – Graphics Processing Unit). Need, mis on head mängimiseks ja 3D jaoks sobivad üldiselt ka sügavõppe jaoks. Google Colabis tuleb GPU kasutamine eraldi aktiveerida.
  • Keskkonnaks Jupyter notebook. Kas oma arvutis või Google Colab, Gradient jne.
  • Koodi käivitamine lahtris: Shift+Enter
  • Igal mudelil on kaks sisendit: 1. andmed (inputs) ja 2. mudeli parameetrid/ kaalud (weights, model parameters).
Mudeli treenimine (vanem terminoloogia)
Mudeli treenimine kaasaegsetes terminites
  • Treenitud mudel on nagu tavaline arvutiprogaamm.
Treenitud mudel
  • Mudelit ei saa luua ilma andmeteta.
  • Andmed peavad olema sildistatud (labels)
  • Mudel väljastab ennustuse/tõenäosuse (predictions) 0-100%
  • classification – ennustab klassi või kategooriat. Näiteks: kass, koer jne.
  • regression – ennustab numbrilist väärtust. Näiteks: temperatuur, asukoht
  • Andmehulk jagatakse valideerimis (validation set) 20% ja treening hulgaks (training set) 80%.
  • Vältida mudeli ülesobitumist (overfitting), kus mudel jätab meelde konkreetsed andmed ja ei üldistu enam uutele andmetele.
  • pretrained model – Eeltreenitud mudel on mudel mida on juba treenitud mingil teisel andmehulgal. Enamusel juhtudel on soovitatv kasutada eeltreenitud mudeleid. Kuna need on juba enne meie andmete sissesöötmist väga võimekad.
  • Pildituvastus mudeleid saab kasutada ka, esmapilgul , mitte pildiliste andmete jaoks. Nagu heli mille saab muuta spektrogrammiks. Aegridu saab muuta graafikuks. Arvuti hiire liikumist matil värvilisteks joonteks jne.

Lühikokkuvõte masinõppest

  • Masinõpe on olukord, kus me ei kirjuta programmi loogikat ise algusest peale, vaid programmi osa õpib ise loogika andmete pealt.
  • Sügavõpe (deep learning) on närvivõrk (neural network) paljude kihtidega (layers). Pildi klassifikatsioon või pildi äratundmine on tüüpiline näide. Alustuseks on sildistatud (labeled) andmehulk st. igal pildil on silt mida see kujutab. Eesmärk on programm ehk mudel, millele andes uue pildi tagastab ennustuse (predictioni) selle kohta mida see kujutab.
  • Iga mudel algab arhitektuuri valimisega. Arhitektuur on üldine mall selle kohta, kuidas see mudel sisemiselt töötab.
  • Treenimine (training or fitting) on protsess, arhitektuurist tulenevate, parameetrite väärtuste (parameter values or weights) kogumi leidmiseks, mis sobiks konkreetselt meie andmete jaoks.
  • Selleks, et määrata, kui hästi mudel töötab ühe ennustusega peame määrama kahju funktsiooni (loss function), mis määrab, kui heaks või halvaks me hindame ennustust.
  • Et treening protsess oleks kiirem saame kasutada eeltreenitud (pretrained) mudelit. Mudel mida on juba treenitud kellegi teise andmete peal. Peame seda ainult natuke treenima oma andmete peal (fine-tuning).
  • Mudeli treenimisel on oluline, et mudel üldistuks (generalize) hästi. Et ta töötaks hästi uutel andmetel, mida ta pole varem näinud. Ülesobitumine (overfitting) on olukord, kus mudel töötab väga hästi treening andmetel aga mitte uutel andmetel. Mudel nö õppinud pähe konkreetsed andmed.
  • Selle vältimiseks jagatakse andmed kaheks: treening (training set) ja valideerimisandmestikuks (validation set).
  • Selleks, et inimene saaks hinnata, kui hästi mudelil valideerimis lähem määrame mõõdiku (metric).
  • Kui mudel on treeningu käigus kõiki treening andmeid näinud – kutsutakse seda epohhiks (epoch).

Kasside ja koerte äratundmine

Colab Märkmik kogu koodiga.

  • Kasutab Oxford ülikooli poolt koostatud kasside-koerte andmehulka Oxford-IIIT Pet Dataset.
  • Kasutame mudelit, mida on juba treenitud 1,3 miljoni pildiga (pretrained model).
  • Toimub eeltreenitud mudeli viimistlemine ja kohandamine spetsiaalselt kasside ja koerte piltide äratundmiseks. Kasutades selleks ülekandeõpet (transfer learning).

0.

0. Et fastai töötaks Google Colabis on vajalik kõigepealt:

!pip install -Uqq fastbook
import fastbook
fastbook.setup_book()
from fastbook import *
  1. Lisame fastai.vision teegi:
from fastai.vision.all import *

2. Andmehulk. Laeb alla standard andmehulga (dataset), pakib lahti ja tagastab selle asukoha (path).

path = untar_data(URLs.PETS)/'images'

3. Abifunktsioon. Sildistab kasside pildid faili nimejärgi. Andekogumi loojate poolt loodud reeglialusel.

def is_cat(x):
  # Tagastab True, kui esimene täht on suurtäht st. on kass
  return x[0].isupper()

Ütleb fastaile, milline andekogum meil on ja kuidas see on struktureeritud. 224px on ajalooline standard ja paljud vanemad mudelid nõuavad selles suuruses pilte. Suuremad pildid võivad anda paremaid tulemusi, kuna pildile jääb rohkem detaile. Aga selle hinnaks on suurenev töötlusaeg ja vajaminev mäluhulk.

dls = ImageDataLoaders.from_name_func(
    path,
    get_image_files(path),
    valid_pct=0.2,        # Jätab 20% andmetest valideerimiseks
    seed=42,              # Määrab juhusliku seemne samale väärtusele igal koodi käivitamisel
    label_func=is_cat,    # Sildistamine
    item_tfms=Resize(224) # Iga pilt muudetakse 224 piksli suuruseks ruuduks 
)

4. Treenime mudeli.

Närvivõrgu tüüp: convolutional neural network (CNN). On praegu kõige populaarsem masinnägemis mudelite loomisel. Inspireeritud inimese nägemissüsteemi toimimisest.

Närvivõrgu arhitektuur: ResNet, 34 – kihtide (layers) arv. Kihtide arv võib olla veel 18, 50, 101 ja 152. Mida rohkem kihte seda kauem võtab treenimine aega ja seda suurem on oht ülesobitumisele (overfitting). Kui andmeid on vähem on vaja ka vähem kihte ja vastupidi.

metrics on funktsioon, mis möödab ennustuse kvaliteeti igal epochil. Antud juhul: error_rate – tagastab valesti ennustatud piltide protsendi. Teise võimalus accuracy, mis tagastab: 1.0 – error_rate

pretrained – kui me eraldi ei määra on True. Nagu antud juhul. On eeltreenitud ImageNet andmehulgal, mis sisaldab üle 1,4 miljoni pildi.

Kui kasutada eeltreenitud mudelit, siss cnn_learner eemaldab mudeli viimase kihi ja asendab selle uue kihi või kihtidega, mis on kohandatud uute andmete jaoks. Viimast osa kutsutakse, ka peaks (head).

Eeltreenitud mudeli (pretrained model) kasutamist teise ülesande jaoks, kui see oli algselt treenitud tuntakse ka kui siirdeõpe/ülekandeõpe (transfer learning).

fine_tune – sobitab (fit) mudeli. Epohhide (epochs) arv. Kui mitu korda igat pilti vaadatakse. Kasutakse siirdeõpe puhul mudeli parameetrite uuendamiseks.

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(1) # fit model

5. Katsetame oma pildiga.

Loob pildi üles laadimise:

uploader = widgets.FileUpload()
uploader
img = PILImage.create(uploader.data[0])
is_cat,_,probs = learn.predict(img)
print(f"Kas see on kass?: {is_cat}.")
print(f"Tõenäosus: {probs[1].item():.6f}")

Terminid

Term Meaning
Label The data that we’re trying to predict, such as "dog" or "cat"
Architecture The template of the model that we’re trying to fit; the actual mathematical function that we’re passing the input data and parameters to
Model The combination of the architecture with a particular set of parameters
Parameters The values in the model that change what task it can do, and are updated through model training
Fit Update the parameters of the model such that the predictions of the model using the input data match the target labels
Train A synonym for fit
Pretrained model A model that has already been trained, generally using a large dataset, and will be fine-tuned
Fine-tune Update a pretrained model for a different task
Epoch One complete pass through the input data
Loss A measure of how good the model is, chosen to drive training via SGD
Metric A measurement of how good the model is, using the validation set, chosen for human consumption
Validation set A set of data held out from training, used only for measuring how good the model is
Training set The data used for fitting the model; does not include any data from the validation set
Overfitting Training a model in such a way that it remembers specific features of the input data, rather than generalizing well to data not seen during training
CNN Convolutional neural network; a type of neural network that works particularly well for computer vision tasks

Fastai

Kuvamaks teavet mõne fastai funktsiooni kohta näiteks: learn.predict:

doc(learn.predict)

Lingid

The artistic shape detection algorithm

Today I learned one simple shape detection algorithm. In contrast image, it tries to find how many corners on some shapes are. When there is three, then it is a triangle and so on. And draws coloured contour around it. In a controlled environment, it works mostly as supposed. When you released it to the real wild world it gives quite artistic results.

#!/usr/bin/env python3

'''
Shape detection from images.
Tauno Erik
13.05.2021
'''

import cv2 as cv
import os

# Colors (BGR)
RED = (0,0,255)
GREEN = (0,255,0)
BLUE = (255,0,0)
YELLOW = (0,255,255)
CYAN = (255,255,0)
MAGENTA = (255,0,255)
ORANGE = (0,140,255)
PINK = (147,20,255)
PURPLE = (128,0,128)
GOLDEN = (32,165,218)
BROWN = (42,42,165)

def full_path(filename):
  ''' Returns full path to file. '''
  folder = os.path.dirname(__file__) # File location
  full_path = os.path.join(folder, filename)
  return full_path

def shape_detection(file):
  img = cv.imread(file)
  gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
  ret, thresh = cv.threshold(gray, 50, 255, 1)
  contours, h = cv.findContours(thresh, 1, 2)

  img_hall = cv.cvtColor(gray, cv.COLOR_GRAY2BGR)

  for cnt in contours:
    approx = cv.approxPolyDP(cnt, 0.01*cv.arcLength(cnt, True), True)  # Returns array
    print("Shape with {} pints".format(len(approx)))
    n = len(approx)

    if n is 15 or n is 16:
      cv.drawContours(img_hall, [cnt], 0, YELLOW, 5)
    elif n is 12:
      cv.drawContours(img_hall, [cnt], 0, BROWN, 5)
    elif n is 11:
      cv.drawContours(img_hall, [cnt], 0, GOLDEN, 5)
    elif n is 10:
      cv.drawContours(img_hall, [cnt], 0, PURPLE, 5)
    elif n is 9:
      cv.drawContours(img_hall, [cnt], 0, PINK, 5)
    elif n is 8:
      cv.drawContours(img_hall, [cnt], 0, CYAN, 5)
    elif n is 7:
      cv.drawContours(img_hall, [cnt], 0, ORANGE, 5)
    elif n is 6:
      cv.drawContours(img_hall, [cnt], 0, CYAN, 5)
    elif n is 5:
      cv.drawContours(img_hall, [cnt], 0, RED, 5)
    elif n is 4:
      cv.drawContours(img_hall, [cnt], 0, GREEN, 5)
    elif n is 3:
      cv.drawContours(img_hall, [cnt], 0, BLUE, 5)

  cv.imshow('Shaps', img_hall)
  cv.waitKey(0)


if __name__ == "__main__":
  print('Shape detection!')
  print('To close window press: q')

  file = full_path('images/kujundid.jpg')
  shape_detection(file)

Motion detection on the webcam

It is surprisingly easy to make a small Python script that takes a webcam or any other video and detects when something is moving there. It uses the OpenCV library.

1. Difference between frames

Compares two frames and displays only what are change. The rest is black.

import cv2

# Select camera. Usualy 0, or 1 and so on
cam = cv2.VideoCapture(0)

try:
	while cam.isOpened():
		ret, frame1 = cam.read()
		ret, frame2 = cam.read()
		diff = cv2.absdiff(frame1, frame2)
    
		# To exit press 'q'    
		if cv2.waitKey(10) == ord('q'):
			break
    	
		# Display
		cv2.imshow('Erinevus', diff)
except:
	print("Error.")

2. Binary image

Turn it into binary: only black and white. To make it easy to find contours.

import cv2

# Select camera. Usualy 0, or 1 and so on
cam = cv2.VideoCapture(0)

try:
	while cam.isOpened():
		ret, frame1 = cam.read()
		ret, frame2 = cam.read()
		# Compare frames
		diff = cv2.absdiff(frame1, frame2)
		# Convert diff to grayscale image
		gray = cv2.cvtColor(diff, cv2.COLOR_RGB2GRAY)
		# Blur gray image
		blur = cv2.GaussianBlur(gray, (5, 5), 0)
		# Converts to Binary images. Only black and white colour.
		_, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
		# Expand moving image part
		dilated = cv2.dilate(thresh, None, iterations=3)
    
		# To exit press 'q'    
		if cv2.waitKey(10) == ord('q'):
			break
    	
		# Display
		cv2.imshow('Erinevus', dilated)
except:
	print("Error.")

3. Contours

Now displays founded contours over the original image.

import cv2

# Select camera. Usualy 0, or 1 and so on
cam = cv2.VideoCapture(0)

try:
	while cam.isOpened():
		ret, frame1 = cam.read()
		ret, frame2 = cam.read()
		# Compare frames
		diff = cv2.absdiff(frame1, frame2)
		# Convert diff to grayscale image
		gray = cv2.cvtColor(diff, cv2.COLOR_RGB2GRAY)
		# Blur gray image
		blur = cv2.GaussianBlur(gray, (5, 5), 0)
		# Converts to Binary images. Only black and white colour.
		_, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
		# Expand moving image part
		dilated = cv2.dilate(thresh, None, iterations=3)
		# Find moving part contures
		contours, _ = cv2.findContours(dilated, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
		# Draw contours
		cv2.drawContours(frame1, contours, -1, (0, 255, 0), 2) #
    
		# To exit press 'q'    
		if cv2.waitKey(10) == ord('q'):
			break
    	
		# Display
		cv2.imshow('Erinevus', frame1)
except:
	print("Error.")

Rectangle

When we know where the contours are. Where are the coordinates of the beginning on the x and y axes. We can draw rectangles around these regions.

import cv2

# Select camera. Usualy 0, or 1 and so on
cam = cv2.VideoCapture(0)

try:
	while cam.isOpened():
		ret, frame1 = cam.read()
		ret, frame2 = cam.read()
		# Compare frames
		diff = cv2.absdiff(frame1, frame2)
		# Convert diff to grayscale image
		gray = cv2.cvtColor(diff, cv2.COLOR_RGB2GRAY)
		# Blur gray image
		blur = cv2.GaussianBlur(gray, (5, 5), 0)
		# Converts to Binary images. Only black and white colour.
		_, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
		# Expand moving image part
		dilated = cv2.dilate(thresh, None, iterations=3)
		# Find moving part contures
		contours, _ = cv2.findContours(dilated, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

		for c in contours:
			# Select movement size area.
			# If contour is smaller it will be ignored.
			if cv2.contourArea(c) < 2000:
				continue
			# Contour position and size
			x, y, w, h = cv2.boundingRect(c)
			# Draw rectangle
			cv2.rectangle(frame1, (x, y), (x+w, y+h), (0, 255, 0), 2)
			# To something
    
		# To exit press 'q'    
		if cv2.waitKey(10) == ord('q'):
			break
    	
		# Display
		cv2.imshow('Liikumine', frame1)
except:
	print("Error.")

Demo video

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

Lämmämõõdusk

Description

A device that measures the ambient temperature in the environment in which it is located. And displays it on the screen.

Artistic description

Plant-like structures. Display like blossom. Bottom woodblock aged with brushing with a wire brush and oxidation solution (steel wool + vingar).

Animations

When the number to display changes it plays short animation.

Technical description

The first goal was to test an easier way to connect 7-segs and shift registers.
Main CPU, Arduino pro mini, uses thermistor to calculate temperature. Uses two different algorithms for this: Steinhart-hart equation and Beta model equation.
The display consists of two seven-segment displays. Drived by two 74HC595 shift registers. Which is connected to the seven-segment displays in a non-traditional way.This makes the construction easier.

Parts list

noDescriptionpcs
1Arduino pro mini Mega328p 5V1
2Shift Register SN74HC595N2
37-segment 5611BH / 5161BS2
4Resistor 12016
5CH340E USB to TTL BTE17-061
6Thermistor 10k1
7Resistor 10K1

Readin data

The simplest way to read serial data is to use Miniterm:

Also, I have made two Python scripts. read.py and simple GUI app gui.py. What display average temperature.

Links

Serial Plotter code

This is my standalone serial plotter. I like to use Arduino, Esp866 etc. and then i started to learn python and decided to make something useful. Something that i will use and what is missing now for me. Although Arduino Ide contains serial plotter. It is little bit different.

Written in python 3 and Qt5. It is basically my first GUI application ever.

Incoming data should be string. Ending with new line character. Number can be separated with almost any character.

Like:

label2la15be17el28/31/42/54 78

or

a2b1.5c1.7d2.8/3.1/4.2/5.4 7.8

But not with unless it is a negative number:

5-10-22-33-40-55-62-75

Script will extracts all numbers and generate graph.

Tested on Ubuntu 19.10 and Windows 10.

Sourcecode and instructions are on Github: https://github.com/taunoe/tauno-serial-plotter