Segmentação semântica Segmentação de elipses e retângulos (usa OpenCV e Keras): [/home/hae/goodreader/algoritmos/deep/Stanford_cs231/cs231n_2017_lecture11_outros_problemas] Alguns anos atrás, fazia downsampling seguido de upsampling. Problema: A localização de fronteira entre regiões não é precisa. Unet resolve este problema. 1
39
Embed
Segmentação semântica Segmentação de elipses e retângulos ... · cv2.imwrite(argv[3],QP) else: qp=255.0*((qp/2.0)+0.5) # Entre 0 e 255 qp=np.clip(qp,0,255) QP=np.uint8(qp)...
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Segmentação semânticaSegmentação de elipses e retângulos (usa OpenCV e Keras):[/home/hae/goodreader/algoritmos/deep/Stanford_cs231/cs231n_2017_lecture11_outros_problemas]
Alguns anos atrás, fazia downsampling seguido de upsampling.
Problema: A localização de fronteira entre regiões não é precisa.
Unet resolve este problema.
1
ruidosa ideal saída CNN saída CNN bin limiarização
Para fazer segmentação semântica, deve fazer upsampling. Há duas formas de fazer upsampling:
1) Unpooling: contrário de maxpooling (veja transparências).Dá a impressão de que não está implementado em Keras. Só está implementado UpSampling2Dque faz interpolação vizinho mais próximo.
2) Transpose convolution: contrário de convolução (veja transparências).
3
Estrutura da rede (ignorando dropout):
4
#~/haepi/deep/keras/segm_eliret/segm02.py#Rede para segmentacao semanticafrom __future__ import print_functionimport cv2import numpy as npnp.random.seed(7)import tensorflow.keras as kerasfrom keras.models import Sequentialfrom keras.layers import Dropout, Conv2D, Conv2DTransposefrom keras import optimizers
def leCsv(nomeArq): n=0 arq=open(nomeArq,"r") for linha in arq: n=n+1
nl,nc = 32,32 AX=np.empty((n,nl,nc),dtype='uint8') AY=np.empty((n,nl,nc),dtype='uint8') i=0 arq.seek(0) for linha in arq: linha=linha.strip('\n') linha=linha.split(';') AX[i]=cv2.imread(linha[0],0) AY[i]=cv2.imread(linha[1],0) i=i+1
arq.close()
ax=2*(np.float32(AX)/255.0-0.5) #Entre -1 e +1 ay=2*(np.float32(AY)/255.0-0.5) #Entre -1 e +1 ax = ax.reshape(n, nl, nc, 1) ay = ay.reshape(n, nl, nc, 1) return ax, ay
ax, ay = leCsv("treino.csv")vx, vy = leCsv("valida.csv")qx, qy = leCsv("teste.csv")
Se eliminar dropout, o erro fica bem maior:Training loss: 0.0861751502752Validation loss: 0.132946297526Test loss: 0.13650231123
6
#~/deep/keras/segm_eliret/pred02.py#Faz segmentacao semantica usando rede gerada pelo segm??.py (retangulo elipse)from __future__ import print_functionimport cv2import numpy as npnp.random.seed(7)import tensorflow.keras as kerasfrom keras.models import load_modelfrom keras.layers import Dropout, Conv2D, Conv2DTransposefrom keras import optimizersimport sysfrom sys import argv
if (len(argv)!=5): print("pred.py rede.h5 ent.png sai.png b/g") print(" b=binarizado g=grayscale") print("Erro: Numero de argumentos invalido") sys.exit() model = load_model(argv[1])
nl,nc = 32,32QX=cv2.imread(argv[2],0)qx=2*(np.float32(QX)/255.0-0.5) #Entre -1 e +1qx=qx.reshape(1, nl, nc, 1)
qp=model.predict(qx)qp=qp.reshape(nl,nc) # entre -1 e +1
if (argv[4]=="b"): QP=np.empty((nl,nc),dtype='uint8') for l in range(qp.shape[0]): for c in range(qp.shape[1]): if qp[l,c]<0: QP[l,c]=0 else: QP[l,c]=255 cv2.imwrite(argv[3],QP)else: qp=255.0*((qp/2.0)+0.5) # Entre 0 e 255 qp=np.clip(qp,0,255) QP=np.uint8(qp) cv2.imwrite(argv[3],QP)
Training loss: 1.1949643849220592e-05Validation loss: 0.014191047586500645Test loss: 0.014867232739925384
O erro diminuiu umas 4 vezes.
9
#~/deep/keras/segm_eliret/unetpred1.py#Faz segmentacao semantica usando rede gerada pelo segm??.py (retangulo elipse)import cv2import numpy as npnp.random.seed(7)import kerasfrom keras.models import load_modelfrom keras.layers import Dropout, Conv2D, Conv2DTransposefrom keras import optimizersimport sysfrom sys import argv
import osos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
if (len(argv)!=5): print("unetpred.py rede.h5 ent.png sai.png b/g") print(" b=binarizado g=grayscale") print("Erro: Numero de argumentos invalido") sys.exit()
model = load_model(argv[1])
nl,nc = 32,32QX=cv2.imread(argv[2],0)qx=np.float32(QX)/255.0 #Entre 0 e +1qx=qx.reshape(1, nl, nc, 1)
qp=model.predict(qx)qp=qp.reshape(nl,nc) # entre 0 e +1
if (argv[4]=="b"): QP=np.empty((nl,nc),dtype='uint8') for l in range(qp.shape[0]): for c in range(qp.shape[1]): if qp[l,c]<0.5: QP[l,c]=0 else: QP[l,c]=255 cv2.imwrite(argv[3],QP)else: qp=255.0*qp # Entre 0 e 255 qp=np.clip(qp,0,255) QP=np.uint8(qp) cv2.imwrite(argv[3],QP)
10
U-net
RONNEBERGER, Olaf; FISCHER, Philipp; BROX, Thomas. U-net: Convolutional networks forbiomedical image segmentation. In: International Conference on Medical image computing andcomputer-assisted intervention. Springer, Cham, 2015. p. 234-241.
0.png 0.png
10.png 10.png
Quer detectar as paredes das células. No site:
https://github.com/zhixuhao/unet
Há 30 imagens exemplos (entrada-saída), 512x512.
11
No site:https://github.com/zhixuhao/unet
Há uma solução para o problema. Só que, no meu computador, essa solução vaza memória(memory leak). O programa ocupa toda a memória, começa usar memória virtual, e trava ocomputador. (Talvez por que rodei no Python 2?)
Como só há 30 exemplos para treino, primeiro deve aumentar artificialmente exemplos detreinamento. Para cada imagem, gero 10 imagens distorcidas. Para acelerar processamento, usoimagens 128x128.
O programa: /home/hae/haepi/deep/keras/unet/hae/gera1.pypega imagens de: /home/hae/haepi/deep/keras/unet/hae/membrane/train/imagereduz para 128x128 e distorce-os (data augmentation) e armazena as imagens distorcidas em im-age_aug.Também pega labels de: /home/hae/haepi/deep/keras/unet/hae/membrane/train/labele distorcê-los da mesma forma e armazenar em label_aug
12
Algumas versões reduzidas e distorcidas de 0.png:
#gera1.py#Reduz imagem de 512x512 para 128x128#Gera 10 imagens distorcidas para cada imagem de entrada#from __future__ import print_functionimport tensorflow.keras as keras;from keras.preprocessing.image import ImageDataGenerator;import numpy as np;import cv2;
#<<<<<<<<<<<<<<< main <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<aug_dict = dict(rotation_range=10, #Int. Degree range for random rotations. width_shift_range=-0.05, #float: fraction of total width, if < 1, or pixels if >= 1. height_shift_range=-0.05, #float: fraction of total height, if < 1, or pixels if >= 1. shear_range=10, #Float. Shear Intensity (Shear angle in counter-clockwise direction in degrees) zoom_range=0.2, #Range for random zoom. If a float, [lower, upper] = [1-zoom_range, 1+zoom_range]. horizontal_flip=False, #Boolean. Randomly flip inputs horizontally. fill_mode='reflect'); #One of {"constant", "nearest", "reflect" or "wrap"}.train_path='membrane/train';image_folder='image';mask_folder= 'label';target_size = (128,128);batch_size=30;seed = 7;save_to_dir = None;
for i in range(10): img=image_generator.next(); #gera uma distorcao de 30 imagens mask=mask_generator.next(); #gera uma distorcao de 30 masks (labels)
14
Parece que é possível gerar amostras distorcidas à medida em que precisa (real-time dataaugmentation). Mas para mim não deu certo. Gerei as amostras distorcidas e gravei como imagens.Depois, rodei o treino:
#treina1.py#from __future__ import print_function;import cv2;import numpy as np; np.random.seed(7);import os;import sys;import tensorflow.keras as keras;
from keras.models import *from keras.layers import *from keras.optimizers import *from keras.callbacks import ModelCheckpoint, LearningRateSchedulerfrom keras import backend as keras
def leDoisDirs(imagePath,maskPath): #Le imagens em dois diretorios com nomes iguais e retorna como float32 entre 0 e +1 imageList = [f for f in os.listdir(imagePath) if os.path.isfile(os.path.join(imagePath, f))]; imageList.sort(); maskList = [f for f in os.listdir(maskPath) if os.path.isfile(os.path.join(maskPath, f))]; maskList.sort(); if (len(imageList)!=len(maskList)): print("Erro: Numero de arquivos diferentes"); sys.exit(0); n=len(imageList);
for i in range(n): if (imageList[i]!=maskList[i]): print("Erro: Nome image diferente de nome mask"); sys.exit(0); AX[i]=cv2.imread(os.path.join(imagePath, imageList[i]),0); AY[i]=cv2.imread(os.path.join(maskPath, maskList[i]),0);
ax=np.float32(AX)/255.0; #Entre 0 e +1 ay=np.float32(AY)/255.0; #Entre 0 e +1 ay[ay>=0.5] = 1; ay[ay<0.5] = 0; #0 ou +1
import tensorflow.keras as kerasfrom keras.models import *from keras.layers import *from keras.optimizers import *from keras import backend as keras
def leUmDir(imagePath): #Le imagens em um diretorio e retorna como float32 entre 0 e +1 #Tambem retorna os nomes das imagens imageList = [f for f in os.listdir(imagePath) if os.path.isfile(os.path.join(imagePath, f))]; imageList.sort(); n=len(imageList);
preds = model.predict(x)p=decode_predictions(preds, top=3)[0]# decode the results into a list of tuples (class, description, probability)# (one such list for each sample in the batch)#print('Predicted:', p)# Predicted: [(u'n02504013', u'Indian_elephant', 0.82658225), (u'n01871265', u'tusker', 0.1122357), (u'n02504458', u'African_elephant', 0.061040461)]
for i in range(len(p)): print("%8.2f%% %s"%(100*p[i][2],p[i][1]))
(PSI3472-2019 Aula 7 exercício 3) Modifique o programa classif1.py para usar o modelo Xception.Teste o seu programa para algumas imagens (pode ser as de cima ou o que pegar na internet).
21
Classificação e localização de objeto:
Gato
Digamos que temos rede neural que reconhece cão, gato e pássaro: Rede tem 3 saídas - cão, gato epássaro.
1) Treinar rede com mais 4 saídas: xcentro, ycentro, largura, altura.
2) Janela móvel. Tem que treinar com 4 categorias: cão, gato, pássaro e "não cão, gato ou pássaro".Lento.
Tem jeito melhor?
3) R-CNN.Region proposal: Propõe regiões onde podem ter objetos.Classifier: Verifica se nessa região tem algum objeto.
22
Rede completamente convolucionalComo modificar rede neural convolucional para localizar um objeto sem rodar janela móvel?
1) Rodar rede neural convolucional em janela móvel. Problema: É lento. 2) Dá para fazer a mesma coisa que rodar rede neural em janelas, eliminando as camadas densas(ficam só camadas convolucionais).
LeNet original que aceita imagem 28x28:1x28x28 → conv5x5 → 20x24x24 → maxpool → 20x12x12 → conv5x5 → 40x8x8 → maxpool→ 40x4x4 → flatten → dense → 500 → dense → 100 → dense → 10A saída possui 10 elementos, referentes a 10 categorias. Não se pode entrar imagens maiores que28x28 nesta rede.
Trocando camadas densas (fc) por convolucionais:1x28x28 → conv5x5 → 20x24x24 → maxpool → 20x12x12 → conv5x5 → 40x8x8 → maxpool→ 40x4x4 → conv4x4 → 500x1x1 → conv1x1 → 100x1x1 → conv1x1 → 10x1x1 Esta camada faz exatamente a mesma coisa que LeNet original. A saída possui 10 imagens 1x1referentes a 10 categorias. (qx0.png)
[[7]]
O que acontece se entrar uma imagem maior que 28x28? Por exemplo, 32x32? 1x32x32 → conv5x5 → 20x28x28 → maxpool → 20x14x14 → conv5x5 → 40x10x10 →maxpool → 40x5x5 → conv4x4 → 500x2x2 → conv1x1 → 100x2x2 → conv1x1 → 10x2x2 A saída será um tensor 10x4x4. Isto equivale a aplicar LeNet original 4 vezes, fazendo stride de 4.
(qx0b.png)
[[ 7 7] [ 7 -1]]
23
O que acontece se entrar uma imagem 80x80? 1x80x80 → conv5x5 → 20x76x76 → maxpool → 20x38x38 → conv5x5 → 40x32x32 →maxpool → 40x16x16 → conv4x4 → 500x12x12 → conv1x1 → 100x12x12 → conv1x1 →10x12x12
qx0 = qx0.astype("float32");qx0 /= 255 #0 a 1qx0=qx0.reshape(1,nl,nc,1);qp = model.predict(qx0);print(qp.shape)
snl, snc = qp.shape[1], qp.shape[2];qp = qp.reshape(snl,snc,10);sai=np.empty([snl,snc],dtype=np.int8);for l in range(snl): for c in range(snc): i=np.argmax(qp[l,c,:]) if qp[l,c,i]>0.999: #print(l,c,i,qp[l,c,i]); sai[l,c]=i; else: sai[l,c]=-1;print(sai);#print(qp);
(PSI3472-2019 Aula 7 exercício 4) Modifique o programa caogato1.py (aula5, exercício 3, daapostila cifar-reduzido) para obter programa que aceita imagens coloridas maiores que 32x32. Testeesse programa na imagem caogato.png que possui dimensão 80x80.
caogato.png
26
Cekpy:
Vamos deixar algumas funções que são usadas repetidamente em cekpy.py. Deixe o arquivo abaixono mesmo diretório do seu programa .py ou num diretório listado pelo variável ambientePYTHONPATH.
#cekpy.pyimport cv2;import sys;import os;import numpy as np;from matplotlib import pyplot as plt;
def erro(st): print(st); sys.exit(0);
def mostra(a): #print(a.dtype); #print(a.shape); if a.dtype=="uint8" and len(a.shape)==3 and a.shape[2]==3: #COR t=cv2.cvtColor(a,cv2.COLOR_BGR2RGB); plt.imshow(t,interpolation="bicubic") plt.show() elif a.dtype=="uint8" and len(a.shape)==2: #GRY plt.imshow(a,cmap="gray",interpolation="bicubic"); plt.show() elif (a.dtype=="float32" or a.dtype=="float64") and len(a.shape)==3 and a.shape[2]==3: #CORF t=cv2.cvtColor(a,cv2.COLOR_BGR2RGB); plt.imshow(t,interpolation="bicubic") plt.show() elif (a.dtype=="float32" or a.dtype=="float64") and len(a.shape)==2: #FLT plt.imshow(a,cmap="gray",interpolation="bicubic"); plt.show() else: print("Tipo de imagem desconhecida"); sys.exit(0);
def leCsv(nomeDir, nomeArq, nl=0, nc=0): #nomeDir = Diretorio onde estao treino.csv, teste.csv e imagens nnna.jpg e nnnb.jpg. #Ex: nomeDir = "/home/hae/haebase/fei/feiFrontCor" st=os.path.join(nomeDir,nomeArq) arq=open(st,"rt") lines=arq.readlines(); arq.close(); n=len(lines)
linhas_separadas=[] for linha in lines: linha=linha.strip('\n'); linha=linha.split(';'); linhas_separadas.append(linha);
t=cv2.imread(os.path.join(nomeDir,linhas_separadas[0][0]),1); onl=t.shape[0]; onc=t.shape[1]; if nl==0 or nc==0: nl=onl; nc=onc;
for i in range(len(linhas_separadas)): linha=linhas_separadas[i]; t=cv2.imread(os.path.join(nomeDir,linha[0]),1); if nl>0 and nc>0: t=cv2.resize(t,(nc,nl),interpolation=cv2.INTER_AREA); ax[i]=np.float32(t)/255.0; #Entre 0 e 1 ay[i]=np.float32(linha[1]); #0=m ou 1=f
return ax, ay;
27
Transfer learning
O site abaixo contém vários bancos de dados de faces humanas:http://fei.edu.br/~cet/facedatabase.html
Desse site, peguei o banco de imagens com faces frontais de 200 pessoas (400 imagens coloridas com360x260 pixels) de rostos frontais com expressão neutra (*a.jpg) e sorridente (*b.jpg), alinhadas manual-mente. Dessas imagens, metade são rostos masculinos e metade são femininos. Recortei as bordas dessasimagens, para que fiquem com 280x200 pixels. As imagens assim obtidas estão em:
http://www.lps.usp.br/hae/apostila/feiCorCrop.zipNesse ZIP, também há 3 arquivos csv (comma separated values): treino.csv, valida.csv e teste.csv, com listade arquivos nome de arquivo com classificação (0=masculino e 1=feminino). Algumas imagens:
095a.jpg 095b.jpg 096a.jpg 096b.jpg
O programa abaixo utiliza rede tipo "LeNet" (sem data augmentation) para classificar imagens em masculinoe feminino. A taxa de acerto teste é 92%. A taxa de acerto de treino de 100% indica que há overfitting e astaxas de acerto de validação/teste aumentariam se fizesse data augmentation.
(PSI3472-2019 Aula 8 exercício 1) Acrescente data augmentation no programa acima para ver ataxa de acerto que se consegue atingir.
29
Vamos fazer transfer learning, usando VGG16:As taxas de acerto de validação/teste foram para 93/98% (treinando só a camada densa superior) e para93%/100% treinando todas as camadas com learning rate pequeno.
Após treinar todas as camadas:Epoch 50/50 - 4s 22ms/step - loss: 1.1634e-05 - acc: 1.0000 - val_loss: 0.1056 - val_acc: 0.9500Training loss: [1.1298586396151222e-05, 1.0]Validation loss: [0.1056408049726042, 0.95]Test loss: [0.1299311416657656, 0.98]
#vgg2b.py#Faz transfer learning usando VGG16 para melhorar acuracidade.#No fim, faz aprendizagem descongelando todas camadas#Usando exemplo#https://medium.com/abraia/first-steps-with-transfer-learning-for-custom-image-classification-with-keras-b941601fcad5
#main#Desliga avisos de Tensorflowimport os; os.environ['TF_CPP_MIN_LOG_LEVEL']='3'#Imprime nome do GPUimport tensorflow as tf; tf.test.gpu_device_name()#Pega nome do programa em execucaofrom inspect import currentframe, getframeinfofi = getframeinfo(currentframe()); nomeprog=os.path.splitext(fi.filename)[0];
#main#Desliga avisos de Tensorflowimport os; os.environ['TF_CPP_MIN_LOG_LEVEL']='3'#Imprime nome do GPUimport tensorflow as tf; tf.test.gpu_device_name()#Pega nome do programa em execucaofrom inspect import currentframe, getframeinfofi = getframeinfo(currentframe()); nomeprog=os.path.splitext(fi.filename)[0];
from keras.utils import plot_modelplot_model(model, to_file=nomeprog+'.png', show_shapes=True)from keras.utils import print_summaryprint_summary(model)
for layer in base_model.layers: layer.trainable = False
print('Using real-time data augmentation.')datagen = ImageDataGenerator( # randomly shift images horizontally - Fraction of width width_shift_range=0.1, # randomly shift images vertically - Fraction of height height_shift_range=0.1, # set range for random shear fill_mode='nearest', # value used for fill_mode = "constant" horizontal_flip=True)
# Compute quantities required for featurewise normalization# (std, mean, and principal components if ZCA whitening is applied).otimizador=keras.optimizers.Adam(lr=1e-3)model.compile(otimizador, loss='categorical_crossentropy', metrics =['accuracy'])datagen.fit(ax)epochs =15;
(PSI3472-2019 Aula 8 exercício 2) Imprima (em arquivos) as imagens de pessoas que estão sendoclassificadas erradamente.
33
Nota: Não consegui fazer transfer learning funcionar com ResNet e Inception. A taxa de acerto fica muitobaixa. Não sei por quê. Talvez por que tem erro na camada batch normalization do Keras?
34
YOLO object detection (You Only Look Once)
Site original do YOLO (escrito em C):https://pjreddie.com/darknet/yolo/
Como converter para Keras:https://machinelearningmastery.com/how-to-perform-object-detection-with-yolov3-in-keras/https://github.com/experiencor/keras-yolo3
# load and prepare an imagedef load_image_pixels(filename, shape):
# load the image to get its shapeimage = load_img(filename)width, height = image.size# load the image with the required sizeimage = load_img(filename, target_size=shape)# convert to numpy arrayimage = img_to_array(image)# scale pixel values to [0, 1]image = image.astype('float32')image /= 255.0# add a dimension so that we have one sampleimage = expand_dims(image, 0)return image, width, height
# get all of the results above a thresholddef get_boxes(boxes, labels, thresh):
v_boxes, v_labels, v_scores = list(), list(), list()# enumerate all boxesfor box in boxes:
# enumerate all possible labelsfor i in range(len(labels)):
# check if the threshold for this label is high enoughif box.classes[i] > thresh:
v_boxes.append(box)v_labels.append(labels[i])v_scores.append(box.classes[i]*100)# don't break, many labels may trigger for one box
return v_boxes, v_labels, v_scores
# draw all resultsdef draw_boxes(filename, v_boxes, v_labels, v_scores):
# load the imagedata = pyplot.imread(filename)# plot the imagepyplot.imshow(data)# get the context for drawing boxesax = pyplot.gca()# plot each boxfor i in range(len(v_boxes)):
box = v_boxes[i]# get coordinatesy1, x1, y2, x2 = box.ymin, box.xmin, box.ymax, box.xmax# calculate width and height of the boxwidth, height = x2 - x1, y2 - y1# create the shaperect = Rectangle((x1, y1), width, height, fill=False, color='white')# draw the boxax.add_patch(rect)
36
# draw text and score in top left cornerlabel = "%s (%.3f)" % (v_labels[i], v_scores[i])pyplot.text(x1, y1, label, color='white')
# show the plotpyplot.show()
if len(sys.argv)!=2: print("detect nomeimg.ext"); sys.exit();# load yolov3 modelmodel = load_model('yolov3.h5')# define the expected input shape for the modelinput_w, input_h = 416, 416 #320 ou 416 ou 608# define our new photophoto_filename = sys.argv[1]# load and prepare imageimage, image_w, image_h = load_image_pixels(photo_filename, (input_w, input_h))# make predictionyhat = model.predict(image)# summarize the shape of the list of arrays#print([a.shape for a in yhat])# define the anchorsanchors = [[116,90, 156,198, 373,326], [30,61, 62,45, 59,119], [10,13, 16,30, 33,23]]# define the probability threshold for detected objectsclass_threshold = 0.6boxes = list()for i in range(len(yhat)):
# decode the output of the networkboxes += decode_netout(yhat[i][0], anchors[i], class_threshold, input_h, input_w)
# correct the sizes of the bounding boxes for the shape of the imagecorrect_yolo_boxes(boxes, image_h, image_w, input_h, input_w)# suppress non-maximal boxesdo_nms(boxes, 0.5)# define the labels - o mesmo que coco.nameslabels = ["person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck",
# get the details of the detected objectsv_boxes, v_labels, v_scores = get_boxes(boxes, labels, class_threshold)# summarize what we foundfor i in range(len(v_boxes)):
print(v_labels[i], v_scores[i])# draw what we founddraw_boxes(photo_filename, v_boxes, v_labels, v_scores)
Note que o programa acima detecta somente 80 categorias de objetos listados nos labels.
(PSI3472-2019 Aula 8 exercício 3) Execute o programa acima em algumas imagens (podem ser osdo site ou da internet).
Façam:Exercício 3 - testar Yolo.Ou exercício 1 ou exercício 2
37
Exemplos de detecções:
38
Explicação simplificada de YOLO (há detalhes que não estou explicando):
1) Digamos que queremos detectar 3 classes de objetos: 1 = pedestre
2 = carro3 = motocicleta
A imagem é dividida em grides. No exemplo abaixo, em 3x3 grides.
2) Para cada gride, faz uma predição que retorna 8 números. Isto pode ser feita fazendo uma única predição.y = [ pc, bx, by, bh, bw, c1, c2, c3 ]
pc=0 se não há objeto. pc=1 se há objeto.bx, by, bh e bw são posições x, y, height e width do bounding box do objeto. O bounding box pode sair forado gride.c1, c2 e c3 se referem a 3 classes de objetos.
Os grides onde não há carro:y = [ 0, ?, ?, ?, ?, ?, ?, ?]
Os dois grides onde há carro:y = [ 1, bx, by, bh, bw, 0, 1, 0]
Faz-se supressão de não-máximo, para ficar com uma única detecção de carro.