Como ensinar um telefone a ver a beleza

imagem

Recentemente, li um livro sobre matemática e a beleza das pessoas e pensei sobre o que há uma década atrás, a idéia de como entender o que a beleza humana era bastante primitiva. O raciocínio sobre qual rosto é considerado bonito do ponto de vista da matemática se resumiu ao fato de que deveria ser simétrico. Além disso, desde o Renascimento, houve tentativas de descrever rostos bonitos usando as relações entre distâncias em alguns pontos do rosto e mostrar, por exemplo, que rostos bonitos têm algum tipo de relacionamento próximo à proporção áurea. Ideias semelhantes sobre a localização dos pontos agora são usadas como um dos métodos para identificar faces (pesquisa de pontos de referência de face). No entanto, como mostra a experiência, se você não limitar o conjunto de sinais à posição de pontos específicos no rosto, poderá obter melhores resultados em várias tarefas, incluindo determinar idade, sexo ou mesmo orientação sexual . Já é evidente aqui que a questão da ética de publicar os resultados de tais estudos pode ser aguda.

O tema da beleza das pessoas e sua avaliação também pode ser eticamente controverso. Ao desenvolver o aplicativo, muitos dos meus amigos se recusaram a usar suas fotos para testes ou simplesmente não queriam saber o resultado (é engraçado que a maioria das meninas se recusasse a saber os resultados). Além disso, o objetivo de automatizar a avaliação da beleza pode levantar questões filosóficas interessantes. Até que ponto o conceito de beleza é determinado pela cultura? Quão verdadeira é a “beleza nos olhos de quem vê”? É possível destacar sinais objetivos de beleza?

Para responder a essas perguntas, você precisa estudar as estatísticas sobre as classificações de algumas pessoas por outras. Tentei projetar e treinar um modelo de rede neural que avaliasse a beleza, além de executá-lo em um telefone Android.

Parte 0. Pipeline


Para entender como as próximas etapas estão relacionadas, desenhei um diagrama do projeto:

imagem

Azul - bibliotecas importantes e dados externos. Amarelo - controla no aplicativo.

Parte 1. Python


Como a avaliação da beleza é um tópico bastante delicado, não há muitos conjuntos de dados no domínio público que contenham fotos com uma avaliação (tenho certeza de que serviços de namoro online como o tinder têm conjuntos de estatísticas muito maiores). Encontrei um banco de dados compilado em uma das universidades da China, contendo 5500 fotografias, cada uma avaliada por 7 avaliadores dentre estudantes chineses. Das 5.500 fotografias, 2.000 são homens asiáticos (AM), 2000 são mulheres asiáticas (AF) e 750 homens Europioid (CM) e mulheres (CF) cada.

imagem

Vamos ler os dados usando o módulo pandas Python e dar uma olhada rápida nos dados. Distribuição estimada para diferentes gêneros e raças:

import pandas as pd import matplotlib.pyplot as plt ratingDS=pd.read_excel('../input/faces-scut/scut-fbp5500_v2/SCUT-FBP5500_v2/All_Ratings.xlsx') Answer=ratingDS.groupby('Filename').mean()['Rating'] ratingDS['race']=ratingDS['Filename'].apply(lambda x:x[:2]) fig, ax = plt.subplots(2, 2, sharex='col') for i, race in enumerate(['CF','CM','AF','AM']): sbp=ax[i%2,i//2] ratingDS[ratingDS['race']==race].groupby('Filename')['Rating'].mean().hist(alpha=0.5, bins=20,label=race,grid=False,rwidth=0.9,ax=sbp) sbp.set_title(race) 

imagem

Percebe-se que, em geral, os homens são considerados menos bonitos que as mulheres, a distribuição é bimodal - existem. que são considerados bonitos e "médios". Quase não existem classificações baixas, portanto os dados podem ser renormalizados. Mas vamos deixá-los por enquanto.

Vejamos o desvio padrão nas estimativas:

 ratingDS.groupby('Filename')['Rating'].std().mean() 

É 0,64, o que significa que a diferença nas avaliações de diferentes avaliadores é inferior a 1 ponto em 5, o que indica unanimidade nas avaliações de beleza. Pode-se dizer razoavelmente que "a beleza NÃO está nos olhos de quem vê". Ao calcular a média, você pode usar os dados de maneira confiável para treinar o modelo e não se preocupar com a impossibilidade fundamental da avaliação programática.

No entanto, apesar do pequeno valor do desvio padrão da estimativa, a opinião de alguns avaliadores pode ser muito diferente da "comum". Vamos construir a distribuição da diferença entre a estimativa e a mediana:

 R2=ratingDS.join(ratingDS.groupby('Filename')['Rating'].median(), on='Filename', how='inner',rsuffix =' median') R2['ratingdiff']=(R2['Rating median']-R2['Rating']).astype(int) print(set(R2['ratingdiff'])) R2['ratingdiff'].hist(label='difference of raings',bins=[-3.5,-2.5,-1.5,-0.5,0.5,1.5,2.5,3.5,4.5],grid=False,rwidth=0.5) 

imagem

Um padrão interessante é encontrado. Pessoas cuja pontuação difere da mediana em mais de 1 ponto

 len(R2[R2['ratingdiff'].abs()>1])/len(R2) 

0.02943333333333333332
Menos de 3%. Ou seja, a impressionante unanimidade é novamente confirmada em questões de avaliação da beleza.
Crie uma tabela com as classificações médias necessárias

 Answer=ratingDS.groupby('Filename').mean()['Rating'] 

Nosso banco de dados é pequeno; Além disso, todas as fotos contêm principalmente imagens de rosto inteiro e eu gostaria de obter um resultado confiável para qualquer posição do rosto. Para resolver problemas com uma pequena quantidade de dados, a técnica de aprendizado de transferência é frequentemente usada - o uso de modelos pré-treinados para tarefas semelhantes e sua modificação. Perto da minha tarefa está a tarefa de reconhecimento de rosto. Geralmente é resolvido de uma maneira de três estágios.

1. Há uma detecção de rosto na imagem e em sua escala.

2. Usando uma rede neural convolucional, a imagem da face é convertida em um vetor de característica, e as propriedades de tal transformação são tais que a transformação é invariável em relação à rotação da face e à mudança no penteado. manifestações de emoções e quaisquer imagens temporárias. Aprender essa rede é uma tarefa interessante por si só, que pode ser escrita por um longo tempo. Além disso, novos desenvolvimentos estão constantemente aparecendo para melhorar essa conversão e melhorar os algoritmos de rastreamento e identificação em massa. Eles otimizam a arquitetura de rede e o método de treinamento (por exemplo, perda tripla - perda de face-arcface).

3. Comparação do vetor de recurso com os armazenados no banco de dados.

Para nossa tarefa, usei soluções prontas de 1 a 2 pontos. A tarefa de detectar rostos geralmente é resolvida de várias maneiras; além disso, quase qualquer dispositivo móvel possui detectores de rostos (no Android, eles fazem parte do pacote de serviços padrão do GooglePlay), que são usados ​​para focar nos rostos ao fotografar. Quanto à tradução de pessoas em forma vetorial, há um ponto sutil não óbvio. O fato é que os sinais. extraídos para resolver o problema de reconhecimento - são característicos de uma pessoa, mas podem não se correlacionar com a beleza. além disso. devido às peculiaridades das redes neurais convolucionais, esses sinais são principalmente locais e, em geral, isso pode causar muitos problemas (ataque de pixel único). No entanto, descobri que os resultados são altamente dependentes da dimensão do vetor e, se 128 sinais não forem suficientes para determinar a beleza, 512 serão suficientes. Com base nisso, foi escolhida uma rede insightFace pré-treinada e baseada em Reset . Também usaremos keras como uma estrutura para aprendizado de máquina.
Um código detalhado para o download de modelos pré-treinados pode ser encontrado aqui.

 model=LResNet100E_IR() 

O detector de rosto mtcnn foi usado como um detector de rosto para pré-processamento .

 detector = MtcnnDetector(model_folder=mtcnn_path, ctx=ctx, num_worker=1, accurate_landmark = True, threshold=det_threshold) 

Alinhar, cortar e vetorizar imagens do conjunto de dados:

 imgpath='../input/faces-scut/scut-fbp5500_v2/SCUT-FBP5500_v2/Images/' #    facevecs=[] for name in tqdm.tqdm(Answer.index): #   img1 = cv2.imread(imgpath+name) # ,     pre1 = np.moveaxis(get_input(detector,img1),0,-1) #  vec = model.predict(np.stack([pre1])) #   facevecs.append(vec) 

Prepararemos os dados dividindo-os em vetores de treinamento (90% deles, estudaremos sobre eles) e validação (verificaremos o trabalho do modelo neles). Normalizamos os dados para um intervalo de 0-1.

 X=np.stack(facevecs)[:,0,:] Y=(Answer[:])/5 Indicies=np.arange(len(Answer)) X,Y,Indicies=sklearn.utils.shuffle(X,Y,Indicies) Xtrain=X[:int(len(facevecs)*0.9)] Ytrain=Y[:int(len(facevecs)*0.9)] Indtrain=Indicies[:int(len(facevecs)*0.9)] Xval=X[int(len(facevecs)*0.9):] Yval=Y[int(len(facevecs)*0.9):] Indval=Indicies[int(len(facevecs)*0.9):] 

Agora vamos para o modelo. descrevendo a beleza.

 def Createheadmodel(): inp=keras.layers.Input((512,)) x=keras.layers.Dense(32,activation='elu')(inp) x=keras.layers.Dropout(0.1)(x) out=keras.layers.Dense(1,activation='hard_sigmoid',use_bias=False,kernel_initializer=keras.initializers.Ones())(x) model=keras.models.Model(input=inp,output=out) model.layers[-1].trainable=False model.compile(optimizer=keras.optimizers.Adam(lr=0.0001), loss='mse') return model modelhead=Createheadmodel() 

Este modelo é uma rede neural totalmente conectada de camada única com 32 neurônios e 512 nós de entrada - uma das arquiteturas mais simples, que, no entanto, é bem treinada:

 hist=modelhead.fit(Xtrain,Ytrain, epochs=4000, batch_size=5000, validation_data=(Xval,Yval) ) 

4950/4950 [===============================] - 0s 3us / passo - perda: 0.0069 - val_loss: 0.0071
Vamos construir curvas de aprendizado

 plt.plot(hist.history['loss'][100:], label='loss') plt.plot(hist.history['val_loss'][100:],label='validation_loss') plt.legend(bbox_to_anchor=(0.95, 0.95), loc='upper right', borderaxespad=0.) 

Vemos que a perda (desvio quadrado médio) é de 0,0071 nos dados de validação, portanto o desvio padrão = 0,084 ou 0,42 pontos em uma escala de cinco pontos, que é menor que o spread nas estimativas dadas pelas pessoas (0,6 pontos). Nosso modelo está funcionando.

Para visualizar como o modelo funciona, você pode usar o diagrama de dispersão - para cada foto dos dados de validação, construímos um ponto em que uma das coordenadas corresponde à classificação média da face e a segunda à classificação média prevista:

 Answer2=Answer.to_frame()[:5500] Answer2['ans']=0 Answer2['race']=Answer2.index Answer2['race']=Answer2['race'].apply(lambda x: x[:2]) Answer2['ans']=modelhead.predict(np.stack(facevecs)[:,0,:])*5 xy=np.array(Answer2.iloc[Indval][['ans','Rating']]) plt.scatter(xy[:,1],xy[:,0]) 

imagem

Eixo Y - valores previstos pelo modelo, eixo X - valores médios das estimativas das pessoas. Vemos uma alta correlação (o diagrama é alongado ao longo da diagonal). Você também pode verificar nossos resultados visualmente - veja as faces de cada uma das categorias com classificações previstas de 1 a 5

 import matplotlib.image as mpimg f, axarr = plt.subplots(4,5,figsize=(10, 10)) for i, race in enumerate(['AF','CF', "AM", 'CM']): for rating in range(1,6): #axarr[i,rating-1].axis('off') axarr[i,rating-1].tick_params(# changes apply to the x-axis which='both', # both major and minor ticks are affected bottom=False, # ticks along the bottom edge are off top=False, # ticks along the top edge are off right=False, left=False, labelbottom=False, labelleft=False ) picname=(Answer2[Answer2['race']==race]['ans']-rating).abs().argmin() axarr[i,rating-1].set_xlabel(Answer2.loc[picname]['ans']) axarr[i,rating-1].imshow(mpimg.imread(imgpath+picname)) 

imagem

Vemos que o resultado da classificação por beleza parece razoável.

Agora, criaremos um modelo completo no qual enviaremos uma face para a entrada; na saída, obteremos uma classificação de 0 a 1 e a converteremos no formato tflite adequado para o telefone.

 import tensorflow as tf finmodel=Model(input=model.input, output=modelhead(model.output)) finmodel.save('finmodel.h5') converter = tf.lite.TFLiteConverter.from_keras_model_file('finmodel.h5') converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE] tflite_quant_model = converter.convert() open ("modelquant.tflite" , "wb").write(tflite_quant_model) from IPython.display import FileLink FileLink(r'modelquant.tflite') 

Este modelo recebe uma imagem de um rosto com um tamanho de 112 * 112 * 3 na entrada e, na saída, fornece um número único de 0 a 1, o que significa a beleza do rosto (embora tenhamos de lembrar que no conjunto de dados as classificações não variaram de 0 a 5, mas de 1 a 5).

Parte 2. JAVA


Vamos tentar escrever um aplicativo simples para um telefone Android. A linguagem Java é nova para mim e nunca estive envolvido no desenvolvimento para o Android; portanto, o projeto não utiliza otimização do trabalho, não usa controle de fluxo e outras coisas que exigem muito trabalho para iniciantes. Como o código java é bastante complicado, aqui darei apenas as partes mais importantes para o programa funcionar. O código completo do aplicativo está disponível aqui . O aplicativo abre uma foto, detecta e avalia um rosto usando uma rede salva anteriormente e exibe o resultado:

imagem

Do ponto de vista do desenvolvimento, as seguintes funções são importantes nele.

1. A função de carregar a rede neural do arquivo model.tflite na pasta assets no objeto intérprete

 import org.tensorflow.lite.Interpreter; Interpreter interpreter; try { interpreter=new Interpreter(loadModelFile(MainActivity.this)); Log.e("TIME", "Interpreter_started "); } catch (IOException e) { e.printStackTrace(); Log.e("TIME", "Interpreter NOT started "); } private MappedByteBuffer loadModelFile(Activity activity) throws IOException { AssetFileDescriptor fileDescriptor = activity.getAssets().openFd("model.tflite"); FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor()); FileChannel fileChannel = inputStream.getChannel(); long startOffset = fileDescriptor.getStartOffset(); long declaredLength = fileDescriptor.getDeclaredLength(); return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength); } 

