https://a.storyblok.com/f/270183/2438073/43889a99db/whos-afraid-v2-1-001.jpeg

É um morango ou não? Classifique uma imagem com o MLKit para Android

Publicado em May 4, 2021

Tempo de leitura: 8 minutos

Não faz muito tempo, o aprendizado de máquina (ML) parecia algo mágico. Por um bom tempo, os detalhes sobre o que era e como deveria ser usado eram bastante vagos para muitos de nós.

Isso foi no passado. Voltando ao presente, um dos anúncios mais empolgantes do Google I/O 2018, para mim, foi o o MLKit. Resumidamente, o MLKit pega alguns casos de uso comuns de aprendizado de máquina e os integra em uma API bem prática.

Além disso, se seu aplicativo lida com um caso de uso específico, o MLKit permite que você crie e utilize modelos personalizados, além de oferecer recursos úteis de gerenciamento de versões.

Um dos casos de uso prontos para uso é rotulagem de imagens, também conhecida como classificação de imagens. Isso significa pegar uma imagem e detectar as entidades que ela contém, tais como: animais, frutas, atividades e assim por diante. Cada imagem inserida receberá como resultado uma lista de entidades (rótulos) com uma pontuação que representa o nível de confiança de que essa entidade está de fato presente na imagem. Esses rótulos podem ser usados para realizar ações como moderação de conteúdo, filtragem ou pesquisa.

Além disso, como os dispositivos móveis têm recursos limitados e minimizar o consumo de dados costuma ser uma vantagem, trabalhar com metadados em vez da foto inteira pode ajudar em questões de desempenho, privacidade, largura de banda, suporte offline e muito mais. Por exemplo, em um aplicativo de bate-papo, poder enviar apenas as legendas, e não a foto inteira, pode trazer muitos benefícios.

Este tutorial irá orientá-lo na criação de um aplicativo móvel capaz de receber uma imagem e detectar as entidades nela contidas. Ele é dividido em três partes:

Sem dúvida, minha comida favorita no mundo são os morangos?! Eu poderia comê-los todos os dias, o dia inteiro, e isso me deixaria muito feliz!

Vamos criar um aplicativo que receba uma imagem e, em seguida, detecte se ela contém um morango ou não!

O aplicativo que você vai criar:


O usuário seleciona uma imagem e um botão para escolher como classificá-la.

A interface do usuário passa a imagem (Bitmap objeto) para a ImageClassifier classe, que enviará a imagem para um classificador específico que foi selecionado (LocalClassifier, CloudClassifier ou CustomClassifier. Este tutorial abordará apenas os dois primeiros). Cada classificador processará a entrada, executará o modelo, processará a saída, se necessário, e, em seguida, enviará o resultado para ImageClassifier que o preparará para facilitar ao máximo a exibição na interface do usuário.


Antes de começar:

  1. Clone este projeto com o código para começar e veja a implementação passo a passo https://github.com/brittBarak/MLKitDemo

  2. Adicione o Firebase ao seu aplicativo:


  • Na guia “Geral” → na seção “Seus aplicativos”, selecione “adicionar aplicativo”.


  • Siga as etapas do tutorial do Firebase para adicionar o Firebase ao seu aplicativo.


  1. Adicione firebase-ml-vision a biblioteca ao seu aplicativo: no arquivo de nível do aplicativo build.gradle , adicione:

dependencies {
// …
implementation ‘com.google.firebase:firebase-ml-vision:17.0.0
}

Conforme mencionado, este tutorial abordará a execução de detectores tanto locais quanto baseados na nuvem. Cada um deles envolve 4 etapas:

  1. Preparação (não é trapaça :) não conta realmente como uma etapa…)

  2. Configurar o classificador

  3. Processar a entrada

  4. Processar a saída

Nota: Se você preferir acompanhar o código final, pode encontrá-lo no branch 1.run_local_model do repositório.

Executando um modelo local (no dispositivo)

