aprendizagem de máquina avançada reconhecimento de … · mnist - que features usar? o número de...

40
Aprendizagem de Máquina Avançada Reconhecimento de dígitos manuscritos da base MNIST (http://yann.lecun.com/exdb/mnist/) Para quem instalou Cekeikon, uma cópia deste banco de dados está em: .../cekeikon5/tiny_dnn/data The MNIST database of handwritten digits has a training set of 60,000 examples, and a test set of 10,000 examples. The digits have been size-normalized and centered in a fixed-size image. The original black and white (bilevel) images from NIST were size normalized to fit in a 20x20 pixel box while preserving their aspect ratio. The resulting images contain grey levels as a result of the anti-aliasing technique used by the normalization algorithm. The images were centered in a 28x28 image by computing the center of mass of the pixels, and translating the image so as to posi- tion this point at the center of the 28x28 field. With some classification methods (particurarly template-based methods, such as SVM and K- nearest neighbors), the error rate improves when the digits are centered by bounding box rather than center of mass. Many methods have been tested with this training set and test set. Here are a few examples. 1

Upload: vuhanh

Post on 08-Dec-2018

217 views

Category:

Documents


0 download

TRANSCRIPT

Aprendizagem de Máquina Avançada

Reconhecimento de dígitos manuscritos da base MNIST(http://yann.lecun.com/exdb/mnist/)

Para quem instalou Cekeikon, uma cópia deste banco de dados está em:.../cekeikon5/tiny_dnn/data

The MNIST database of handwritten digits has a training set of 60,000 examples, and a test set of10,000 examples. The digits have been size-normalized and centered in a fixed-size image.

The original black and white (bilevel) images from NIST were size normalized to fit in a 20x20pixel box while preserving their aspect ratio. The resulting images contain grey levels as a result ofthe anti-aliasing technique used by the normalization algorithm. The images were centered in a28x28 image by computing the center of mass of the pixels, and translating the image so as to posi-tion this point at the center of the 28x28 field.

With some classification methods (particurarly template-based methods, such as SVM and K-nearest neighbors), the error rate improves when the digits are centered by bounding box ratherthan center of mass.

Many methods have been tested with this training set and test set. Here are a few examples.

1

MNIST - taxas de erro.

CLASSIFIER PREPROCESSINGTEST ERROR RATE

(%)Reference

Linear Classifiers

linear classifier (1-layer NN) none 12.0 LeCun et al. 1998

linear classifier (1-layer NN) deskewing 8.4 LeCun et al. 1998

K-Nearest Neighbors

K-nearest-neighbors, Euclidean (L2) none 3.09 Kenneth Wilder, U. Chicago

K-NN with non-linear deformation (P2DHMDM) shiftable edges 0.52 Keysers et al. IEEE PAMI 2007

Boosted Stumps

boosted stumps none 7.7 Kegl et al., ICML 2009

product of stumps on Haar f. Haar features 0.87 Kegl et al., ICML 2009

Non-Linear Classifiers

40 PCA + quadratic classifier none 3.3 LeCun et al. 1998

1000 RBF + linear classifier none 3.6 LeCun et al. 1998

SVMs

SVM, Gaussian Kernel none 1.4

Virtual SVM, deg-9 poly, 2-pixel jittered deskewing 0.56 DeCoste and Scholkopf, MLJ 2002

Neural Nets

2-layer NN, 300 hidden units, mean square error none 4.7 LeCun et al. 1998

6-layer NN 784-2500-2000-1500-1000-500-10 (on GPU) [elastic distortions]

none 0.35Ciresan et al. Neural Computation 10, 2010 and arXiv 1003.0358, 2010

Convolutional nets

Convolutional net LeNet-1subsampling to 16x16 pi-xels

1.7 LeCun et al. 1998

Convolutional net LeNet-4 none 1.1 LeCun et al. 1998

committee of 35 conv. net, 1-20-P-40-P-150-10 [elastic dis-tortions]

width normalization 0.23 Ciresan et al. CVPR 2012

2

MNIST - formatos de arquivos

There are 4 files:

train-images-idx3-ubyte: training set images train-labels-idx1-ubyte: training set labels t10k-images-idx3-ubyte: test set images t10k-labels-idx1-ubyte: test set labels

The training set contains 60000 examples, and the test set 10000 examples.

TRAINING SET LABEL FILE (train-labels-idx1-ubyte):[offset] [type] [value] [description] 0000 32 bit integer 0x00000801(2049) magic number (MSB first) 0004 32 bit integer 60000 number of items 0008 unsigned byte ?? label 0009 unsigned byte ?? label ........ xxxx unsigned byte ?? label

The labels values are 0 to 9.

Para ler arquivo de labels, é só ignorar os 8 primeiros bytes.

TRAINING SET IMAGE FILE (train-images-idx3-ubyte):[offset] [type] [value] [description] 0000 32 bit integer 0x00000803(2051) magic number 0004 32 bit integer 60000 number of images 0008 32 bit integer 28 number of rows 0012 32 bit integer 28 number of columns 0016 unsigned byte ?? pixel 0017 unsigned byte ?? pixel ........ xxxx unsigned byte ?? pixel

Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

Para ler arquivo de imagem, é só ignorar os 16 primeiros bytes.

3

MNIST - que features usar?

O número de features 28x28=784 é um tanto alto.

Excesso de features Tempo de processamento alto e baixa taxa de acertos.

Como diminuir o número de features?

* Diminuir a resolução.* Calcular bounding box.

Imagem original 28x28 Imagem com bounding box

Imagem reduzido para 50% 14x14 Imagem com bounding box reduzido para 50%

Imagem reduzido para 30% Imagem com bounding box reduzido para 30%8x8=64 features

Também seria possível normalizar a inclinação calculando o eixo principal e rotacionando para ali-nhar ao eixo y.

4

MNIST

Faremos aprendizagem redimensionando as imagens de entrada (28x28) para nladonlado (nlado 28). Podemos usar bounding box ou não.

Rotinas de io (já estão dentro do Cekeikon) [Nota: Alterei estas rotinas em 3/12/2018. Devo copiar a nova versão aqui.]

class MNIST { public: int nlado; bool inverte; bool ajustaBbox; string metodo; int na; vector< Mat_<GRY> > AX; vector<int> AY; Mat_<FLT> ax; Mat_<FLT> ay; int nq; vector< Mat_<GRY> > QX; vector<int> QY; Mat_<FLT> qx; Mat_<FLT> qy; Mat_<FLT> qp;

MNIST(int _nlado=28, bool _inverte=true, bool _ajustaBbox=true, string _metodo="flann") { nlado=_nlado; inverte=_inverte; ajustaBbox=_ajustaBbox; metodo=_metodo; } Mat_<GRY> bbox(Mat_<GRY> a); // Ajusta para bbox Mat_<FLT> bbox(Mat_<FLT> a); // Ajusta para bbox void leX(string nomeArq, int n, vector< Mat_<GRY> >& X, Mat_<FLT>& x); // funcao interna void leY(string nomeArq, int n, vector<int>& Y, Mat_<FLT>& y); // f. interna void le(string caminho="", int _na=60000, int _nq=10000); // Le banco de dados MNIST que fica no path caminho // ex: mnist.le("."); mnist.le("c:/diretorio"); // Se _na ou _nq for zero, nao le o respectivo // ex: mnist.le(".",60000,0); int contaErros(); Mat_<GRY> geraSaida(Mat_<GRY> q, int qy, int qp); // f. interna Mat_<GRY> geraSaidaErros(int maxErr=0); // conta erros e gera imagem com maxErr primeiros erros Mat_<GRY> geraSaidaErros(int nl, int nc); // Gera uma imagem com os primeiros nl*nc digitos classificados erradamente};

Mat_<GRY> MNIST::bbox(Mat_<GRY> a) { int esq=a.cols, dir=0, cima=a.rows, baixo=0; // primeiro pixel diferente de 255. for (int l=0; l<a.rows; l++) for (int c=0; c<a.cols; c++) { if (a(l,c)!=255) { if (c<esq) esq=c; if (dir<c) dir=c; if (l<cima) cima=l; if (baixo<l) baixo=l; } } if (!(esq<dir && cima<baixo)) xerro1("Erro MNIST::bbox"); Mat_<GRY> roi(a, Rect(esq,cima,dir-esq+1,baixo-cima+1)); Mat_<GRY> d; resize(roi,d,Size(nlado,nlado),0, 0, INTER_AREA); return d;}

Mat_<FLT> MNIST::bbox(Mat_<FLT> a) { int esq=a.cols, dir=0, cima=a.rows, baixo=0; // primeiro pixel diferente de 255. for (int l=0; l<a.rows; l++) for (int c=0; c<a.cols; c++) { if (a(l,c)<=0.5) { if (c<esq) esq=c; if (dir<c) dir=c; if (l<cima) cima=l; if (baixo<l) baixo=l; } } if (!(esq<dir && cima<baixo)) xerro1("Erro MNIST::bbox"); Mat_<FLT> roi(a, Rect(esq,cima,dir-esq+1,baixo-cima+1)); Mat_<FLT> d; resize(roi,d,Size(nlado,nlado),0, 0, INTER_AREA); return d;}

void MNIST::leX(string nomeArq, int n, vector< Mat_<GRY> >& X, Mat_<FLT>& x) { X.resize(n); for (unsigned i=0; i<X.size(); i++) X[i].create(nlado,nlado);

FILE* arq=fopen(nomeArq.c_str(),"rb"); if (arq==NULL) erro("Erro: Arquivo inexistente ",nomeArq); BYTE b; Mat_<GRY> t(28,28),d; fseek(arq,16,SEEK_SET); for (unsigned i=0; i<X.size(); i++) { for (unsigned l=0; l<28; l++) for (unsigned c=0; c<28; c++) { fread(&b,1,1,arq); t(l,c)=255-b; } if (ajustaBbox) { d=bbox(t); } else{ if (nlado!=28) resize(t,d,Size(nlado,nlado),0, 0, INTER_AREA); else t.copyTo(d); } if (!inverte) d=255-d; X[i]=d.clone(); }

5

fclose(arq);

x.create(X.size(),X[0].total()); for (int i=0; i<x.rows; i++) for (int j=0; j<x.cols; j++) x(i,j)=X[i](j)/255.0;}

void MNIST::leY(string nomeArq, int n, vector<int>& Y, Mat_<FLT>& y) { Y.resize(n); y.create(n,1); FILE* arq=fopen(nomeArq.c_str(),"rb"); if (arq==NULL) erro("Erro: Arquivo inexistente ",nomeArq); BYTE b; fseek(arq,8,SEEK_SET); for (unsigned i=0; i<y.total(); i++) { fread(&b,1,1,arq); Y[i]=b; y(i)=b; } fclose(arq);}

void MNIST::le(string caminho, int _na, int _nq) { na=_na; nq=_nq; if (na>60000) xerro1("na>60000"); if (nq>10000) xerro1("nq>10000");

if (na>0) { leX(caminho+"/train-images.idx3-ubyte",na,AX,ax); leY(caminho+"/train-labels.idx1-ubyte",na,AY,ay); } if (nq>0) { leX(caminho+"/t10k-images.idx3-ubyte",nq,QX,qx); leY(caminho+"/t10k-labels.idx1-ubyte",nq,QY,qy); qp.create(nq,1); }}

int MNIST::contaErros() { // conta numero de erros int erros=0; for (int l=0; l<qp.rows; l++) if (qp(l)!=qy(l)) erros++; return erros;}

Mat_<GRY> MNIST::geraSaida(Mat_<GRY> q, int qy, int qp) { Mat_<GRY> d(28,38,192); putTxt(d,0,28,to_string(qy)); putTxt(d,14,28,to_string(qp)); int delta=(28-q.rows)/2; copia(q,d,delta,delta); return d;}

Mat_<GRY> MNIST::geraSaidaErros(int maxErr) { // Gera imagem 23x38, colocando qy e qp a direita. int erros=contaErros(); Mat_<GRY> e(28,40*min(erros,maxErr),192); for (int j=0, i=0; j<qp.rows; j++) { if (qp(j)!=qy(j)) { Mat_<GRY> t=geraSaida(QX[j],qy(j),qp(j)); copia(t,e,0,40*i); i++; if (i>=min(erros,maxErr)) break; } } return e;}

Mat_<GRY> MNIST::geraSaidaErros(int nl, int nc) { // Gera uma imagem com os primeiros nl*nc digitos classificados erradamente Mat_<GRY> e(28*nl,40*nc,192); int j=0; for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) { //acha o proximo erro while (qp(j)==qy(j) && j<qp.rows) j++; if (j==qp.rows) goto saida; Mat_<GRY> t=geraSaida(QX[j],qy(j),qp(j)); copia(t,e,28*l,40*c); j++; } saida: return e;}

6

Comentários:1) AX e QX são vetor de imagens de treino e teste respectivamente (já com cores invertidas e ajus-tadas por bounding box, dependendo das opções de leitura).2) AY e QY são vetor de inteiros de rótulos (classificações corretas) de treino e teste respectiva-mente.3) na é o número de elementos de AX e AY. nq é o número de elementos de QX e QY;4) ax e qx são imagens tipo float (com na e nq linhas) que tem que em cada linha os pixels de umaimagem em AX/QX (convertidos para 0=preto e 1=branco).4) ay e qy são imagens tipo float (com na e nq linhas) que só tem uma coluna. São cópias dos ve-tores AY e QY (com rótulos de 0 a 9).5) qp é imagem tipo float com nq linhas e 1 coluna. O seu algoritmo deve preencher qp com asclassificações.6) O método int e=mnist.contaErros() devolve o número de entradas diferentes entre qy e qp. 7) Método Mat_<GRY> a=mnist.geraSaidaErros(2,5) gera imagem a com no máximo 10 imagensclassificadas incorretamente, organizadas em 2 linhas e 5 colunas (2 e 5 são parâmetros que vocêpode mudar).MNIST - flann