2. Detectando rostos usando o módulo FaceDetector, que faz parte do pacote de biblioteca padrão do google, usando uma rede neural e exibindo os resultados.

 import com.google.android.gms.vision.face.Face; import com.google.android.gms.vision.face.FaceDetector; private void detectFace(){ //Create a Paint object for drawing with Paint myRectPaint = new Paint(); myRectPaint.setStrokeWidth(5); myRectPaint.setColor(Color.GREEN); myRectPaint.setStyle(Paint.Style.STROKE); Paint fontPaint = new Paint(); fontPaint.setStrokeWidth(3); fontPaint.setTextSize(70); fontPaint.setColor(Color.BLUE); fontPaint.setStyle(Paint.Style.FILL_AND_STROKE); //Create a Canvas object for drawing on tempBitmap = Bitmap.createBitmap(myBitmap.getWidth(), myBitmap.getHeight(), Bitmap.Config.RGB_565); Canvas tempCanvas = new Canvas(tempBitmap); tempCanvas.drawBitmap(myBitmap, 0, 0, null); //Detect the Faces FaceDetector faceDetector = new FaceDetector.Builder(getApplicationContext()).build(); Frame frame = new Frame.Builder().setBitmap(myBitmap).build(); SparseArray<Face> faces = faceDetector.detect(frame); Face face; float[][] labelProbArray = new float[1][1]; imgData.order(ByteOrder.nativeOrder()); //Draw Rectangles on the Faces if (faces.size()>0){ for (int i = 0; i < faces.size(); i++) { face = faces.valueAt(i); isFaceFound=true; float x1 = Math.max(face.getPosition().x,0); float y1 = Math.max(face.getPosition().y,0); float x2 = Math.min(x1 + face.getWidth(),frame.getBitmap().getWidth()); float y2 = Math.min(y1 + face.getHeight(),frame.getBitmap().getHeight()); Bitmap tempbitmap2 = Bitmap.createBitmap(tempBitmap, (int)x1, (int)y1, (int) (x2-x1), (int) (y2-y1)); tempbitmap2 = Bitmap.createScaledBitmap(tempbitmap2, 112, 112, true); convertBitmapToByteBuffer(tempbitmap2); interpreter.run(imgData, labelProbArray); String textToShow = String.format("%.1f", (Answer[0][0]*5-1)/4 * 10); textToShow = textToShow + "/10"; int width= tempCanvas.getWidth(); //int height=tempCanvas.getHeight(); int fontsize=Math.max(width/20,imgView.getWidth()/20); fontPaint.setTextSize(fontsize); tempCanvas.drawText(textToShow, x1, y1-10, fontPaint); tempCanvas.drawRoundRect(new RectF(x1, y1, x2, y2), 2, 2, myRectPaint) } imgView.setImageDrawable(new BitmapDrawable(getResources(),tempBitmap)); } } 

Se você quiser jogar com a classificação no seu telefone, pode fazer o download do aplicativo no mercado do GooglePlay .

Source: https://habr.com/ru/post/pt485858/


All Articles