Detecção de objetos próprios para não-cientista de dados

Source: Deep Learning on Medium


Go to the profile of Italo José

Usando o Tensorflow Object detection API para treinar modelos com seu próprio dataset de forma fácil.

O que você vai poder fazer no final desse artigo

A grosso modo, no final disso tudo você basicamente vai poder pegar seu dataset, colocar nesse jupyter notebook, treinar e usar seu modelo :)

A foto acima é o resultado no exemplo que vamos brincar aqui.

WARNING

Esse artigo é mais pra fazer uma chamada pro material mesmo, mas isso aqui foi feito nas coxas, se você quer ver algo mais estruturado e bonitinho, vai pro meu jupyter notebook que lá tem os mesmo textos que aqui, só que de uma forma mais bonito e fácil de entender que o medium :)

Pra começar, vamos instalar as dependências.

!pip install pillow
!pip install lxml
!pip install Cython
!pip install jupyter
!pip install matplotlib
!pip install pandas
!pip install opencv-python
!pip install tensorflow

Baixando o Tensorflow Object detection API

Primeiro de tudo vamos baixar o repositório do tensorflow models, dentro dele há vários projetos, inclusive o de detecção de objetos que vamos usar aqui para treinar nossa rede neural para detectar nossos próprios objetos.
udo o que vamos fazer será dentro da pasta models /research/object_detection.

!git clone https://github.com/tensorflow/models/
%cd models/research/object_detection

Aqui só estamos criando algumas pastas que vamos usar mais tarde.
O comando `mkdir` cria diretórios no linux

!mkdir training
!mkdir inference_graph
!mkdir -p images/train
!mkdir -p images/test

Escolhendo nosso modelo pré-treinado.

Dentro do tensorflow zoo models nós temos alguns modelos pré-treinados que nós vamos usar para treinar suas ultimas camadas com nosso pŕoprio dataset.
Dentro desse readme.md nós temos uma lista de modelos que podemos escolher dependendo da nossa necessidade, você pode escolher qualuquer um que quiser que o procedimendo aqui nesse tutorial será o mesmo.
Para cada modelo nós temos a velocidade em milisegundos ( speed — ms ) e a precisão desse modelo, ou seja, o quão acertivo ele é (mAP — mean Average Precision ).
Para esse artigo eu vou usar o faster_rcnn_inception_v2_coco_2018_01_28 só porque eu quero mesmo :)

!wget http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_2018_01_28.tar.gz
!tar -xvzf faster_rcnn_inception_v2_coco_2018_01_28.tar.gz
!rm -rf faster_rcnn_inception_v2_coco_2018_01_28.tar.gz

Segundo a documentaçao, é importante que exportamos a variável de ambiente PYTHONPATH com o caminho dos models, reasearch e slim

import os
os.environ['PYTHONPATH'] = "{}/content/obj_detect_api/models:/content/obj_detect_api/models/research:/content/obj_detect_api/models/research/slim".format(os.environ['PYTHONPATH'])

Compilando umas paradas

Aqui temos alguns protobuffers que precisam ser compilados, lembrem-se que por ser compilado, se você mudar de máquina, precisará executar isso novamente, apenas copiar e colar os arquivos gerados por esses protbuffers não irá funcionar.
Sendo bem sincero eu não consegui executar esses caras de dentro da pasta ”/research/object_detection”, tentei de N maneiras e não funcionou, então eu simplesmente compilo eles de dentro da pasta “/research”.
Entender o que são protobuffers não é algo necessário para esse tutorial, mas se você quiser aprender mais sobre, recomendo que veja a documentação, basicamente >são estruturas de texto (como o json, xml e etc) contendo estruturas de mensagens e que você pode tranformar isso pra uma porrada de linguagem, é um pouco mais que isso, mas por hora isso é mais do que o suficiente pra você seguir esse artigo.

%cd ..
!protoc ./object_detection/protos/*.proto --python_out=.

Eu não lembro exatamente a versão necessária que o Tensroflow Objection detect API necessita, mas sei que a partir da versão 3.0 funcoina bem.

!protoc --version

depois é só instalar

!python3 setup.py build
!python3 setup.py install

Testando se a instalação está correta.

Depois te termos instalado tudo, podemos rodar alguns scripts de exemplo
do próprio Tensorflow Object detection API para verificar se tudo etá correto.

Esse código aqui não é meu, eu só copiei alguns códigos do próprio repositório
para que eu pudesse fazer algumas modificaçãoe e rodar isso no jupyter notebook.

Eu não vou explicar esse código pois ele é só pra verificar se a nossa instalação
está correta, veremos uma codificaçao similar mais pra frente.

Apenas execute essas células.

Warning: Certifique-se que você está usando uma versão do Tensorflow >= da 1.12.0

Obs: Lembre-se que você pode sair desse artigo e ir direto para o jupyter notebook, que é bem melhor:)

Se você conseguir imprimir uma imagem de um cachorro e uma praia na célula abaixo, está tudo funcionando!

Treinando o modelo com nosso pŕoprio dataset.

Primeiro de tudo vamos baixar nosso dataset.

Sobre o dataset
Nesse artigo vamos utilizar um dataset da kaggle, porém não se preocupe, no decorrer do tutorial eu te direciono para um outro artigo meu que te ensina a como criar seu próprio dataset classificado.

Pascal-VOC
Antes de falar sobre o dataset propriamente dito, quero falar só lembrar que nesse tutorial vamos utilizardatasets no padrão Pascal-VOC, esse é um formato de classificaçao onde você tem:
 — Imagens no formato jpg, jpeg, png…
 — Annotations: arquivos .xml nesse seguinte formato:

<annotation>
<folder>GeneratedData_Train</folder>
<filename>000001.png</filename>
<path>/my/path/GeneratedData_Train/000001.png</path>
<source>
<database>Unknown</database>
</source>
<size>
<width>224</width>
<height>224</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>21</name>
<pose>Frontal</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<occluded>0</occluded>
<bndbox>
<xmin>82</xmin>
<xmax>172</xmax>
<ymin>88</ymin>
<ymax>146</ymax>
</bndbox>
</object>
</annotation>

Para cada imagem, nós temos um .xml com o mesmo nome, exemplo: image001.png -> image001.xml 
Note que dentro desse .xml temos várias informações sobre nossa imagem, a localização dela, o tamanho,os objetos e suas localizações dentro dessa imagem…

O dataset que estamos usando nesse artigo é o LISA Traffic Light Datasetextraído do kaggle, esse dataset contém imagens e classificações sobre semáforos onde temos as seguintes classes:
 — go;
 — stop;
 — warning;
 — goLeft;
 — goForward;
 — stopLeft;
 — warningLeft;

Porém para que esse tutorial fique mais simples, eu modifiquei as classes para que tenhamos apenas:
 — go;
 — stop;
 — warning;

Após o dowloand, note que estamos jogando tudo para dentro da pasta …/images

%cd object_detection
!wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=15WlpBbq4EpxUxZeKEAbfI_YJABASpmFs' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=15WlpBbq4EpxUxZeKEAbfI_YJABASpmFs" -O lisa608.zip
!unzip -qq ./lisa608.zip
!mv images_output/* images/
!mv annotations_output/* images/

Para esse simples tutorial, eu não quero trabalhar com muitas classes de uma vez só, você pode fazer isso, mas para simpleficar o porcesso eu vou renomear todas as classes que tenha “Left” e “Forward” no final.

Aqui eu só divido meu dataset pra treinamento e teste e depois eu volto para a pasta object_detection

Caso suas annotations estejam no formato Pascal-VOC (como é o nosso caso) você precisará tranformá-los em csv.

Eu sei, nosso dataset original já estava em csv, mas eu tranformei ele para xml justamente para mostrar esse cenário, 
pois muitas vezes você terá seu dataset no formato Pascal-VOC

Aqui iteramos sobre cada uma das nossas pastas, train, test e validation (se nós tivessemos dados para validação) , 
extraímos os dados :

  • filename
  • width
  • height
  • class
  • xmin
  • ymin
  • xmax
  • ymax

e colocamos isso em uma linha do nosso csv.

Para usar o TF Object detection vamos precisar seguir o padrão de input do framework, o
famoso TFRecord que é um formato de amazenamento de binários próprio do TF.

Quando estamos trabalhando com MUITOOS dados, pricipalmente imagens, é importante 
trabalharmos com ela em um formato que seja leve e rápido ,uma opção é trabalhar com o
binário desse documento, que é exatamente o que o TFRecords faz, mas além disso ele é
otimizado para trabalhar com o Tensorflow pois ele foi criado para o Tensorflow, por exemplo 
quando você está trabalhando com um dataset muito grande e tenta carrega-lo na memória, 
obviamente você não vai conseguir, para isso terá que trabalhar com batches, mas com o 
TFRecords não, ele abstrai pra você a forma de carregar os dados na memória sem que você 
precise programar a divisão dos baches.

Aqui há um artigo que explica mais sobre o funcionamento do TFRecords

Para cada caso, algumas mudanças deverão ser feitas no método class_text_to_int()
note que lá temos uma estrutura lógica bem simples onde temos que retornar um inteiro dependendo 
da classe que estamos trabalhando ali no momento, a lógica aqui é muito simples, basta ir incrementando 
o retorno a cada classe diferente que você tiver no seu dataset.

Obs: você pode ver o código inteiro direto pelo jupyter notebook.

Também vamos precisar informar para o TF quais são as nossas classes e para isso vamos criar um arquivo .pbtxt com a seguinte estrutura:

item {
id: 1
name: 'stop'
}

Para cada classe nós temos um item, para cada item nós temos um id e um nome, o Id refere-se ao id que usamos no nossa TFRecords:

def class_text_to_int(row_label):
if row_label == 'stop':
return 1
elif row_label == 'warning':
return 2
elif row_label == 'go':
return 3
else:
None

lembre-se de usar os mesmo ids.

%%writefile training/labelmap.pbtxt
item {
id: 1
name: 'stop'
}
item {
id: 2
name: 'warning'
}
item {
id: 3
name: 'go'
}

Aqui vamos imprimir uma informação que vamos usar posteriormente, que é a quantidade de imagens que temos para teste.

import os
test_path = "images/test"
len_dir = len(os.listdir(test_path))
print("{} imagens inside the {}".format(len_dir, test_path))

O arquivo a seguir é um arquivo de configuração da rede neural, nele temos VÁRIOS hyper-paramêtros
que podemos e temos que modificar.

Obviamente eu não escrevi todo esse documento, mas sim copiei um arquivo de configuração do 
faster_rcnn_inception_v2_pets.config e a partir dele criei um arquivo novo com minhas respectivas
modificações, para cada arquitetura de deep learning que vocÊ usar, voce terá um arquivo de configuração 
diferente, você pode encontrar todas dentro de [...]/research/object_detection/samples/configs 
ou se prefereir, acessá-los através do github.

Dentro desses arquivos, algumas coisas devem ser alterados, como:

num_classes

faster_rcnn {
num_classes: 3

fine_tune_checkpoint

fine_tune_checkpoint: "/home/<full_path>/research/object_detection/faster_rcnn_inception_v2_coco_2018_01_28/model.ckpt"

dentro da sessão train_input_reader, altere o input_path e o label_map_path para:

tf_record_input_reader {
input_path: "/<full_path>/research/object_detection/train.record"
}
label_map_path: "/<full_path>/research/object_detection/training/labelmap.pbtxt"
}

Lembre-se que tem que ser o train.record e o labelmap.pbtxt dos dados de treinamento também!

Dentro da propriedade eval_config, mude o numero de exemples que você tem para teste (dentro da pasta
…/images/test) na propriedade num_examples:

eval_config: {
metrics_set: "coco_detection_metrics"
num_examples: 36268
}

obs: obtivemos essa informação na célula anterior.

Dentro de eval_input_reader nós temos uma estrutura semelhando, mas agora os dados modificados aqui 
são relacionados aos dados de teste:

eval_input_reader: {
tf_record_input_reader {
input_path: "/full_path/research/object_detection/test.record"
}
label_map_path: "/full_path/research/object_detection/training/labelmap.pbtxt"

Note que estou usando o test.record ao invés de train.record

Obs: lembre-se que você pode ver o código e até memso já rodar tudo isso pelo google colabotory

%cd ..
!protoc ./object_detection/protos/*.proto --python_out=.
%cd object_detection

Me desculpe por isso, mas eu estava obtendo um erro muito chato onde dizia que a lib slim não estava 
sendo encontrada, muitos diziam que o problema estava na minha váriavel de ambiente PYTHONPATH, 
mas eu virifiquei mil vezes e não encontrei o problema, então resolvi dessa maneira, copiando todo o código 
do research/slim para dentro da pasta /object_detection, se alguém identificar o que está sendo feito de errado, 
por favor, comente aqui embaixo.

!cp -a ../slim/. .

Vamos botar essa bagaça pra treinar!

%run legacy/train.py --logtostderr --train_dir=training/ --pipeline_config_path=training/faster_rcnn_inception_v2.config

Depois de treinar, note que dentro da pasta /training nós vamos ter um arquivo model.ckpt-22230 provavelmente
na sua máquina terá outro número, esse arquivo é o seu modelo salvo em uma determinada epoca do seu treinamento! 
e é com ele que vamos fazer nossas predições.

!ls training

Como nós copiamos todo a pasta /slim para a /object_detection, nós acabamos que sobre 
escrevendo o arquivo inference_graph.py, por esse motivo aqui nós recriamos o arquivo 
que antes nós tinhamos sobre escrevido só para poder executa-lo e gerar nossa inferencia.

Para saber mais sobre inferencias, recomendo esses artigos que são muito boms artigo 1 artigo 2

Vamos rodar nossa inferencia!

!python3 my_inference_graph.py \
--input_type image_tensor \
--pipeline_config_path training/faster_rcnn_inception_v2_pets.config \
--trained_checkpoint_prefix training/model.ckpt-22230 \
--output_directory ./inference_graph

Vamos baixar uma imagem de teste aqui para que possamos ver alguns resultados da nossa rede.

!wget 'http://marcusquintella.sigonline.com.br/openged/conteudos/1306/001306_59bc593899b85_Semaforos_out_2017.jpg' -O semaforo.png

Esse código é bem similar ao código de teste!

import os
import cv2
import numpy as np
import tensorflow as tf
import sys
# This is needed since the notebook is stored in the object_detection folder.
sys.path.append("..")

Vamos definir algumas constantes e alguns paths como o da imagem, do checkpoint do nosso modelo, dos nossos labels e etc.

# Import utilites
from utils import label_map_util
from utils import visualization_utils as vis_util
# Name of the directory containing the object detection module we're using
MODEL_NAME = 'inference_graph'
IMAGE_NAME = 'semaforo.png'
# Grab path to current working directory
CWD_PATH = os.getcwd()
# Path to frozen detection graph .pb file, which contains the model that is used
# for object detection.
PATH_TO_CKPT = os.path.join(CWD_PATH,MODEL_NAME,'frozen_inference_graph.pb')
# Path to label map file
PATH_TO_LABELS = os.path.join(CWD_PATH,'training','labelmap.pbtxt')
# Path to image
PATH_TO_IMAGE = os.path.join(CWD_PATH,IMAGE_NAME)
# Number of classes the object detector can identify
NUM_CLASSES = 3

aqui carregamos nossos label_maps para que possamos fazer o depada da predição do modelo, 
por exemplo, saber que o número 3 corresponde à classe “go”

label_map = label_map_util.load_labelmap(PATH_TO_LABELS)
categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=NUM_CLASSES, use_display_name=True)
category_index = label_map_util.create_category_index(categories)

Vamos carregar nosso modelo e selecionar algumas layers do nosso modelo

# Load the Tensorflow model into memory.
detection_graph = tf.Graph()
with detection_graph.as_default():
od_graph_def = tf.GraphDef()
with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid:
serialized_graph = fid.read()
od_graph_def.ParseFromString(serialized_graph)
tf.import_graph_def(od_graph_def, name='')
sess = tf.Session(graph=detection_graph)
# Define input and output tensors (i.e. data) for the object detection classifier
# Input tensor is the image
image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')
# Output tensors are the detection boxes, scores, and classes
# Each box represents a part of the image where a particular object was detected
detection_boxes = detection_graph.get_tensor_by_name('detection_boxes:0')
# Each score represents level of confidence for each of the objects.
# The score is shown on the result image, together with the class label.
detection_scores = detection_graph.get_tensor_by_name('detection_scores:0')
detection_classes = detection_graph.get_tensor_by_name('detection_classes:0')
# Number of objects detected
num_detections = detection_graph.get_tensor_by_name('num_detections:0')

Vamos colocar esse trem pra rodar de verdade?

abaixo nós carregamos a nossa imagem e executamos nosso modelo, caso você nao conheça nada do Tensorflow, 
sugiro que leia esse artigo (um artigo rápido) e leia a documentação caso queira mais detalhes de como funciona
uma sessão dentro do tensorflow.

Alguns parâmetros são configuraveis aqui, como o line_thickness que corresponde a grossura da sua linha e o 
min_score_thresh que corresponde a porcentagem de confiança que você quer ter para que o Tensoflow diga: ó, 
tem um objeto aqui com mais de X% de confiança (no nosso caso vamos usar 0.6)

Load image using OpenCV and
# expand image dimensions to have shape: [1, None, None, 3]
# i.e. a single-column array, where each item in the column has the pixel RGB value
image = cv2.imread(PATH_TO_IMAGE)
image_expanded = np.expand_dims(image, axis=0)
# Perform the actual detection by running the model with the image as input
(boxes, scores, classes, num) = sess.run(
[detection_boxes, detection_scores, detection_classes, num_detections],
feed_dict={image_tensor: image_expanded})
# Draw the results of the detection (aka 'visulaize the results')
vis_util.visualize_boxes_and_labels_on_image_array(
image,
np.squeeze(boxes),
np.squeeze(classes).astype(np.int32),
np.squeeze(scores),
category_index,
use_normalized_coordinates=True,
line_thickness=8,
min_score_thresh=0.60)

Vamos imprimir nossa imagem com sua respectiva classificação. obs: em uma aplicação real não é necessário usar o visualize_boxes_and_labels_on_image_array(), você pode usar os boxes, classes e scores de forma separada.

%matplotlib inline
plt.figure(figsize=(20,10))
plt.imshow(image)

E aqui a imagem original

image = cv2.imread(PATH_TO_IMAGE)
plt.figure(figsize=(20,10))
plt.imshow(image)

Agora você pode baixar essa infenrecia e usa-la na sua máquina, no servidor, em onde você quiser :)

!zip -r inference_graph.zip /content/obj_detect_api/models/research/object_detection/inference_graph

e enviar para o seu google drive

!pip install -U -q PyDrive
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials
# 1. Authenticate and create the PyDrive client.
auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)
model_file = drive.CreateFile({'title' : 'inference_graph.zip'})
model_file.SetContentFile('./inference_graph.zip')
model_file.Upload()
# download to google drive
drive.CreateFile({'id': model_file.get('id')})