A escolha de um modelo local é a opção mais leve, com suporte offline, e é gratuita. Por outro lado, ela conta com mais de 400 rótulos, o que limita a precisão — fato que devemos levar em consideração.

A interface do usuário recebe o bitmap → chama ImageClassifier.executeLocal(bitmap)ImageClassifier chama LocalClassifier.execute()

Se você preferir acompanhar o código final, acesse-o no branch 1.run_local_model.

Etapa 0: Configuração

  1. Como adicionar o detector local ao seu aplicativo, com a ajuda do Firebase MLKit:

No seu arquivo de nível de aplicativo build.gradle , adicione:

dependencies {
  // ...
  implementation 'com.google.firebase:firebase-ml-vision-image-label-model:15.0.0'
}

Opcional, mas recomendado: por padrão, o próprio modelo de ML só será baixado quando você executar o detector. Isso significa que haverá alguma latência na primeira execução, além de ser necessário acesso à rede. Para contornar isso e fazer com que o modelo de ML seja baixado assim que o aplicativo for instalado da Play Store, basta adicionar a seguinte declaração ao arquivo AndroidManifest.xml do seu aplicativo:

...

<!-- To use multiple models: android:value="label,barcode,face..." -->

Etapa 1: Configurar o classificador

Crie LocalClassifier uma classe que contenha o objeto detector:

public class LocalClassifier {
  detector = FirebaseVision.getInstance().getVisionLabelDetector();
}

Esta é a instância básica do detector. Você pode ser mais seletivo quanto ao resultado retornado e adicionar Limite de Confiança, que varia entre 0 e 1, sendo 0,5 o valor padrão.

class LocalClassifier {
  //...

  FirebaseVisionImage image;
  public void execute(Bitmap bitmap) {
    image = FirebaseVisionImage.fromBitmap(bitmap);
  }
}

Etapa 2: Processar os dados de entrada

FirebaseVisionLabelDetector sabe como trabalhar com uma entrada do tipo FirebaseVisionImage. Você pode obter uma FirebaseVisionImage instância de uma das seguintes formas:

Bitmap (é isso que vamos usar aqui), Image Uri, MediaImage (de uma fonte de mídia, por exemplo, a câmera do dispositivo), ByteArray, ou ByteBuffer.

O processamento de um Bitmap é feito assim:

public class LocalClassifier {
  //...

  public void execute(Bitmap bitmap, OnSuccessListener successListener, OnFailureListener failureListener) {
    //...
    detector.detectInImage(image)
      .addOnSuccessListener(successListener)
      .addOnFailureListener(failureListener);
  }
}

Dica: Um dos motivos pelos quais podemos querer usar um modelo local é que a execução é mais rápida; no entanto, a execução de qualquer modelo leva algum tempo. Se você usar o modelo em um aplicativo em tempo real, talvez precise dos resultados ainda mais rapidamente. Reduzir o tamanho do bitmap antes de passar para a próxima etapa pode melhorar o tempo de processamento do modelo.

Etapa 3: Executar o modelo

É aqui que a mágica acontece! ? Como o modelo leva algum tempo de processamento, devemos fazer com que ele seja executado de forma assíncrona e retorne o resultado de sucesso ou falha por meio de ouvintes.

public class LocalClassifier {
  //...

  public void execute(Bitmap bitmap, OnSuccessListener successListener, OnFailureListener failureListener) {
    //...
    detector.detectInImage(image)
      .addOnSuccessListener(successListener)
      .addOnFailureListener(failureListener);
  }
}

Etapa 4: Processar a saída

A saída de detecção é fornecida em OnSuccessListener. Eu prefiro que o OnSuccessListener seja passada para LocalClassifier a partir de ImageClassifier, que lida com a comunicação entre a interface do usuário e LocalClassifier.

A interface do usuário chama ImageClassifier.executeLocal() , que deve ter uma aparência mais ou menos assim:

Em ImageClassifier.java:

localClassifier = new LocalClassifier();

