CV2 Trovare Modelli sulle Immagini

CV2 Trovare Modelli nelle Immagini

In questo articolo ho utilizzato la visione artificiale e le reti neurali per trovare una parola in un testo scritto in corsivo più di cento anni fa.

In questo breve esempio, ricorro al pacchetto CV2, focalizzato sulla visione artificiale, per elaborare un’immagine con testo in corsivo ed estrarre una parola specifica, seguendo un modello allenato con Tensorflow / Keras.

import cv2import numpy as npimport pandas as pdfrom google.colab.patches import cv2_imshowfrom statistics import meanimport tensorflow as tf

L’immagine viene letta e trasformata da colore a scala di due colori.

image = cv2.imread('test.jpg')#converti in scala di grigioimage = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)cv2_imshow(image)

L’immagine viene interpretata nel backend come un tensore, formato da tre punti per ogni pixel. Questi tre punti rappresentano la saturazione del colore.

image

Ora utilizzo la funzione inRange per impostare ciascun pixel in una forma di classificazione binaria, bianco o nero. Successivamente, devo invertire i valori poiché lo standard nell’apprendimento automatico è bianco su nero.

lower = np.array([0, 0, 120])upper = np.array([0, 0, 255])msk = cv2.inRange(image, lower, upper)msk = cv2.bitwise_not(msk)cv2_imshow(msk)

Le due funzioni successive sono erosione e dilatazione. La prima viene effettuata per rimuovere i punti bianchi che potrebbero essere rumore (immagina il arosiona una roccia). La seconda serve ad ingrandire le aree bianche per creare uno schema sfocato; con questo si suppone che un blocco continuo corrisponda a una parola, o almeno sia vicino ad essa.

In [5]:

#è necessario definire un kernel, in questo caso è la forma della figura che vogliamo estrarre (rettangolo, ellisse, cerchio, ecc.). In questo caso rettangolokernel = cv2.getStructuringElement(cv2.MORPH_RECT,(2,2))#poi erodo il bianco per rimuovere i punti di rumore, o almeno ridurli rrmask = cv2.erode(msk,kernel,iterations = 1)kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(10,5))rrmask = cv2.dilate(msk, kernel, iterations=1)cv2_imshow(rrmask)

Questa sezione serve solo come illustrazione per identificare le parole nell’immagine iniziale. Ho anche creato un oggetto parola che memorizza i rettangoli delle immagini di ogni parola trovata in questo modo.

contours, hierarchy = cv2.findContours(rrmask, mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE)minw = 50minh = 10image = cv2.imread('test.jpg')image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)cleancontours = []words = []for contour in contours: x,y,w,h = cv2.boundingRect(contour) if ((w>=minw) & (h>=minh)):  cleancontours.append(contour)  word = image[y-5:y+h+5,x-5:x+w+5]  words.append(word)cleancontours = tuple(cleancontours)imageC = cv2.imread('test.jpg')imageC = cv2.cvtColor(imageC, cv2.COLOR_BGR2HSV)cv2.drawContours(imageC, cleancontours, -1, (0,255,0), 3)cv2_imshow(imageC)

Prendo queste parole e le gestisco allo stesso modo in cui ho fatto nei passaggi precedenti.

image = cv2.imread('test.jpg')image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)lower = np.array([0, 0, 150])upper = np.array([0, 0, 255])masks = []for word in words: if len(word) > 0:  mask = cv2.inRange(word, lower, upper)  mask = cv2.bitwise_not(mask)  masks.append(mask)

Qui, mi occuperò degli esempi di addestramento positivi. Cercherò la parola “Garcia” nell’immagine. Ho raccolto campioni di altre immagini per la stessa parola, scritte dalla stessa persona. Ho pulito queste immagini nello stesso modo descritto in precedenza, poi le ho trasmformate alle stesse dimensioni (la media di tutti i campioni raccolti). La terza immagine visualizzata qui è una media di tutte e 16 le immagini.

lower = np.array([0, 0, 150])upper = np.array([0, 0, 255])samples = ['sample1.jpg','sample2.jpg','sample3.jpg','sample4.jpg','sample5.jpg','sample6.jpg','sample7.jpg','sample8.jpg','sample9.jpg','sample10.jpg','sample11.jpg','sample12.jpg','sample13.jpg','sample14.jpg','sample15.jpg','sample16.jpg']train = []hs = []ws = []for sample in samples: im = cv2.imread(sample) im = cv2.cvtColor(im, cv2.COLOR_BGR2HSV) im = cv2.inRange(im, lower, upper) im = cv2.bitwise_not(im) height, width = im.shape hs.append(height) ws.append(width) train.append(im)from statistics import meanhh = int(mean(hs))+1ww = int(mean(ws))+1trainr = []for im in train: im = cv2.resize(im, (ww, hh), interpolation = cv2.INTER_CUBIC) trainr.append(im)cv2_imshow(trainr[2])cv2_imshow(trainr[1])meanimg = np.mean(trainr, axis=0)cv2_imshow(meanimg)

