Tutorial sobre como implantar o Edge no iOS

O que você criará

Neste tutorial, você fará o download de um modelo personalizado do TensorFlow Lite exportado do AutoML Vision Edge. Depois, você executará um app pré-desenvolvido para iOS que usará o modelo para identificar imagens de flores.

Captura de tela do produto final em dispositivo móvel
Crédito da imagem: Felipe Venâncio, "from my mother's garden" (CC BY 2.0, imagem exibida no app).

Objetivos

Neste tutorial de apresentação completo, você usará o código para fazer o seguinte:

  • Executar um modelo pré-treinado em um app para iOS usando o intérprete do TFLite.

Antes de começar

Instalar o TensorFlow

Antes de começar o tutorial, você precisa instalar vários softwares:

Se você tem uma instalação Python funcional, execute os seguintes comandos para fazer o download desse software:

pip install --upgrade  "tensorflow==1.7.*"
pip install PILLOW

Clonar o repositório Git

Usando a linha de comando, clone o repositório Git com o seguinte comando:

git clone https://github.com/googlecodelabs/tensorflow-for-poets-2

Acesse o diretório do clone local do repositório (diretório tensorflow-for-poets-2). Execute todos os exemplo de código a seguir deste diretório:

cd tensorflow-for-poets-2

Configurar o app para iOS

O app de demonstração para iOS requer várias outras ferramentas:

  1. Xcode
  2. Ferramentas de linha de comando do Xcode
  3. Cocoapods

Fazer o download do Xcode

Use este link para fazer o download do Xcode no seu computador.

Instalar as ferramentas de linha de comando do Xcode

Para instalar as ferramentas de linha de comando do Xcode, execute o comando a seguir:

xcode-select --install

Instalar o Cocoapods

O Cocoapods usa o Ruby, que é instalado por padrão no macOS.

Para instalar o Cocoapods, execute o comando a seguir:

sudo gem install cocoapods
Install TFLite Cocoapod

É necessário executar o restante deste codelab diretamente no macOS. Portanto, feche o Docker agora (use Ctrl + D).

Execute o comando a seguir para instalar o TensorFlow Lite e criar o arquivo .xcworkspace usando o Cocoapods:

pod install --project-directory=ios/tflite/

Abra o projeto com o Xcode. É possível fazer isso usando a linha de comando ou pela IU.

Para abrir o projeto por meio da linha de comando, execute o comando a seguir:

open ios/tflite/tflite_photos_example.xcworkspace

Para abrir o projeto pela IU, inicie o Xcode e selecione o botão "Open another Project".

IU do Xcode

Após abrir o projeto, navegue até o arquivo .xcworkspace, e não até .xcproject.

Executar o app original

O app é um exemplo simples que executa um modelo de reconhecimento de imagens no iOS Simulator. Ele lê a biblioteca de fotos porque o Simulator não é compatível com a entrada da câmera.

Antes de inserir o modelo personalizado, teste a versão de valor de referência do app que usa a MobileNet básica treinada nas mil categorias do ImageNet (em inglês).

Para iniciar o app no Simulator, selecione o botão de reprodução ícone de reprodução do xcode no canto superior direio da janela do Xcode.

Com o botão Next Photo, você avança pelas fotos no dispositivo.

Para adicionar imagens à biblioteca de fotos do dispositivo, basta arrastá-las e soltá-las na janela do Simulator.

O resultado exibirá anotações assim:

Captura de tela do app na execução do teste

Executar o app personalizado

Com a configuração original do app, as imagens são incluídas em uma das mil classes do ImageNet através do MobileNet padrão.

Modifique o app para que ele use o modelo treinado novamente com as categorias de imagens personalizadas.

Adicionar arquivos de modelo ao projeto

O projeto de demonstração é configurado para procurar os arquivos graph.lite e labels.txt no diretório android/tflite/app/src/main/assets/.

Para substituir esses dois arquivos por suas versões, execute o comando a seguir:

cp tf_files/optimized_graph.lite ios/tflite/data/graph.lite
cp tf_files/retrained_labels.txt ios/tflite/data/labels.txt

Executar o app

Para reiniciar o app no Simulator, selecione o botão de reprodução ícone de reprodução do xcode no canto superior direito da janela do Xcode.