public void executeLocal(Bitmap bitmap, ClassifierCallback callback) {
  successListener = new OnSuccessListener<List>() {

  public void onSuccess(List labels) {
    processLocalResult(labels, callback, start);
  }
};

localClassifier.execute(bitmap, successListener, failureListener);

processLocalResult() apenas prepara os rótulos de saída para serem exibidos na interface do usuário.

No meu caso específico, optei por exibir os três resultados com maior probabilidade. Você pode escolher qualquer outro tipo de formato. Para completar, eis a minha implementação:

OnImageClassifier.java:

void processLocalResult(List labels, ClassifierCallback callback) {
  labels.sort(localLabelComparator);
  resultLabels.clear();
  FirebaseVisionLabel label;
  for (int i = 0; i < Math.min(3, labels.size()); ++i) {
    label = labels.get(i);
    resultLabels.add(label.getLabel() +:+ label.getConfidence());
  }
  callback.onClassified(“Local Model”, resultLabels);
}

ClassifierCallback É uma interface simples que criei para transmitir os resultados de volta à tela da interface do usuário. Também poderíamos ter usado qualquer outro método disponível para fazer isso. É uma questão de preferência.

interface ClassifierCallback {
  void onClassified(String modelTitle, List topLabels);
}

É isso aí!

Você usou seu primeiro modelo de aprendizado de máquina para classificar uma imagem! ? Foi bem simples, não foi?!

Vamos rodar o aplicativo e ver alguns resultados! Baixe o código final desta parte no repositório , no branch 1.run_local_model


Muito bom! Conseguimos alguns rótulos gerais, como “comida” ou “fruta”, que se encaixam perfeitamente na imagem. Para alguns casos de uso de aplicativos, esse modelo funciona muito bem. Ele pode ajudar a agrupar imagens, fazer uma busca e assim por diante. Mas, no nosso caso, esperamos um modelo que consiga identificar exatamente qual fruta está na foto.

Vamos tentar obter rótulos mais indicativos e precisos, utilizando o detector baseado em nuvem, que possui mais de 10.000 rótulos disponíveis:

Executar um modelo baseado em nuvem

Etapa 0: Configuração

Os modelos baseados em nuvem do MLKit pertencem à Cloud Vision API, e você precisa se certificar de que ela esteja habilitada para o seu projeto:

  1. O uso de um modelo baseado em nuvem exige pagamento a partir de uma cota de mais de 1.000 utilizações mensais. Para fins de demonstração e desenvolvimento, é improvável que você chegue perto dessa cota. No entanto, é necessário atualizar o plano do seu projeto do Firebase, para que, teoricamente, ele possa ser cobrado, se necessário. Atualize seu projeto do Spark , que é gratuito, para um Blaze , que é do tipo “pague conforme o uso” e permite que você utilize as APIs do Cloud Vision. Você pode fazer isso no console do Firebase.

  2. Ative a API do Cloud Vision na biblioteca de APIs do Cloud Console. No menu superior, selecione seu projeto do Firebase e, caso ainda não esteja ativada, clique em Ativar.


Observação: Para o desenvolvimento, essa configuração é suficiente. No entanto, antes de implantar em produção, você deve tomar algumas medidas adicionais para garantir que nenhuma chamada não autorizada seja feita com sua Account. Nesse caso, confira as instruções aqui.

Etapa 1: Configurar o classificador

Crie uma CloudClassifier classe que contenha o objeto detector:

public class CloudClassifier {
  detector = FirebaseVision.getInstance().getVisionCloudLabelDetector();
}

Isso é quase igual ao exemplo acima LocalClassifier, exceto pelo tipo do detector.

Existem algumas opções adicionais que podemos configurar no detector:

  • setMaxResults() — Por padrão, serão exibidos 10 resultados. Se precisar de mais do que isso, você precisará especificar o número. Por outro lado, ao projetar o aplicativo de demonstração, decidi apresentar apenas os três primeiros resultados. Posso definir isso aqui e tornar o cálculo um pouco mais rápido.

  • setModelType() — pode ser STABLE_MODEL ou LATEST_MODEL; o último é o padrão.

public class CloudClassifier {
  options = new FirebaseVisionCloudDetectorOptions.Builder()
  .setModelType(FirebaseVisionCloudDetectorOptions.LATEST_MODEL)
  .setMaxResults(ImageClassifier.RESULTS_TO_SHOW)
  .build();

  detector = FirebaseVision.getInstance().getVisionCloudLabelDetector(options);
}

Etapa 2: Processar os dados de entrada

Da mesma forma que LocalDetector, FirebaseVisionCloudLabelDetector usa uma entrada de FirebaseVisionImage, que obteremos de um Bitmap, para facilitar a interface do usuário;

public class CloudClassifier {
  //...
  FirebaseVisionImage image;
  public void execute(Bitmap bitmap) {
    image = FirebaseVisionImage.fromBitmap(bitmap);
  }
}

Etapa 3: Executar o modelo

Assim como nas etapas anteriores, esta etapa é extremamente semelhante ao que fizemos para executar o modelo local:

public class CloudClassifier {
  public void execute(Bitmap bitmap, OnSuccessListener successListener, OnFailureListener failureListener) {
  //...
  detector.detectInImage(image)
    .addOnSuccessListener(successListener)
    .addOnFailureListener(failureListener);
  }
}

Etapa 4: Processar a saída

Como o modelo local é diferente do modelo baseado em nuvem, seus resultados serão diferentes, de modo que o tipo de objeto que recebemos como resposta OnSuccessListener difere de acordo com o detector. No entanto, os objetos são praticamente os mesmos para fins de trabalho.

Em ImageClassifier.java:

cloudClassifier = new CloudClassifier();

public void executeCloud(Bitmap bitmap, ClassifierCallback callback) {
  successListener = new OnSuccessListener<List>() {

    public void onSuccess(List labels) {
      processCloudResult(labels, callback, start);
    }
  };
  cloudClassifier.execute(bitmap, successListener, failureListener);
}

Mais uma vez, o processamento dos resultados a serem exibidos pela interface do usuário depende da sua própria decisão sobre o que a interface deve apresentar. Neste exemplo:

processCloudResult(List labels, ClassifierCallback callback) {
  labels.sort(cloudLabelComparator);
  resultLabels.clear();
  FirebaseVisionCloudLabel label;
  for (int i = 0; i < Math.min(RESULTS_TO_SHOW, labels.size()); ++i) {
    label = labels.get(i);
    resultLabels.add(label.getLabel() + ":" + label.getConfidence());
  }
  callback.onClassified("Cloud Model", resultLabels);
}

É basicamente isso! ?

Vamos ver alguns resultados: O código desta postagem pode ser encontrado no repositório, no branch 2.run_cloud_model


Como era de se esperar, o modelo demorou um pouco mais, mas agora consegue identificar qual fruta específica está na imagem. Além disso, ele tem mais de 90% de confiança no resultado, em comparação com os 70–80% de confiança do modelo local.


Espero que isso ajude você a entender como é simples e divertido usar o Firebase MLKit. O uso dos outros modelos — detecção de rosto, leitura de código de barras etc. — funciona de maneira muito semelhante, e recomendo que você experimente!

Será que podemos obter resultados ainda melhores? Vamos explorar essa possibilidade usando também um modelo personalizado, em um próximo post.

E agora?

Se você quiser saber mais sobre o que é aprendizado de máquina e como ele funciona, confira estas postagens de blog introdutórias, voltadas para desenvolvedores: bit.ly/brittML-1, bit.ly/brittML-2, bit.ly/brittML-3

Para saber mais sobre por que usar o MLKit, consulte esta publicação e a documentação oficial

Compartilhar:

https://a.storyblok.com/f/270183/384x384/b935cb065d/brittbarak.png
Britt BarakEx-funcionários da Vonage

Britt Barak é gerente de produto na Nexmo, responsável pela Conversation API da Nexmo e pelos SDKs para clientes.