//flann.cpp - grad2018//Linkar com opencv 2 ou 3 #include <cekeikon.h>int main() { MNIST mnist(14, true, true); // redimensiona para 14x14, // inverte pt/br=true, ajustaBbox=true mnist.le("/home/hae/cekeikon5/tiny_dnn/data"); flann::Index ind(mnist.ax,flann::KDTreeIndexParams(9)); vector<int> indices(1); vector<float> dists(1); for (int l=0; l<mnist.qx.rows; l++) { ind.knnSearch(mnist.qx.row(l),indices,dists,1); mnist.qp(l)=mnist.ay(indices[0]); } printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq); Mat_<GRY> e=mnist.geraSaidaErros(10,20); imp(e,"flann.png"); }

Demora 2,7s e comete 2,93% de erro.

Algumas imagens classificadas incorretamente (classificação correta acima à direita, classificação atribuída pelo algoritmo abaixo à direita).

//visual.cpp - imprime imagens-teste de MNIST#include <cekeikon.h>

int main() { MNIST mnist(28,true,false); //nao redimensiona imagens //inverte preto/branco=true, //crop bounding box=false mnist.le("/home/hae/cekeikon5/tiny_dnn/data"); mnist.qp.setTo(-1); Mat_<GRY> e=mnist.geraSaidaErros(100,100); imp(e,"visual.png");}

7

MNIST - flann2 - informa tempo de leitura/treino e predição. Permite especificar tamanho de ladode imagens redimensionadas. Permite especificar se usar bounding box ou não.

Nota: -omp e #pragma omp parallel for são para fazer processamento paralelo usando OpenMP.

//flann2.cpp - grad2018//informa tempo de leitura/treino/teste//Linkar com opencv 2 ou 3 //compila flann2 -c -omp#include <cekeikon.h>

int main(int argc, char** argv) { if (argc!=3) erro("flann nlado bbox=false/true"); int nlado; convArg(nlado,argv[1]); bool bbox; convArg(bbox,argv[2]);

TimePoint t1=timePoint(); xdebug1("Lendo MNIST"); MNIST mnist(nlado, true, bbox); mnist.le("/home/hae/cekeikon5/tiny_dnn/data");

TimePoint t2=timePoint(); xdebug1("Treinando"); flann::Index ind(mnist.ax,flann::KDTreeIndexParams(4));

TimePoint t3=timePoint(); xdebug1("Fazendo predicao"); #pragma omp parallel for for (int l=0; l<mnist.nq; l++) { vector<int> indices(1); vector<float> dists(1); ind.knnSearch(mnist.qx.row(l),indices,dists,1); mnist.qp(l)=mnist.ay(indices[0]); } TimePoint t4=timePoint();

printf("Tempo de leitura: %f\n",timeSpan(t1,t2)); printf("Tempo de treinamento: %f\n",timeSpan(t2,t3)); printf("Tempo de predicao: %f\n",timeSpan(t3,t4)); printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq);}