Para testar as modificações, adicione arquivos de imagem do diretório flower_photos/ e receba previsões.

Os resultados serão assim:

Captura de tela do produto final em dispositivo móvel
Crédito da imagem: Felipe Venâncio, "from my mother's garden" (CC BY 2.0, imagem exibida no app).

As imagens padrão não são de flores.

Para realmente testar o modelo, adicione algumas das imagens de dados de treinamento salvas anteriormente ou faça o download das imagens de uma pesquisa do Google para usar na previsão.

Como funciona?

Agora que o app está em execução, analise o código específico do TensorFlow Lite.

Pod do TensorFlowLite

Neste app, um CocoaPod pré-compilado do TFLite é usado. O Podfile inclui o CocoaPod no projeto:

Podfile

platform :ios, '8.0'
inhibit_all_warnings!

target 'tflite_photos_example'
       pod 'TensorFlowLite'

O código de interface do TFLite está todo no arquivo CameraExampleViewController.mm.

Configurar

O primeiro bloco relevante após as importações necessárias é o método viewDidLoad:

CameraExampleViewController.mm

#include "tensorflow/contrib/lite/kernels/register.h"
#include "tensorflow/contrib/lite/model.h"
#include "tensorflow/contrib/lite/string_util.h"
#include "tensorflow/contrib/lite/tools/mutable_op_resolver.h"

...

- (void)viewDidLoad {
  [super viewDidLoad];
  labelLayers = [[NSMutableArray alloc] init];

  NSString* graph_path = FilePathForResourceName(model_file_name, model_file_type);
  model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]);
  if (!model) {
    LOG(FATAL) << "Failed to mmap model " << graph_path;
  }
  LOG(INFO) << "Loaded model " << graph_path;
  model->error_reporter();
  LOG(INFO) << "resolved reporter";

  ...

A linha principal da primeira metade do método é a model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]);. Com esse código, você cria um FlatBufferModel a partir do arquivo de gráfico.

Um FlatBuffer é uma estrutura de dados mapeável da memória. Esse é um dos recursos principais do TFLite, já que ele permite que o sistema gerencie melhor a memória usada pelo modelo. O sistema pode trocar partes do modelo de forma transparente para dentro ou fora da memória conforme necessário.

A segunda parte do método cria um intérprete do modelo, anexando implementações de operação à estrutura de dados do gráfico carregada anteriormente:

CameraExampleViewController.mm

- (void)viewDidLoad {
  ...

  tflite::ops::builtin::BuiltinOpResolver resolver;
  LoadLabels(labels_file_name, labels_file_type, &labels);

  tflite::InterpreterBuilder(*model, resolver)(&interpreter);
  if (!interpreter) {
    LOG(FATAL) << "Failed to construct interpreter";
  }
  if (interpreter->AllocateTensors() != kTfLiteOk) {
    LOG(FATAL) << "Failed to allocate tensors!";
  }

  [self attachPreviewLayer];
}

Se você tiver familiaridade com o TensorFlow em Python, isso equivale a criar um tf.Session().

Executar o modelo

Com o método UpdatePhoto, você processa todos os detalhes da busca da próxima foto, da atualização da janela de visualização e da execução do modelo na foto.

CameraExampleViewController.mm

- (void)UpdatePhoto{
  PHAsset* asset;
  if (photos==nil || photos_index >= photos.count){
    [self updatePhotosLibrary];
    photos_index=0;
  }
  if (photos.count){
    asset = photos[photos_index];
    photos_index += 1;
    input_image = [self convertImageFromAsset:asset
                                   targetSize:CGSizeMake(wanted_input_width, wanted_input_height)
                                         mode:PHImageContentModeAspectFill];
    display_image = [self convertImageFromAsset:asset
                                     targetSize:CGSizeMake(asset.pixelWidth,asset.pixelHeight)
                                           mode:PHImageContentModeAspectFit];
    [self DrawImage];
  }

  if (input_image != nil){
    image_data image = [self CGImageToPixels:input_image.CGImage];
    [self inputImageToModel:image];
    [self runModel];
  }
}

As linhas relevantes são as três últimas.