Prendo l’immagine media e la trasformo in valori binari.

lower = 80upper = 255meanimgT = cv2.inRange(meanimg, lower, upper)cv2_imshow(meanimgT)

In questa sezione, prendo la media ed estraggo una distanza euclidea da ciascuna delle parole nel testo. Vediamo se questo semplice metodo dà buoni risultati.

minh = min(hs)-20minw = min(ws)-20maxh = max(hs)+20maxw = max(ws)+20testr = []for im in masks: height, width = im.shape if ((height >= minh) & (width >= minw) & (height <= maxh) & (width <= maxw)):  im = cv2.resize(im, (ww, hh), interpolation = cv2.INTER_CUBIC)  testr.append(im)a,b=meanimg.shapeleng = a*bdistc = []i = 0for im in testr: distance = np.sqrt(np.sum(np.square(meanimg - im)))/leng dist = pd.DataFrame({'position':[i], 'distance':[distance]}) i = i + 1 distc.append(dist)distc = pd.concat(distc, axis=0, ignore_index=True)distc.sort_values('distance')

E… la parola più simile è effettivamente Garcia.

cv2_imshow(testr[23])

In questa prossima sezione, prendo sette immagini di testo e le processo allo stesso modo dell’immagine di prova. L’idea è estrarre un numero di immagini fasulle di parole che posso utilizzare come set di addestramento negativo.

lower = np.array([0, 0, 120])upper = np.array([0, 0, 255])samples2 = ['dummy1.jpg','dummy2.jpg','dummy3.jpg','dummy4.jpg','dummy5.jpg','dummy6.jpg','dummy7.jpg']minw = 50minh = 10train2r = []for sample in samples2: im = cv2.imread(sample) im = cv2.cvtColor(im, cv2.COLOR_BGR2HSV) im = cv2.inRange(im, lower, upper) im = cv2.bitwise_not(im) kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(2,2)) im = cv2.erode(im,kernel,iterations = 1) kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(10,5)) im = cv2.dilate(im, kernel, iterations=1) image = cv2.imread(sample) image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) for contour in contours:  x,y,w,h = cv2.boundingRect(contour)  if ((w>=minw) & (h>=minh)):   word = image[y-5:y+h+5,x-5:x+w+5]   if (len(word) > 0):    try:     mask = cv2.inRange(word, lower, upper)     mask = cv2.bitwise_not(mask)     height, width = mask.shape     if ((height >= minh) & (width >= minw) & (height <= maxh) & (width <= maxw)):      mask = cv2.resize(mask, (ww, hh), interpolation = cv2.INTER_CUBIC)      train2r.append(mask)    except Exception as error:     print(error)

Questo è il nucleo del modello di rete neurale in Keras. In questo caso, vengono utilizzati strati che supportano input bidimensionali e output binari unidimensionali. È stato necessario un buon numero di tentativi poiché il set di addestramento è molto piccolo e il modello è saltato rapidamente in una regione di sovradattamento. Ho affrontato la situazione con una dimensione di batch bassa, che ha permesso una “flessibilità” nella selezione dei dati per ogni epoca, e facendolo girare più volte su campioni diversi.

from random import sampletrain2r_s = sample(train2r,30)training = trainr + train2r_sys = ([1] * len(trainr)) + ([0] * len(train2r_s))training = np.array(training)ys = np.array(ys)#servono forma dell'inputnn, xx, yy = np.array(training).shape#inizializzaneur = tf.keras.models.Sequential()#stratineur.add(tf.keras.layers.Conv2D(5,3, activation='relu', input_shape=(xx,yy,1)))neur.add(tf.keras.layers.Conv2D(15,3, activation='tanh'))neur.add(tf.keras.layers.Conv2D(15,3, activation='tanh'))neur.add(tf.keras.layers.Flatten())neur.add(tf.keras.layers.Dense(10, activation='tanh'))#strato di outputneur.add(tf.keras.layers.Dense(units=1, activation='sigmoid'))neur.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])neur.fit(np.array(training), np.array(ys), batch_size=15, epochs=3)

Per ridurre il sovradattamento, ho creato un ciclo per allenare il modello in diversi set di addestramento casuali ogni volta. Sono riuscito a farlo perché avevo un grande campione di negativi. Dovevo anche tenere conto dell’alto sbilanciamento tra positivi e negativi.

for i in list(range(0,30)): train2r_s = sample(train2r,30) training = trainr + train2r_s ys = ([1] * len(trainr)) + ([0] * len(train2r_s)) training = np.array(training) ys = np.array(ys) print("parte di addestramento: " + str(i+1)) neur.fit(np.array(training), np.array(ys), batch_size=30, epochs=3)

Corri sul test, dati non visti.

test_out = neur.predict(np.array(testr))pd.DataFrame(test_out).sort_values(0, ascending=False)

Estrai l’immagine di test superiore (valore più vicino a 1), che è effettivamente la parola Garcia.

check = 23print(test_out[check])cv2_imshow(testr[check])