$ flann2 14 true (com openmp processamento paralelo)File=flann2.cpp line=12 Lendo MNISTFile=flann2.cpp line=16 TreinandoFile=flann2.cpp line=19 Fazendo predicaoTempo de leitura: 0.682986Tempo de treinamento: 0.778823Tempo de predicao: 0.067839Erros= 2.99%

$ flann2 14 true (sem openmp - processamento em série)File=flann2.cpp line=12 Lendo MNISTFile=flann2.cpp line=16 TreinandoFile=flann2.cpp line=19 Fazendo predicaoTempo de leitura: 0.670360Tempo de treinamento: 0.778895Tempo de predicao: 0.324567Erros= 2.99%

8

MNIST - dtree

//dtree.cpp - grad2018//Linkar com opencv 2//compila dtree -c -omp#include <cekeikon.h>

int main(int argc, char** argv) { if (argc!=3) erro("dtree nlado bbox=true/false"); int nlado; convArg(nlado,argv[1]); bool bbox; convArg(bbox,argv[2]);

TimePoint t1=timePoint(); xdebug1("Lendo MNIST"); MNIST mnist(nlado, true, bbox); mnist.le("/home/hae/cekeikon5/tiny_dnn/data"); TimePoint t2=timePoint(); xdebug1("Treinando"); CvDTree ind; ind.train(mnist.ax,CV_ROW_SAMPLE,mnist.ay);

TimePoint t3=timePoint(); xdebug1("Fazendo predicao"); #pragma omp parallel for for (int l=0; l<mnist.nq; l++) { mnist.qp(l)=ind.predict(mnist.qx.row(l))->value; } TimePoint t4=timePoint(); printf("Tempo de leitura: %f\n",timeSpan(t1,t2)); printf("Tempo de treinamento: %f\n",timeSpan(t2,t3)); printf("Tempo de predicao: %f\n",timeSpan(t3,t4)); printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq);}

$ dtree 14 trueFile=dtree.cpp line=11 Lendo MNISTFile=dtree.cpp line=15 TreinandoFile=dtree.cpp line=18 Fazendo predicaoTempo de leitura: 0.649742Tempo de treinamento: 5.416059Tempo de predicao: 0.003407Erros= 22.87%

9

MNIST - KNearest

//knearest.cpp - pos2017//Linkar com opencv 2#include <cekeikon.h>int main(int argc, char** argv) { MNIST mnist(12, true, true); // inverte=true, ajustaBbox=true mnist.le("/home/hae/cekeikon5/tiny_dnn/data"); CvKNearest ind(mnist.ax,mnist.ay,Mat(),false,1); ind.find_nearest(mnist.qx, 1, &mnist.qp); printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq);}

Erros=2.42%Tempo=19s

//knn.cpp - grad2017#include <cekeikon.h>int main() { MNIST mnist(12, true, true); // inverte=true, ajustaBbox=true mnist.le("/home/hae/cekeikon5/tiny_dnn/data"); for (int i=0; i<mnist.qx.rows; i++) { if (i%100==0) cout << i << endl; // classificar qx.row(i) preenchendo qp(i) float menordistancia=1e30; int menorindice=0; for (int j=0; j<mnist.ax.rows; j++) { // comparar qx.row(i) com ax.row(j) float distancia=norm( mnist.qx.row(i)-mnist.ax.row(j) ); if (distancia<menordistancia) { menordistancia=distancia; menorindice=j; } mnist.qp(i)=mnist.ay(menorindice); } } printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq);}

Erros=2.42%Tempo gasto: 6 minutos.

Aqui, a implementação CvKNearest do OpenCV é muito boa. É cerca de 20 vezes mais rápido doque a nossa implementação. Provavelmente, usa instrução SIMD.

10

MNIST bayes

//bayes.cpp - pos2017//Linkar com opencv 2#include <cekeikon.h>int main(int argc, char** argv) { MNIST mnist(12, true, true); // inverte=true, ajustaBbox=true mnist.le("/home/hae/cekeikon5/tiny_dnn/data"); CvNormalBayesClassifier ind; ind.train(mnist.ax, mnist.ay); ind.predict(mnist.qx, &mnist.qp); printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq);}

Erros = 4.96%Tempo = 3s

11

MNIST svm

//svm.cpp - pos2017//Linkar com opencv 2#include <cekeikon.h>int main(int argc, char** argv) { MNIST mnist(12, true, true); // inverte=true, ajustaBbox=true mnist.le("/home/hae/cekeikon5/tiny_dnn/data"); CvSVM ind; ind.train(mnist.ax, mnist.ay); for (int l=0; l<mnist.nq; l++) { mnist.qp(l)=ind.predict(mnist.qx.row(l)); } printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq);}

erro = 4.18% tempo = 4 min.

12

MNIST - annRede neural necessita de 10 neurônios na saída. Um neurônio na saída não consegue classificar 10 classes.

Para isso, no treino, vamos construir matriz temporário ay2. Matriz ay contém na linhas e uma coluna com rótulos 0 a 9. Matriz ay2 contém na linhas e 10 colunas, onde todas as colunas são -1, exceto a columa de índice igual ao rótulo correto, que contém +1.

Na predição, vamos usar argMax para calcular rótulo a partir de 10 saídas de rede neural.

//ann.cpp - grad2016//Linkar com opencv 2.4.10#include <cekeikon.h>

int argMax(Mat_<FLT> a) { FLT maximo=a(0); int indmax=0; for (int i=1; i<a.total(); i++) if (a(i)>maximo) { maximo=a(i); indmax=i; } return indmax;}

int main() { xdebug1("Lendo MNIST"); MNIST mnist(14, true, true); mnist.le("/home/hae/cekeikon5/tiny_dnn/data");

int t1=centseg(); xdebug1("Calculando ay2"); Mat_<FLT> ay2(mnist.ay.rows, 10); ay2.setTo(-1.0); for (int i=0; i<mnist.ay.rows; i++) { int j=arredonda(mnist.ay(i)); assert(0<=j && j<10); ay2(i,j)=+1.0; }

xdebug1("Treinando"); Mat_<int> v = (Mat_<int>(1,4) << mnist.ax.cols, 100, 30, 10); xprint(v); CvANN_MLP ind(v); ind.train(mnist.ax,ay2,Mat());

int t2=centseg(); xdebug1("fazendo predicao"); //#pragma omp parallel for for (int l=0; l<mnist.nq; l++) { Mat_<FLT> saida(1,mnist.qx.cols); ind.predict(mnist.qx.row(l),saida); mnist.qp(l)=argMax(saida); } int t3=centseg();

int tt=t2-t1; printf("Tempo de treinamento: %d.%02d\n",tt/100,tt%100); int tp=t3-t2; printf("Tempo de predicao: %d.%02d\n",tp/100,tp%100);

xdebug1("contando erros e gerando imagem de erros"); printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq);

Mat_<GRY> e=mnist.geraSaidaErros(10); imp(e,"flann.png"); }

13

$ ann 14 true (30 neurônios)File=ann.cpp line=20 Lendo MNISTFile=ann.cpp line=32 TreinandoFile=ann.cpp line=37 Fazendo predicaoTempo de leitura: 0.664592Tempo de treinamento: 20.592960Tempo de predicao: 0.016476Erros= 8.77%

$ ann 14 true (100 neurônios)File=ann.cpp line=20 Lendo MNISTFile=ann.cpp line=32 TreinandoFile=ann.cpp line=37 Fazendo predicaoTempo de leitura: 0.668131Tempo de treinamento: 130.075906Tempo de predicao: 0.045612Erros= 7.39%

14

Boost só consegue tomar decisão binária. Assim, vamos usar 10 boosts para fazer classificação em 10 classes. Cada boost (de 0 a 9) vai ser treinado com -1 (se a imagem não for daquela classe) e +1 (se a ima-gem for daquela classe. Isto é feito convertendo ay (an linhas e 1 coluna) para ay2 (an linhas e 10colunas).Cada boost (de 0 a 9) vai fazer predição retornando um número real indicando a "certeza" de que onúmero da imagem é daquela classe. Vamos classificar a imagem na classe com maior "certeza".

//boost.cpp - grad2018//Linkar com opencv 2//compila boost -c -omp#include <cekeikon.h>

int argMax(Mat_<FLT> a){ FLT maximo=a(0); int indmax=0; for (int i=1; i<a.total(); i++) if (a(i)>maximo) { maximo=a(i); indmax=i; } return indmax;}

int main(int argc, char** argv) { if (argc!=3) erro("ann nlado bbox=false/true"); int nlado; convArg(nlado,argv[1]); bool bbox; convArg(bbox,argv[2]);

TimePoint t1=timePoint(); xdebug1("Lendo MNIST"); MNIST mnist(nlado, true, bbox); mnist.le("/home/hae/cekeikon5/tiny_dnn/data");

Mat_<FLT> ay2(mnist.na,10); ay2.setTo(-1.0); for (unsigned i=0; i<mnist.na; i++) { int j=mnist.ay(i); assert(0<=j && j<10); ay2(i,j)=1.0; } TimePoint t2=timePoint(); xdebug1("Treinando"); vector< CvBoost > ind(10); #pragma omp parallel for for (unsigned i=0; i<ind.size(); i++) ind[i].train(mnist.ax,CV_ROW_SAMPLE,ay2.col(i));

TimePoint t3=timePoint(); xdebug1("Fazendo predicao"); #pragma omp parallel for for (int l=0; l<mnist.nq; l++) { Mat_<FLT> saida(1,ind.size()); for (unsigned c=0; c<ind.size(); c++) saida(c)=ind[c].predict(mnist.qx.row(l),Mat(),Range::all(),false,true); mnist.qp(l)=argMax(saida); } TimePoint t4=timePoint();

printf("Tempo de leitura: %f\n",timeSpan(t1,t2)); printf("Tempo de treinamento: %f\n",timeSpan(t2,t3)); printf("Tempo de predicao: %f\n",timeSpan(t3,t4)); printf("Erros=%10.2f%%\n",100.0*mnist.contaErros()/mnist.nq);}

$ boost 14 trueFile=boost.cpp line=20 Lendo MNISTFile=boost.cpp line=31 TreinandoFile=boost.cpp line=37 Fazendo predicaoTempo de leitura: 0.681949Tempo de treinamento: 394.401766Tempo de predicao: 0.065186Erros= 7.50%

15

MNIST Taxas de erro (%) e tempos de processamento sem usar bbox. Taxa de erro com inverte=false entre parênteses.

Não conta tempo de leitura/escrita.Tempo com o uso de OpenMP entre parênteses.

nlado 4 6 8 10 12 14 16 28

cuttree3.1.0

Erros24.46%(25.68)

Tempo 0.04

dtree2.4.10

Erros 33.30% 27.90% 24.79% 26.80%

Treino 1.54 7.02 44.46

Predi-ção

0.00 0.01 0.01

knearest3.1.0

Erros 2.72%

Tempo 150.48

flann3.1.0

Erros 3.83%

Treino 1.49

Predi-ção

0.34

bayes2.4.10

Erros 10.92% 9.09%

Tempo 1.22 3.44

SVM2.4.10

Erros 1.95% 2.55%

Tempo 2.2min 3,6min

ANN2.4.10

[nlado2,30, 10]

Erros 11.43% 11.04% 9.36%9.31%

(8.93%)

Treino 8.41 12.11 22.06 94.31

Predi-ção

0.04 0.05 0.06 0.40

Boost1

2.4.10

Erros 8.63%

Tempo528.11

[333.37]

RTrees2.4.10

Erros 17.64%

Tempo 74.97

1 ANN e Boosting só funcionam para 2 categorias. Assim, foram utilizados 10 boostings, escolhen-do a maior saída.

16

MNIST Taxas de erro (%) e tempos de processamento usando bbox. Taxa de erro com inverte=false entre parênteses.

O tempo é para treinar e fazer predição. Não conta tempo de leitura/escrita.Tempo com o uso de OpenMP entre parênteses.

nlado 4 6 8 10 12 14 16 28

cuttree3.1.0

Erros 23.00% 31.94%

Tempo 0.03 0.04

dtree2.4.10

Erros 29.22% 24.95% 25.32% 24.67% 22.87%

Treino 1.63 5.41

Predição 0.00 (0.003)

knearest3.1.0

Erros 2.42% 2.50%

Tempo 19s

flann3.1.0

ou 2.4.10

Erros 3.30% 3.09% 2.99% 3.24%

Treino 0.94 0.77 1.49

Predição (0.08) (0.07)0.33

(0.08)

bayes2.4.10

Erros 4.96%

Tempo 3s

SVM2.4.10

Erros 4.18%

Tempo 4min

Boost

Erros 7.5%

Treino 394

(0.06)

ANN OpenCV2.4.10

30 neurôniosescondidos

Erros 8.82% 8.26% 9.19% 8.20% 8.45%

Treino 9.77 13.76 17 25.45 111.71

Predição 0.04 0.05 (0.03) 0.07 0.19

ANN OpenCV2.4.10

100 neurô-nios escondi-dos

Erro 7.91%

Rede neural mi-nha implem.100 hidden

neurons

Erros 2.17% 2.04% 2.13%

Tempo 159s 200s 300s

RedeNeuralMinha implem.30 hidden neu-

rons

Erros 4.04%

Tempo 68s

FisherfaceErros 8.39% 8.45%

Tempo 66s

LBPHnlado=12

(parâm.)Erros

(1,4,2,2)20%

(1,8,2,2)17.2%

(1,4,3,3)12%

(1,4,4,4)10.76%

Tempo 58s 256s 90s 117s

Tiny-DNN rede neural rasa100 neurônios

2.22%

Pythonrede neural rasa100 neurônios

2.22%

DNN tiny-dnn 0.35%

DNN theano 0.98%

17

Pedestre/carro Decidir se uma imagem colorida 128x64 contém um pedestre ou é um carro. O banco de dados ori-ginal está em:

http://cbcl.mit.edu/cbcl/software-datasets/PedestrianData.htmlhttp://cbcl.mit.edu/projects/cbcl/software-datasets/CarData1Readme.html

EP2 de pós-graduação, 2011. Diretório ~/algpi/mle_avancada/pedestre, ~/haebase/mit_cars~/haebase/mit_pedestrians

per00001.ppm per00003.ppm carl0001.ppm carr0001.ppm carr0002.ppm924 imagens de pedestres.516 imagens da parte esquerda dos carros. 516 imagens da parte direita dos carros.

Ideia intuitiva: Existe um contorno que lembre pedestre?Isto pode ser implementado usando histograma de gradiente orientado (HOG).

Se entrar diretamente os 128x64x3=24576 valores dos pixels, a aprendizagem de máquina terá di-ficuldade em acertar: o espaço das características é grande demais.

f : [0 ,255 ]24576→{0,1}

Gradiente: Veja apostila de convolução.

Gradiente de uma imagem: Aponta para direção onde a imagem torna-se mais clara. A magnitudede gradiente é proporcionar à “inclinação”.

18

Pedestre/carro

Dalal, Navneet, and Bill Triggs. "Histograms of oriented gradients for human detection." Computer Vis-ion and Pattern Recognition, 2005. CVPR 2005. IEEE Computer Society Conference on . Vol. 1. IEEE,2005.

Solução: Calcular histograma de gradiente orientado (HOG) de uma imagem por blocos:

128x64x3=24576 valores reduzem-se a 1024.Boosting é bom para este problema, pois consegue descobrir quais são features importantes e quaissão inúteis.

19

Pedestre/carro

Código HOG (incluído em Cekeikon):

class EXPORTA HOG { public: M3d_<FLT> blo; // I3D int nBins, cellSizeL, cellSizeC; // nBins=nAngulos, cellSize em pixels void CPX2his(Mat_<CPX> b, M3d_<FLT>& his); // his = I3D void his2blo(M3d_<FLT> his, M3d_<FLT>& cell); // his,cell = I3D

HOG(Mat_<COR> a, int _nBins=8, int _cellSizeL=8, int _cellSizeC=0); void exporta(Mat_<GRY>& d, int zoom=4, double mult=1.0); void show(int zoom=4, double mult=1.0, string nome="");};

void HOG::CPX2his(Mat_<CPX> b, M3d_<FLT>& his)// Converte imagem de gradiente b para histograma de gradiente pixel-a-pixel his// angulo gira em sentido horario{ int vint[]={nBins,b.rows,b.cols}; his.create(3,vint); his.setTo(0.0);

FLT denominador=180.0/nBins; FLT denominador2=denominador/2; for (int l=0; l<b.rows; l++) for (int c=0; c<b.cols; c++) { CPX e=b(l,c); FLT v=abs(e); FLT a=fastAtan2(e.imag(),e.real()); // 0<=a<360 a+=denominador2; if (a>=180) a=a-180; // 0<=a<180 if (a>=180) a=a-180; // 0<=a<180 int q=int(a/denominador); //cout << a << " " << q << endl; if (!(0<=q && q<nBins)) erro("Erro HOG::CPX2his inesperado"); his(q,l,c) += v; }}

void HOG::his2blo(M3d_<FLT> his, M3d_<FLT>& cell)// histograma para celula{ int ns=his.size[0]; int nl=his.size[1]/cellSizeL; int nc=his.size[2]/cellSizeC; int vint[]={ns,nl,nc}; cell.create(3,vint); cell.setTo(0.0);

for (int s=0; s<ns; s++) for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) { for (int l2=0; l2<cellSizeL; l2++) for (int c2=0; c2<cellSizeC; c2++) cell(s,l,c) += his(s,cellSizeL*l+l2,cellSizeC*c+c2); cell(s,l,c) /= (cellSizeL*cellSizeC); }}

HOG::HOG(Mat_<COR> a, int _nBins, int _cellSizeL, int _cellSizeC){ nBins=_nBins; cellSizeL=_cellSizeL; cellSizeC=_cellSizeC; if (cellSizeC<=0) cellSizeC=cellSizeL; Mat_<CPX> b=gradiente(a);

M3d_<FLT> his; // I3D CPX2his(b,his);

20

his2blo(his,blo); // I3d}

void HOG::exporta(Mat_<GRY>& d, int zoom, double mult){ int bsl=zoom*cellSizeL; // tamanho da celula na imagem int bsc=zoom*cellSizeC; // tamanho da celula na imagem int ns=blo.size[0]; int nl=blo.size[1]; int nc=blo.size[2]; //printf("%d %d %d\n",ns,nl,nc); double angstep=180/nBins; int graystep=max(255/nBins,64); double bsl2=bsl/2.0; double bsc2=bsc/2.0; d.create(bsl*nl,bsc*nc); d.setTo(0);

for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) { for (int s=0; s<ns; s++) { double angulo=angstep*s; if (angulo>=180) angulo -= 180; if (angulo>=180) angulo -= 180; angulo=deg2rad(angulo); double v=blo(s,l,c); if (v==0) continue; double x=mult*zoom*bsc2*v*cos(angulo); double y=mult*zoom*bsl2*v*sin(angulo); int px=cvRound(c*bsc+bsc2+x); int py=cvRound(l*bsl+bsl2+y); int qx=cvRound(c*bsc+bsc2-x); int qy=cvRound(l*bsl+bsl2-y); LineIterator it( d, Point(px,py), Point(qx,qy) ); for (int i=0; i<it.count; i++, ++it) { **it = saturate_cast<GRY>(**it + graystep); } } }}

void HOG::show(int zoom, double mult, string nome){ Mat_<GRY> d; exporta(d,zoom,mult); if (nome=="") mostra(d); else mostra(d,nome);}

21

(~/algpi/mle_avancada/pedestre)//pedestre.cpp - 2016//Extrai HOG de imagens 128x64#include <cekeikon.h>

int main(int argc, char** argv){ if (argc<4) { printf("Pedestre: Extrai HOG de 128x64 e grava como .IMG ou .TXT\n"); printf(" Alimenta tambem a imagem espelhada\n"); printf("Pedestre pedestre.img -1|+1 imagens128x64*.jpg\n"); printf(" Se nao existe pedestre.img, cria novo\n"); printf(" Caso contrario, acrescenta novas linhas em pedestre.img\n"); printf(" -1: exemplo negativo. +1: exemplo positivo.\n"); erro("Erro: Numero de argumentos invalido"); }

int n=argc-3; Mat_<FLT> pedant; if (existeArq(argv[1])) { le(pedant,argv[1]); if (pedant.cols!=1024+1)

erro("Erro: pedestre.img deve ter 1025 colunas"); } Mat_<FLT> ped(pedant.rows+2*n,1024+1,0.0); for (int l=0; l<pedant.rows; l++) for (int c=0; c<pedant.cols; c++) ped(l,c)=pedant(l,c); int inicio=pedant.rows;

FLT rotulo; convArg(rotulo,argv[2]);

int ns=8; int nl=16; int nc=8; Mat_<COR> a; for (int i=0; i<n; i++) { le(a,argv[i+3]); printf("Processando %s\r",argv[i+3]); { HOG hog(a,8, 8,8); //8 bins, blocos 8x8 if (hog.blo.size[0]!=ns || hog.blo.size[1]!=nl || hog.blo.size[2]!=nc)

erro("Erro: Dimensoes diferentes"); for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) { for (int s=0; s<ns; s++) ped(2*i+inicio,l*nc*ns+c*ns+s)=hog.blo(s,l,c); ped(2*i+inicio,1024)=rotulo; } }

{ flip(a,a,1); // espelha HOG hog(a,8, 8,8); //8 bins, blocos 8x8 if (hog.blo.size[0]!=ns || hog.blo.size[1]!=nl || hog.blo.size[2]!=nc)

erro("Erro: Dimensoes diferentes"); for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) { for (int s=0; s<ns; s++) ped(2*i+1+inicio,l*nc*ns+c*ns+s)=hog.blo(s,l,c); ped(2*i+1+inicio,1024)=rotulo; } } } printf("\n"); printf("Tamanho do arquivo: %d %d\n",ped.rows,ped.cols); imp(ped,argv[1]);}

22

Pedestre/carro (~/algpi/mle_avancada/pedestre)

//bootrain2.cpp grad2018#include <cekeikon.h>

int main(int argc, char** argv){ if (argc<3) erro("Erro: BooTrain2 treino.img boo.xml (ou .yaml)");

Mat_<FLT> tr; le(tr,argv[1]); if (tr.rows<20) xerro1("Erro: Tem que ter pelo menos 20 imagens");

Mat_<FLT> features=tr(Rect(0,0,tr.cols-1,tr.rows)); Mat_<FLT> saidas=tr(Rect(tr.cols-1,0,1,tr.rows));

printf("Treinando...\n"); // Escolhemos os parametros do Boosting CvBoostParams param(CvBoost::GENTLE, 200, 0.95, 2, false, 0); CvBoost ind(features,CV_ROW_SAMPLE,saidas,Mat(),Mat(),Mat(),Mat(),param); ind.save(argv[2]);}

//boopred2.cpp grad2018#include <cekeikon.h>

int main(int argc, char** argv){ if (argc<3) erro("Erro: BooPred boo.xml teste.img");

CvBoost ind; ind.load(argv[1]); Mat_<FLT> te; le(te,argv[2]);

printf("Classificando...\n"); int acertos=0; for (int l=0; l<te.rows; l++) { Mat_<FLT> query(1,te.cols-1); for (int c=0; c<query.cols; c++) query(0,c)=te(l,c); float predicao=ind.predict(query); float rotulo=te(l,te.cols-1); if (predicao==rotulo) acertos++; } printf("Total=%d acertos=%d taxa_acerto=%8.1f%%\n",te.rows,acertos,100.0*acertos/te.rows);}

23

Pedestre/carro (~/algpi/mle_avancada/pedestre)

demo.sh:

#!/bin/bash#demo.shset -vrm *.imgrm *.xmlrm *.txt

#dir /home/hae/haebase/mit_pedestrians/per0001*.ppm #dir /home/hae/haebase/mit_cars/carl001*.ppm #dir /home/hae/haebase/mit_cars/carr001*.ppm

#Cria HOG de treino treino.txt usando 9 imagens de pedestres e 18 imagens de carrospedestre treino.txt +1 /home/hae/haebase/mit_pedestrians/per0000*.ppm pedestre treino.txt -1 /home/hae/haebase/mit_cars/carl000*.ppm /home/hae/hae-base/mit_cars/carr000*.ppm

#Cria HOG de teste teste.txt usando 10 imagens de pedestres e 20 imagens de carrospedestre teste.txt +1 /home/hae/haebase/mit_pedestrians/per0001*.ppm pedestre teste.txt -1 /home/hae/haebase/mit_cars/carl001*.ppm /home/hae/hae-base/mit_cars/carr001*.ppm

#Faz o treino e predicao usando Adaboostbootrain2 treino.txt boo.xmlboopred2 boo.xml treino.txtboopred2 boo.xml teste.txt

Mostrar treino.txt e teste.txtTreinando com 27 imagens e testando em outras 30 imagens (com espelhamento): 93.3% de acerto.

roda.sh:

#!/bin/bash# roda.shset -vrm t*.imgrm t*.xmlrm t*.txt

pedestre treino.img +1 /home/hae/haebase/mit_pedestrians/per000*.ppm /home/hae/haebase/mit_pedestri-ans/per001*.ppm /home/hae/haebase/mit_pedestrians/per002*.ppm /home/hae/haebase/mit_pedestri-ans/per003*.ppm pedestre treino.img -1 /home/hae/haebase/mit_cars/carl00*.ppm /home/hae/haebase/mit_cars/carr00*.ppm/home/hae/haebase/mit_cars/carl01*.ppm /home/hae/haebase/mit_cars/carr01*.ppm

pedestre teste.img +1 /home/hae/haebase/mit_pedestrians/per*.ppm pedestre teste.img -1 /home/hae/haebase/mit_cars/carl*.ppm /home/hae/haebase/mit_cars/carr*.ppm

bootrain2 treino.img boo.xmlboopred2 boo.xml treino.imgboopred2 boo.xml teste.img

Treinando com 797 imagens e testando em 1956 imagens (incluindo imagens de treino e com espe-lhamento): 100.0% de acerto.

[Para eu lembrar: Seria interessante testar alguns outros métodos de aprendizagem.]

24

Pedestre/carro O que acontece se não usa HOG?Vamos reduzir as imagens para 32x16 pixels e usar valores RGB como features. 1536 features.

//pixels.cpp - 2016#include <cekeikon.h>

int main(int argc, char** argv){ if (argc<4) { printf("Pixels: Reduz 128x64 para 32x16 grava RGB como .IMG ou .TXT\n"); printf(" Alimenta tambem a imagem espelhada\n"); printf("Pixels pixels.img -1|+1 imagens128x64*.jpg\n"); printf(" Se nao existe pixels.img, cria novo\n"); printf(" Caso contrario, acrescenta novas linhas em pixels.img\n"); printf(" -1: exemplo negativo. +1: exemplo positivo.\n"); erro("Erro: Numero de argumentos invalido"); }

int n=argc-3; int nl=32; int nc=16; int total=nl*nc*3; //1536

Mat_<FLT> pedant; if (existeArq(argv[1])) { le(pedant,argv[1]); if (pedant.cols!=total+1) erro("Erro: pixels.img deve ter 1537 colunas"); } Mat_<FLT> ped(pedant.rows+2*n,total+1,0.0); for (int l=0; l<pedant.rows; l++) for (int c=0; c<pedant.cols; c++) ped(l,c)=pedant(l,c); int inicio=pedant.rows;

FLT rotulo; convArg(rotulo,argv[2]);

Mat_<COR> a,b; for (int i=0; i<n; i++) { le(a,argv[i+3]); printf("Processando %s\r",argv[i+3]); { resize(a,b,Size(nc,nl)); for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) { for (int s=0; s<3; s++) ped(2*i+inicio,3*(l*nc+c)+s)=b(l,c)[s]; ped(2*i+inicio,total)=rotulo; } }

{ flip(b,b,1); for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) { for (int s=0; s<3; s++) ped(2*i+inicio+1,3*(l*nc+c)+s)=b(l,c)[s]; ped(2*i+inicio+1,total)=rotulo; } } } printf("\n"); imp(ped,argv[1]);}

25

#!/bin/bash#pixdemo.shset -vrm pix*.imgrm pix*.xmlrm pix*.txt

pixels pixtreino.txt +1 /home/hae/haebase/mit_pedestrians/per0000*.ppm pixels pixtreino.txt -1 /home/hae/haebase/mit_cars/carl000*.ppm /home/hae/hae-base/mit_cars/carr000*.ppm

pixels pixteste.txt +1 /home/hae/haebase/mit_pedestrians/per0001*.ppm pixels pixteste.txt -1 /home/hae/haebase/mit_cars/carl001*.ppm /home/hae/hae-base/mit_cars/carr001*.ppm

bootrain2 pixtreino.txt pixboo.xmlboopred2 pixboo.xml pixtreino.txtboopred2 pixboo.xml pixteste.txt=============c:\haepi\algpi\mle\pedestre>boopred pixboo.xml pixtreino.txtTotal=54 acertos=54 taxa_acerto= 100.0%

c:\haepi\algpi\mle\pedestre>boopred pixboo.xml pixteste.txtTotal=60 acertos=41 taxa_acerto= 68.3%================

#!/bin/bash# pixroda.batset -vrm pix*.imgrm pix*.xmlrm pix*.txt

pixels pixtreino.img +1 /home/hae/haebase/mit_pedestrians/per0000*.ppm /home/hae/haebase/mit_pedes-trians/per0001*.ppm /home/hae/haebase/mit_pedestrians/per0002*.ppm /home/hae/haebase/mit_pedestri-ans/per0003*.ppm /home/hae/haebase/mit_pedestrians/per0004*.ppm /home/hae/haebase/mit_pedestri-ans/per0005*.ppm pixels pixtreino.img -1 /home/hae/haebase/mit_cars/carl000*.ppm /home/hae/hae-base/mit_cars/carr000*.ppm /home/hae/haebase/mit_cars/carl001*.ppm /home/hae/hae-base/mit_cars/carr001*.ppm /home/hae/haebase/mit_cars/carl002*.ppm /home/hae/hae-base/mit_cars/carr002*.ppm

pixels pixteste.img +1 /home/hae/haebase/mit_pedestrians/per*.ppm pixels pixteste.img -1 /home/hae/haebase/mit_cars/carl*.ppm /home/hae/haebase/mit_cars/carr*.ppm

bootrain2 pixtreino.img pixboo.xmlboopred2 pixboo.xml pixtreino.imgboopred2 pixboo.xml pixteste.img===========================c:\haepi\algpi\mle\pedestre>boopred pixboo.xml pixtreino.imgTotal=1594 acertos=1594 taxa_acerto= 100.0%

c:\haepi\algpi\mle\pedestre>boopred pixboo.xml pixteste.imgTotal=3912 acertos=3889 taxa_acerto= 98.4%

Portanto, a aprendizagem consegue acertar razoavelmente mesmo sem usar HOG!

26

Pedestre/carro O que acontece se não usa HOG nem cor? Vamos converter as imagens para níveis de cinza, redu-zir as imagens para 64x32 pixels e usar valores de cinza como features. 2048 features.

//gray.cpp - 2016#include <cekeikon.h>

int main(int argc, char** argv){ if (argc<4) { printf("Gray: Reduz 128x64 para 64x32 grava gray como .IMG ou .TXT\n"); printf(" Alimenta tambem a imagem espelhada\n"); printf("Gray gray.img -1|+1 imagens128x64*.jpg\n"); printf(" Se nao existe gray.img, cria novo\n"); printf(" Caso contrario, acrescenta novas linhas em gray.img\n"); printf(" -1: exemplo negativo. +1: exemplo positivo.\n"); erro("Erro: Numero de argumentos invalido"); }

int n=argc-3; int nl=64; int nc=32; int total=nl*nc; //2048

Mat_<FLT> pedant; if (existeArq(argv[1])) { le(pedant,argv[1]); if (pedant.cols!=total+1) erro("Erro: pixels.img deve ter 2049 colunas"); } Mat_<FLT> ped(pedant.rows+2*n,total+1,0.0); for (int l=0; l<pedant.rows; l++) for (int c=0; c<pedant.cols; c++) ped(l,c)=pedant(l,c); int inicio=pedant.rows;

FLT rotulo; convArg(rotulo,argv[2]);

Mat_<COR> a; Mat_<GRY> b; for (int i=0; i<n; i++) { le(a,argv[i+3]); printf("Processando %s\r",argv[i+3]); { converte(a,b); resize(b,b,Size(nc,nl)); for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) ped(2*i+inicio,l*nc+c)=b(l,c); ped(2*i+inicio,total)=rotulo; }

{ flip(b,b,1); for (int l=0; l<nl; l++) for (int c=0; c<nc; c++) ped(2*i+inicio+1,l*nc+c)=b(l,c); ped(2*i+inicio+1,total)=rotulo; } } printf("\n"); imp(ped,argv[1]);}

27

#!/bin/bash#grydemo.shrm gry*.imgrm gry*.xmlrm gry*.txt

gray grytreino.txt +1 /home/hae/haebase/mit_pedestrians/per0000*.ppm gray grytreino.txt -1 /home/hae/haebase/mit_cars/carl000*.ppm /home/hae/hae-base/mit_cars/carr000*.ppm

gray gryteste.txt +1 /home/hae/haebase/mit_pedestrians/per0001*.ppm gray gryteste.txt -1 /home/hae/haebase/mit_cars/carl001*.ppm /home/hae/haebase/mit_cars/carr001*.ppm

bootrain2 grytreino.txt gryboo.xmlboopred2 gryboo.xml grytreino.txtboopred2 gryboo.xml gryteste.txt=========================================c:\haepi\algpi\mle\pedestre>boopred gryboo.xml grytreino.txtTotal=54 acertos=54 taxa_acerto= 100.0%c:\haepi\algpi\mle\pedestre>boopred gryboo.xml gryteste.txtTotal=60 acertos=51 taxa_acerto= 85.0%==========================================

#!/bin/bash#gryroda.shrm gry*.imgrm gry*.xmlrm gry*.txt

gray grytreino.img +1 /home/hae/haebase/mit_pedestrians/per000*.ppm /home/hae/haebase/mit_pedestri-ans/per001*.ppm /home/hae/haebase/mit_pedestrians/per002*.ppm /home/hae/haebase/mit_pedestri-ans/per003*.ppmgray grytreino.img -1 /home/hae/haebase/mit_cars/carl00*.ppm /home/hae/haebase/mit_cars/carr00*.ppm/home/hae/haebase/mit_cars/carl01*.ppm /home/hae/haebase/mit_cars/carr01*.ppm

gray gryteste.img +1 /home/hae/haebase/mit_pedestrians/per*.ppm gray gryteste.img -1 /home/hae/haebase/mit_cars/carl*.ppm /home/hae/haebase/mit_cars/carr*.ppm

bootrain2 grytreino.img gryboo.xmlboopred2 gryboo.xml grytreino.imgboopred2 gryboo.xml gryteste.img======================c:\haepi\algpi\mle\pedestre>boopred gryboo.xml grytreino.imgTotal=1594 acertos=1594 taxa_acerto= 100.0%c:\haepi\algpi\mle\pedestre>boopred gryboo.xml gryteste.imgTotal=3912 acertos=3881 taxa_acerto= 99.2%======================

A taxa de acertos é bem alta![Experimentar usando menos imagens de treinamento.]

28

Outro problema usando HOG: Detectar estado de chave de alta tensão.

29

Detectar rostos:

Exercícios-programas relacionados: EP2 grad-2013, EP2 pos-2013

Explicando EP2 pos-2013:

O banco de dados de imagens da FEI: http://fei.edu.br/~cet/frontalimages_spatiallynormalized_part1.zip http://fei.edu.br/~cet/frontalimages_spatiallynormalized_part2.zip

contém 400 imagens de faces humanas 300x250. Redimensionei essas imagens para tamanho30x25.

Peguei 290 dessas imagens, construindo as amostras positivas ap????.jpg:

Peguei cinco imagens 240x320 que não contêm faces para servirem de amostras negativas (fuiandando com janela 30x25 para obter amostras negativas):

Escondi as 10 imagens restantes em outras imagens sem faces, para servirem de imagens tes-tes:

Fiz treinamento usando Boosting, e encontrei corretamente todas as 10 faces escondidas, semfalsos positivos.

30

Fiz busca em outras imagens, tomando cuidado para deixar as faces aproximadamente nasmesmas dimensões do treino:

Achou algumas faces, mas perdeu outras e cometeu erros falso positivo. Se treinar com bancode imagens de faces maior, deve melhorar.

//Treina.cpp - demora 21 minutos#include <cekeikon.h>int main() { vector<string> vs; vsWildCard("../dados/ap*.jpg",vs); vsWildCard("../dados/an*.jpg",vs);

int nfeatures=30*25; Mat_<FLT> features; Mat_<FLT> feature(1,nfeatures); Mat_<FLT> saidas; Mat_<FLT> saida(1,1); Mat_<FLT> a; for (unsigned i=0; i<vs.size(); i++) { printf("Lendo arquivo %12s\r",vs[i].c_str()); le(a,vs[i]); string letra=minuscula(semDiret(vs[i]).substr(1,1)); if (letra=="p") saida(0)=1; else if (letra=="n") saida(0)=-1; else erro("Erro nome arquivo");

for (int l=0; l<a.rows-30+1; l++) for (int c=0; c<a.cols-25+1; c++) { int j=0; for (int l2=0; l2<30; l2++) for (int c2=0; c2<25; c2++) { feature(j)=a(l+l2,c+c2); j++; } features.push_back(feature); saidas.push_back(saida); } } printf("\nHa %d exemplos de treino\n",features.rows); printf("Construindo a arvore\n"); CvBoost ind(features,CV_ROW_SAMPLE,saidas); ind.save("criterio.xml");}

Ir para diretório ~/algpi/mle_avancada/detface-pos2013-ep2/boostExecutar:>localiza ../dados/q?.jpg (ate q8.jpg)

31

//testa.cpp//Diz quantas janelas com faces e não-faces existem numa imagem#include <cekeikon.h>FLT limiar=0.0;

int main(int argc, char** argv){ if (argc<2) { printf("Testa nome1.pgm nome2.pgm...\n"); erro("Erro: Numero de argumentos invalido"); } vector<string> vs; for (int i=1; i<argc; i++) vsWildCard(argv[i],vs); CvBoost ind; ind.load("criterio.xml");

int nfeatures=30*25; Mat_<FLT> feature(1,nfeatures); Mat_<FLT> saida(1,1); Mat_<FLT> a; for (unsigned i=0; i<vs.size(); i++) { printf("Lendo arquivo %12s. ",vs[i].c_str()); le(a,vs[i]); int pos=0; int neg=0; FLT predicao=0; for (int l=0; l<a.rows-30+1; l++) for (int c=0; c<a.cols-25+1; c++) { int j=0; for (int l2=0; l2<30; l2++) for (int c2=0; c2<25; c2++) { feature(j)=a(l+l2,c+c2); j++; }

predicao=ind.predict(feature,Mat(),Range::all(),false,true); if (predicao<=limiar) neg++; else if (predicao>limiar) pos++; else erro("Erro: Saida inesperada"); } if (pos+neg==1) printf("Predicao=%-10g ",predicao); printf("pos=%4d neg=%4d\n",pos,neg); }}

32

//localiza.cpp//Traca um retangulo em torno das faces#include <cekeikon.h>FLT limiar=0;

int main(int argc, char** argv){ if (argc<2) { printf("Localiza nome1.pgm nome2.pgm...\n"); printf(" Saidas: _nome1.pgm _nome2.pgm...\n"); erro("Erro: Numero de argumentos invalido"); } vector<string> vs; for (int i=1; i<argc; i++) vsWildCard(argv[i],vs); CvBoost ind; ind.load("criterio.xml");

int nfeatures=30*25; Mat_<FLT> feature(1,nfeatures); Mat_<FLT> saida(1,1); Mat_<GRY> ori; Mat_<COR> sai; Mat_<FLT> flt; for (unsigned i=0; i<vs.size(); i++) { printf("Lendo arquivo %12s. ",vs[i].c_str()); le(ori,vs[i]); //assegura(ori.rows>=30 && ori.cols>=25,"Erro imagem muito pequena"); converte(ori,flt); converte(ori,sai);

for (int l=0; l<flt.rows-30+1; l++) for (int c=0; c<flt.cols-25+1; c++) { int j=0; for (int l2=0; l2<30; l2++) for (int c2=0; c2<25; c2++) { feature(j)=flt(l+l2,c+c2); j++; } FLT predicao=ind.predict(feature,Mat(),Range::all(),false,true); if (predicao>limiar) { retang(sai,l,c,l+30,c+25,COR(0,0,255)); retang(sai,l+14,c+12,l+15,c+14,COR(0,255,0)); } } string nomedir=diretorio(vs[i]); string nomearq=semDiret(vs[i]); string nomesuf=sufixo(nomearq); string nomesem=semSufixo(nomearq); string nomesai="_"+nomesem+".png"; printf("Imprimindo %12s.\n",nomesai.c_str()); imp(sai,nomesai); }}

33

OpenCV vem com vários "classifcadores em cascata" pré-treinados para detectar faces e ou-tras partes do corpo humano:../cekeikon5/opencv2410/sources/data/haarcascades

1,1M haarcascade_eye_tree_eyeglasses.xml 495K haarcascade_eye.xml 818K haarcascade_frontalface_alt2.xml 3,5M haarcascade_frontalface_alt_tree.xml 899K haarcascade_frontalface_alt.xml 1,2M haarcascade_frontalface_default.xml 622K haarcascade_fullbody.xml 316K haarcascade_lefteye_2splits.xml 520K haarcascade_lowerbody.xml 350K haarcascade_mcs_eyepair_big.xml 401K haarcascade_mcs_eyepair_small.xml 306K haarcascade_mcs_leftear.xml 760K haarcascade_mcs_lefteye.xml 703K haarcascade_mcs_mouth.xml 1,6M haarcascade_mcs_nose.xml 318K haarcascade_mcs_rightear.xml 1,4M haarcascade_mcs_righteye.xml 1,5M haarcascade_mcs_upperbody.xml 1,1M haarcascade_profileface.xml 317K haarcascade_righteye_2splits.xml 276K haarcascade_smile.xml 1022K haarcascade_upperbody.xml

Há programas-exemplos para detecção de faces:cekeikon5/opencv2410/sources/samples/c/facedetect2.cppUtiliza: haarcascade_frontalface_alt.xml

$ facedetect2 $ facedetect2 012.jpg$ facedetect2 --scale=0.7 ~/algpi/mle_avancada/detface-pos2013-ep2/dados/q1.jpg(ate q8)

Como é que funcionam? Que features são usados? Por que são tão rápidos? Por que acertamtanto?

Viola, Paul, and Michael Jones. "Rapid object detection using a boosted cascade of simple features."Computer Vision and Pattern Recognition, 2001. CVPR 2001. Proceedings of the 2001 IEEE ComputerSociety Conference on. Vol. 1. IEEE, 2001.

34

BD de faces. Usa janela 24x24.

35

Neste problema, o treino pode ser lento. A predição deve ser ultra-rápido. Pense na detecçãode faces ao tirar foto num smartphone.

1) Usa núcleo Haar-like e fltro linear. É o mesmo conceito do matchTemplate usandoCV_TM_CCORR (correlação cruzada não-normalizada). Preto=-1. Branco=+1. Em janelas24x24, há 180.000 núcleos de Haar potenciais. Escolhe as melhores depois de testar todos eles.Características de Haar é a subtração dos pixels no retângulo branco menos os pixels do retân-gulo preto.

1, 1, 1, -1, -1, -11, 1, 1, -1, -1, -11, 1, 1, -1, -1, -11, 1, 1, -1, -1, -11, 1, 1, -1, -1, -11, 1, 1, -1, -1, -1

Núcleo "Haar-like".

2) Usa imagem integral para acelerar a aplicação do fltro linear. Apostila integral.

3) Usa boosting. Apostila aprendizagem. Cada AdaBoost (strong classifer) tem dentro váriasárvores de decisão (weak classifer). A predição de boosting gera um "grau de certeza" quepode ser usado junto com um limiar para controlar taxa de falsos positivos versus falsos negati-vos. Aqui, a taxa de falsos negativos deve ser quase zero, mesmo que a taxa de falsos positivosseja alto.

4) Usa classifcadores em cascata (cascade of classifers). O primeiro estágio utiliza "strongclassifer" de dois features, com limiar ajustado para detectar 100% das faces com taxa de fal -sos positivos de 40% no conjunto de validação.

36

Classifcadores em cascata: Cada estágio é um AdaBoost. Cada AdaBoost tem uma ou mais ár -vores de decisão dentro. Cada árvore de decisão pode usar um ou mais feature obtido pelo nú -cleo Haar-like. Os limiares são ajustados para detectar quase 100% das faces, mesmo que ataxa de falsos positivos seja alta.

Copiando do artigo [Viola and Jones, 2001]:

Each classifer in the cascade was trained with the 4916 training faces (plus their vertical mir -ror images for a total of 9832 training faces) and 10,000 non-face sub-windows (also of size 24by 24 pixels) using the Adaboost training procedure.

The complete face detection cascade has 38 stages with over 6000 features. Nevertheless thecascade structure results in fast average detection times. On a dificult dataset, (...) faces aredetected using an average of 10 feature evaluations per sub-window.

37

Traincascade e detecção de carros vistos lateralmente

Traincascade é o método de aprendizagem de máquina feito para ser muito rápido na hora de fazerpredição, mesmo que o tempo de treinamento seja longo. Este método foi usado por Viola e Jonespara detectar faces. Vamos usar este método para detectar carros, seguindo post:

https://abhishek4273.com/2014/03/16/traincascade-and-car-detection-using-opencv/

Vamos usar o programa opencv_createsamples e opencv_traincascade que já vem pronto junto comOpenCV.

::roda.batopencv_createsamples -info cars.info -num 550 -w 48 -h 24 -vec cars.vecopencv_traincascade -data data -vec cars.vec -bg bg.txt -numStages 10 -nsplits 2 -minHitRate 0.999

-maxFalseAlarmRate 0.5 -numPos 500 -numNeg 500 -w 48 -h 24 cascadedet4 data/cascade.xml TestImages c:\haebase\CarData\TestImages\*.pgmcascadedet4 data/cascade.xml TestImages_Scale c:\haebase\CarData\TestImages_Scale\*.pgm

Demorou 1 hora para treinar. A predição de 170 carros demorou 4 segundos. Estes programas sãomuito difíceis de usar. Só funcionam se escolher parâmetros precisamente escolhidos.

Alguns exemplos onde a detecção teve sucesso:

Alguns exemplos com falsos positivos e/ou falsos negativos:

38

//cascadedet4.cpp#include <cekeikon.h>

Mat_<COR> detectAndDraw( Mat_<COR> a, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale=1.0, bool tryflip=false ) { Mat_<COR> img=a.clone(); double t = 0; vector<Rect> faces, faces2; const static Scalar colors[] = { Scalar(255,0,0), Scalar(255,128,0), Scalar(255,255,0), Scalar(0,255,0), Scalar(0,128,255), Scalar(0,255,255), Scalar(0,0,255), Scalar(255,0,255) }; Mat gray, smallImg;

cvtColor( img, gray, COLOR_BGR2GRAY ); double fx = 1 / scale; resize( gray, smallImg, Size(), fx, fx, INTER_LINEAR ); equalizeHist( smallImg, smallImg );

t = (double)cvGetTickCount(); cascade.detectMultiScale( smallImg, faces, 1.1, 2, 0 //|CASCADE_FIND_BIGGEST_OBJECT //|CASCADE_DO_ROUGH_SEARCH |CASCADE_SCALE_IMAGE, Size(30, 30) ); if( tryflip ) { flip(smallImg, smallImg, 1); cascade.detectMultiScale( smallImg, faces2, 1.1, 2, 0 //|CASCADE_FIND_BIGGEST_OBJECT //|CASCADE_DO_ROUGH_SEARCH |CASCADE_SCALE_IMAGE, Size(30, 30) ); for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); r++ ) { faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height)); } } t = (double)cvGetTickCount() - t; printf( "detection time = %g ms\n", t/((double)cvGetTickFrequency()*1000.) ); for ( size_t i = 0; i < faces.size(); i++ ) { Rect r = faces[i]; Mat smallImgROI; vector<Rect> nestedObjects; Point center; Scalar color = colors[i%8]; int radius;

double aspect_ratio = (double)r.width/r.height; if( 0.75 < aspect_ratio && aspect_ratio < 1.3 ) { center.x = cvRound((r.x + r.width*0.5)*scale); center.y = cvRound((r.y + r.height*0.5)*scale); radius = cvRound((r.width + r.height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } else rectangle( img, cvPoint(cvRound(r.x*scale), cvRound(r.y*scale)), cvPoint(cvRound((r.x + r.width-1)*scale), cvRound((r.y + r.height-1)*scale)), color, 3, 8, 0); if( nestedCascade.empty() ) continue; smallImgROI = smallImg( r ); nestedCascade.detectMultiScale( smallImgROI, nestedObjects, 1.1, 2, 0 //|CASCADE_FIND_BIGGEST_OBJECT //|CASCADE_DO_ROUGH_SEARCH //|CASCADE_DO_CANNY_PRUNING |CASCADE_SCALE_IMAGE, Size(30, 30) ); for ( size_t j = 0; j < nestedObjects.size(); j++ ) { Rect nr = nestedObjects[j]; center.x = cvRound((r.x + nr.x + nr.width*0.5)*scale); center.y = cvRound((r.y + nr.y + nr.height*0.5)*scale); radius = cvRound((nr.width + nr.height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } } return img;}

int main(int argc, const char** argv) { if (argc<4) {

39

printf("cascadeDet4 cascade.xml saidir ent0.pgm ent1.pgm ...\n"); printf(" Ex: cascadeDet4 cascade.xml ../saidir ../entdir/img*.pgm\n"); printf(" Imagens de saida serao em ../saidir com sufixo .png\n"); erro("Erro: Numero de argumentos invalido"); } CascadeClassifier cascade,nestedCascade; if (!cascade.load(argv[1])) erro("ERRO: Could not load classifier cascade ",argv[1]); for (int i=3; i<argc; i++) { cout << "Processando " << argv[i] << endl; Mat_<COR> a; le(a,argv[i]); Mat_<COR> b=detectAndDraw(a, cascade, nestedCascade); imp(b,string(argv[2])+"/"+semDiretSufixo(argv[i])+".png"); }}

40