Com o método CGImageToPixels, você converte a CGImage retornada pela biblioteca de fotos do iOS em uma estrutura simples que contém os dados de largura, altura, canais e pixels.

CameraExampleViewController.h

typedef struct {
  int width;
  int height;
  int channels;
  std::vector<uint8_t> data;
} image_data;

Com o método inputImageToModel, você processa a inserção da imagem na memória do intérprete. Isso inclui o redimensionamento e o ajuste dos valores de pixel para corresponder ao que é esperado pelo modelo.

CameraExampleViewController.mm

- (void)inputImageToModel:(image_data)image{
  float* out = interpreter->typed_input_tensor<float>(0);

  const float input_mean = 127.5f;
  const float input_std = 127.5f;
  assert(image.channels >= wanted_input_channels);
  uint8_t* in = image.data.data();

  for (int y = 0; y < wanted_input_height; ++y) {
    const int in_y = (y * image.height) / wanted_input_height;
    uint8_t* in_row = in + (in_y * image.width * image.channels);
    float* out_row = out + (y * wanted_input_width * wanted_input_channels);
    for (int x = 0; x < wanted_input_width; ++x) {
      const int in_x = (x * image.width) / wanted_input_width;
      uint8_t* in_pixel = in_row + (in_x * image.channels);
      float* out_pixel = out_row + (x * wanted_input_channels);
      for (int c = 0; c < wanted_input_channels; ++c) {
        out_pixel[c] = (in_pixel[c] - input_mean) / input_std;
      }
    }
  }
}

Como o modelo tem apenas uma entrada, a linha float* out = interpreter->typed_input_tensor<float>(0); solicita ao intérprete um ponteiro para a memória na entrada 0 (zero). O restante do método processa o dimensionamento aritmético e de pixels do ponteiro para copiar os dados para essa matriz de entrada.

Por último, com o método runModel, você executa o modelo:

CameraExampleViewController.mm

- (void)runModel {
  double startTimestamp = [[NSDate new] timeIntervalSince1970];
  if (interpreter->Invoke() != kTfLiteOk) {
    LOG(FATAL) << "Failed to invoke!";
  }
  double endTimestamp = [[NSDate new] timeIntervalSince1970];
  total_latency += (endTimestamp - startTimestamp);
  total_count += 1;
  NSLog(@"Time: %.4lf, avg: %.4lf, count: %d", endTimestamp - startTimestamp,
        total_latency / total_count,  total_count);

  ...

}

Depois, runModel lê os resultados. Para isso, ele solicita ao intérprete um ponteiro para os dados de matriz da saída. A saída é uma matriz simples de flutuantes. Com o método GetTopN, você processa a extração dos cinco principais resultados usando uma fila de prioridade.

CameraExampleViewController.mm

- (void)runModel {
  ...

  const int output_size = (int)labels.size();
  const int kNumResults = 5;
  const float kThreshold = 0.1f;

  std::vector<std::pair<float, int>> top_results;

  float* output = interpreter->typed_output_tensor<float>(0);
  GetTopN(output, output_size, kNumResults, kThreshold, &top_results);

  ...
}

As próximas linhas convertem os cinco principais pares de (probability, class_id) em pares (probability, label) e, em seguida, transmitem esse resultado de forma assíncrona ao método setPredictionValues, que atualiza o relatório exibido:

CameraExampleViewController.mm

- (void)runModel {
  ...

  std::vector<std::pair<float, std::string>> newValues;
  for (const auto& result : top_results) {
    std::pair<float, std::string> item;
    item.first = result.first;
    item.second = labels[result.second];

    newValues.push_back(item);
  }
  dispatch_async(dispatch_get_main_queue(), ^(void) {
    [self setPredictionValues:newValues];
  });
}

A seguir

Você concluiu o tutorial de um app de classificação de flores do iOS usando um modelo do Edge. Você usou um modelo treinado do Tensorflow Lite para Edge para testar um app de classificação de imagens antes de modificá-las e de receber amostras de anotações. Em seguida, você examinou o código específico do TensorFlow Lite para entender a funcionalidade subjacente.

Use os recursos a seguir para saber mais sobre os modelos do TensorFlow e o AutoML Vision Edge: