terça-feira, 16 de abril de 2019

Usando modelo quantizado ssd mobilenet v1 no exemplo Android do Tensoflow Lite para Object Detection

colocando modelo quantizado ssd mobilenet v1 no Android
1- App
https://github.com/tensorflow/examples/tree/master/lite/examples/object_detection
Baixe no seu computador, abra com o Android Studio, e teste no seu celular

2- Download do modelo (ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18):
https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
Especificamente
http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18.tar.gz


3- Convertendo
funcionou (4 outputs), QUANTIZED_UINT8 (tflite de 6,7MB): :
toco --graph_def_file=C:\tmp\my_object_detection\ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18-ModelZoo\tflite_graph.pb --output_file=C:\tmp\my_object_detection\ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18-ModelZoo\tflite\ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18.tflite --input_shapes=1,300,300,3 --input_arrays=normalized_input_image_tensor --output_arrays=TFLite_Detection_PostProcess,TFLite_Detection_PostProcess:1,TFLite_Detection_PostProcess:2,TFLite_Detection_PostProcess:3 --inference_type=QUANTIZED_UINT8 --mean_values=128 --std_dev_values=128 --change_concat_input_ranges=false --allow_custom_ops

2-Labels (opcional, pois o labelmap.txt original serve para este modelo, mas vou colocar a instrução mesmo assim)
https://github.com/nightrome/cocostuff/blob/master/labels.txt
troque ": " por ";" mude a extensão para .csv, abra no Excel, delete a primeira coluna.
salve como txt.
substitue a primeira linha de "unlabeled" por "???" (sem aspas)
delete todas linhas depois da 91, O arquivo deve ter 91 linhas e a última label é toothbrush. (São 90 objetos fora a primeira linha que tem ???)
Salve o arquivo como: labels_map_ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18.txt

3-Copie para:
C:\object_detection\app\src\main\assets
(este diretório só vai existir depois de abrir o Android Studio e sincronizar tudo)

4-Edite DetectorActivity.java para usar o modelo e as labels.

private static final String TF_OD_API_MODEL_FILE = "ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18.tflite";

Se o arquivo com labels foi trocado use isso, senão use labelmap.txt original.
private static final String TF_OD_API_LABELS_FILE = "file:///android_asset/labels_map_ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18.txt";

Para este modelo use o mesmo do exemplo para estas variáveis:
private static final int TF_OD_API_INPUT_SIZE = 300;
private static final boolean TF_OD_API_IS_QUANTIZED = true;

Transfer Learning (re-trainnig a deep learning model)

Retrain a deep learning model ssd_mobilenet_v1_0.75_depth_quantized_coco using a custom dataset.


Post based in: https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10#2-set-up-tensorflow-directory-and-anaconda-virtual-environment


Instructions using Windows 10 and TensorFlow 1.13

Preparation


First, install TensorFlow according to these instructions.

Install Object Detection, instructions here.

Donwload de models/master and copy to your machine, see in https://github.com/tensorflow/models/releases the release that you download and rename models-master to tensorflow plus version

For instance, when I write this post de release was 1.11 (it is not the TensorFlow version), so I rename models-master to:
c:\tensorflow1.11  (I will assume this name until the end of this post)

Protobuf

Download the last version of Protocol Buffers:
https://developers.google.com/protocol-buffers/docs/downloads

In my case the current version is 3.7.1, I download and copy to my machine in:
C:\Program Files (x86)\protoc-3.7.1.

From: C:\tensorflow1.11\research

Execute the command (point to the protoc.exe in your machine):
"C:\Program Files (x86)\protoc-3.7.1\bin\protoc" --python_out=. .\object_detection\protos\anchor_generator.proto .\object_detection\protos\argmax_matcher.proto .\object_detection\protos\bipartite_matcher.proto .\object_detection\protos\box_coder.proto .\object_detection\protos\box_predictor.proto .\object_detection\protos\calibration.proto .\object_detection\protos\eval.proto .\object_detection\protos\faster_rcnn.proto .\object_detection\protos\faster_rcnn_box_coder.proto .\object_detection\protos\grid_anchor_generator.proto .\object_detection\protos\hyperparams.proto .\object_detection\protos\image_resizer.proto .\object_detection\protos\input_reader.proto .\object_detection\protos\losses.proto .\object_detection\protos\matcher.proto .\object_detection\protos\mean_stddev_box_coder.proto .\object_detection\protos\model.proto .\object_detection\protos\optimizer.proto .\object_detection\protos\pipeline.proto .\object_detection\protos\post_processing.proto .\object_detection\protos\preprocessor.proto .\object_detection\protos\region_similarity_calculator.proto .\object_detection\protos\square_box_coder.proto .\object_detection\protos\ssd.proto .\object_detection\protos\ssd_anchor_generator.proto .\object_detection\protos\string_int_label_map.proto .\object_detection\protos\train.proto .\object_detection\protos\keypoint_box_coder.proto .\object_detection\protos\multiscale_anchor_generator.proto .\object_detection\protos\graph_rewriter.proto


It will generate .py files.

Environment Variables

Create a variable named PYTHONPATH
and add
C:\tensorflow1.11\models
C:\tensorflow1.11\models\research
C:\tensorflow1.11\models\research\slim

also add in PATH:
C:\tensorflow1.11\models
C:\tensorflow1.11\models\research
C:\tensorflow1.11\models\research\slim

Obs: Point to your directories, the name used here correspond to my installation.


Setup 


From: C:\tensorflow1.11\research

python setup.py build

python setup.py install

Preparing the object_detection directory

Model

Download and extract the pre-trained model:
ssd_mobilenet_v1_0.75_depth_quantized_coco
More models here.

Copy the directory ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18 to:

C:\tensorflow1.11\research\object_detection

Training directory

Create an empty training directory:

C:\tensorflow1.11\research\object_detection\training

Copy 2 files, inside this directory:

labelmap.pbtxt (see item 5a)

Copy:
C:\tensorflow1\models\research\object_detection\inference_graph_ssdlite_mobilenet_v2_coco\pipeline.config

To:
 C:\tensorflow1\models\research\object_detection\training

Rename to:
ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18.config

And edit to your directories:

Edit ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18.config
in: train_config

batch_size: 1
fine_tune_checkpoint: "C:/tensorflow1.11/research/object_detection/ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18/model.ckpt"
from_detection_checkpoint: true
eplicas_to_aggregate: 1


train_input_reader
  label_map_path: "C:/tensorflow1.11/research/object_detection/training/labelmap.pbtxt"
  input_path: "C:/tensorflow1.11/research/object_detection/train.record"


eval_config:
  num_examples: 76     

eval_input_reader
  label_map_path: "C:/tensorflow1.11/research/object_detection/training/labelmap.pbtxt"
     input_path: "C:/tensorflow1.11/research/object_detection/test.record"


Dataset

Copy:
test.record
train.record


to:
 C:\tensorflow1.11\models\research\object_detection



Retraining

From:
C:\tensorflow1.11\research

Execute
python C:\tensorflow1.11\research\object_detection\legacy\train.py --logtostderr --train_dir=object_detection\training --pipeline_config_path=object_detection\training\ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18.config


After, open another prompt, activate your Tensorflow environment and from:
C:\tensorflow1.11\research

execute:
tensorboard --logdir=training





Export Inference Graph

Create a directory:
inference_graph
inside:
C:\tensorflow1.11\research\object_detection


python C:\tensorflow1.11\research\object_detection\export_inference_graph.py --input_type image_tensor --pipeline_config_path C:\tensorflow1.11\research\object_detection\training\pipeline.config --trained_checkpoint_prefix training/model.ckpt-XXXX --output_directory C:\tensorflow1.11\research\object_detection\inference_graph

Where XXXX must be the highest number in the model.ckpt files generated.

This command will generate the: frozen_inference_graph.pb and model.ckpt files.

For TensorFlow Lite

Export TensorFlow Lite SSD Graph

Create a directory:
tflite
inside:
C:\tensorflow1.11\research\object_detection\inference_graph


python C:\tensorflow1.11\research\object_detection\export_tflite_ssd_graph.py --pipeline_config_path=C:\tensorflow1.11\research\object_detection\training\ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18.config --trained_checkpoint_prefix=C:\tensorflow1.11\research\object_detection\inference_graph\model.ckpt --output_directory=C:\tensorflow1.11\research\object_detection\inference_graph\tflite --add_postprocessing_op=true

This command will generate the: tflite_graph.pb and tflite_graph.pbtxt



toco --graph_def_file=C:\tensorflow1.11\research\object_detection\inference_graph\tflite\tflite_graph.pb --output_file=C:\tensorflow1.11\research\object_detection\inference_graph\tflite\ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18.tflite --input_shapes=1,300,300,3 --input_arrays=normalized_input_image_tensor --output_arrays=TFLite_Detection_PostProcess,TFLite_Detection_PostProcess:1,TFLite_Detection_PostProcess:2,TFLite_Detection_PostProcess:3 --inference_type=QUANTIZED_UINT8 --mean_values=128 --std_dev_values=128 --change_concat_input_ranges=false --allow_custom_ops

This command will generate the: ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18.tflite






Errors

c_api.TF_GetCode(self.status.status))
tensorflow.python.framework.errors_impl.InvalidArgumentError: NewRandomAccessFile failed to Create/Open: C:     ensorfloesearch\object_detection        raining\labelmap.pbtxt : A sintaxe do nome do arquivo, do nome do diret\udcf3rio ou do r\udcf3tulo do volume est\udce1 incorreta.
; Unknown error

In .config file write paths with / not \ , example:
C:/tensorflow1.11/research/object_detection/training/labelmap.pbtxt

not:
C:\tensorflow1\models\research\object_detection\training\labelmap.pbtxt



ValueError: not enough values to unpack (expected 7, got 0)
Edit ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18.config
in: train_config
change: replicas_to_aggregate: 8
to:replicas_to_aggregate: 1




WARNING:root:Variable [MobilenetV1/Conv2d_0/BatchNorm/beta] is not available in checkpoint
https://github.com/tensorflow/models/issues/4862


Edit ssd_mobilenet_v1_0.75_depth_quantized_300x300_coco14_sync_2018_07_18.config
in: train_config
add: from_detection_checkpoint: true

More information:

segunda-feira, 28 de setembro de 2015

Usando um Port Expander com Arduino

O MCP 23017 é um port expander, ele permite que você tenha mais entradas e saída que normalmente tem na Arduino. Por outro lado permite que você estenda a comunicação com apenas 4 fios (dois do I2C, um de alimentação e outro do terra).

As dicas aqui contidas resolveram problemas que eu encontrei, mas podem não ser a melhor forma de resolve-los, não exite em contribuir nos comentários.

Os exemplos de código consideram o uso da biblioteca da Adafruit para o MCP 23017.

É importante que alguns detalhes sejam observados:

1 - Coloque um capacitor de 1µF em paralelo com o botão (caso você use). Ative, no código, o pull-up interno do MCP23017 que é de 100KΩ, então teremos um resistor em série com o capacitor, quando o botão está aberto. E o tempo de suavização (anti-debounce) será de 0,1s (τ = RC => τ = 100K*1µ => τ = 0,1s).


O uso deste capacitor (um em paralelo para cada botão) além do efeito desejado que é a suavização durante o acionamento tem um efeito indesejado: quando o circuito é energizado o capacitor está descarregado e irá parecer para o MCP 23017 que ocorreu um acionamento do botão. Para eliminar este efeito indesejado é necessário incluir um delay de 60ms (testado empiricamente como um tempo adequado  20ms não deu certo 30ms sim, então escolhi 60ms para ter folga ) e uma leitura para limpar o buffer, no código da Arduino no setup depois da inicialização do MCP 23017.




#define button1 13
#define led 0

void setup() { 

//inicializa o MCP23017     
mcp.begin(); // use default address 0
mcp.setupInterrupts(true, false, LOW); // mirror habilitado - cada interrupcao em INTA e espelhada em INTB
//saídas (só para mostrar com se faz)    
mcp.pinMode(led, OUTPUT);//pino 0  mcp_PinA0 configurado com saída    
//entradas    
mcp.pinMode(button1, INPUT); // pino 13 mcp_PinB5 button1 configurado com entrada
mcp.pullUp(button1, HIGH); // ativa pullup interno de 100K
mcp.setupInterruptPin(button1, FALLING); // gera interrupcao quando de sai de nivel alto para nivel baixo

//tratamento para o uso dos capacitores em paralelo com os botões    
delay(60);//pequena espera antes da leitura inicial 
mcp.getLastInterruptPinValue();//leitura inicial, para limpar o buffer






2 - Cuidados no código com a interrupção

A) Quando se trabalha com Interrupções, uma boa prática é realizar o mínimo possível dentro da função  (interrupt service routine (ISR)) chamada pela interrupção, preferencialmente mude uma variável booleana e trate no loop. IMPORTANTE  está variável global e volatile, por exemplo:



volatile boolean interruptHappen = false;


Normalmente no loop você terá uma chamada que trata a interrupção, algo como:


if(interruptHappen)
    handleInterrupt();


mas ainda não implemente desta forma, veja o item C).


B) Para evitar que ocorra uma interrupção enquanto a função handleInterrupt() deve desligar a interrupção no seu inicio e ligar no final:

void handleInterrupt(){
  detachInterrupt(arduinoInterrupt);
   ....
  attachInterrupt(arduinoInterrupt,intCallBack,FALLING); }


C) É recomendável que se limite o intervalo mínimo entre uma interrupção e a próxima (depois de experimentar uma instabilidade, que travava o funcionamento do MCP 23017, eu resolvi com este intervalo mínimo, equivale a um anti debounce de software para a interrupção.


//variávei globais
volatile unsigned long button_time = 0;  //anti debounce de software na interrupção
volatile unsigned long last_button_time = 0; //anti debounce de software na interrupção
volatile int buttonInterruptInterval = 100; //intervalo mínimo entre uma interrupção e outra, evita problema de debounce
volatile uint8_t mcpPin;

...
dentro do loop:

if (interrupted)
     {
     button_time = millis(); //Anti debounce de software para a interrupção
     mcpPin=mcp.getLastInterruptPin(); //lê qual pino foi acionado
     if (((button_time - last_button_time) > buttonInterruptInterval)&&(mcpPin==button1))//somente trata se for um botão válido e em intervalos superiores a 100ms
       {
       handleInterrupt();
       last_button_time = button_time;
       }
     else mcp.getLastInterruptPinValue();//senão limpa o bus (ignora o acionamento)
     }

D) A função hanleInterrupt() deve ser a mais direta possível, não adicione código que poderia estar em outra função, veja um exemplo:



void handleInterrupt()
   {
   detachInterrupt(digitalPinToInterrupt(PIN_INTERRUPT));//desativa a interrupção para evitar multiplos acionamentos
     
   if(mcpPin==button1) {código de botão 1();} // false = desligado
     
   while( ! (mcp.digitalRead(button1) );//espera o botão ser solto
     
   updateLedsProfile();
   cleanInterrupts();
   attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPT), intCallBack, FALLING);//reativa a interrupção
   }



E) Estará sendo usada comunicação I2C, que é uma comunicação serial, logo recomendo uma implementação como a usada acima, no passo C uma variável global volatil mcpPin é carregada usando mcp.getLastInterruptPin() esta mesma variável é usada dentro do handleInterrupt() para definir a ação, ao invés de carregar novamente. A ideia é: seja simples recebeu uma interrupção identifique o botão e trate.


Informações sobre debounce podem ser encontradas em:
http://www.gammon.com.au/forum/?id=10945

Informações sobre interrupções podem ser encontradas em: https://www.arduino.cc/en/Reference/AttachInterrupt

Atenção: Quando se trabalha com delay() no loop pode ocorrer instabilidade na chamada da função através de interrupção, mesmo com acionamento LOW (por exemplo). Esta instabilidade aumenta caso a interrupção seja acionada por transição como FALLING.

segunda-feira, 20 de julho de 2015

Breakout Board para módulo HC-05 Bluetooth

O módulo Bluetooth HC-05 é barato e fácil de usar, existem vários sites com instruções.

Para se ter acesso ao pinos necessários você pode soldar fios diretamente, ou usar uma breakout board. Muitos já são vendidos soldados em uma.

Neste post eu disponibilizo o desenho de uma PCB que serve de breakout board.

Como o HC-05 se comunica com 3,3v é necessário um divisor resistivo (R1-10K R2-20K) para conectar o RX (3,3v) do módulo ao TX (5v) de uma Arduino Uno, por exemplo, já vi projetos que fazem este divisor com 1K 2K, mas eu prefiro trabalhar com 10K 20K.


Caso a intenção seja conectar o módulo a uma Arduino Due (que trabalha com 3,3v) este divisor não é necessário, logo não é necessário montar R1 e R2, mas é necessário ligar o pad 1 e 2 do JP1 para ligar diretamente o RX ao pin header SV1.


A PCB é single layer, somente o Top, como a figura abaixo;


O resultado final com o módulo soldado ficou assim:



Para testar o funcionamento eu liguei a breakout board a uma Arduino Leonardo:

Arduino Leonardo           Breakout HC-05
3,3v -------------------------- 3,3v
GND --------------------------- GND
TX ---------------------------- RX  (ATENÇÃO é TX com RX)
RX ---------------------------- TX  (ATENÇÃO é RX com TX)

O código para testar está disponível em:
https://github.com/Marchanjo/SimpleBreakoutHC-05/tree/master/Firmware/BluetoothHC05


Finalmente, basta instalar uma app de Terminal Bluetooth no seu celular Android, eu usei esta:
https://play.google.com/store/apps/details?id=ptah.apps.bluetoothterminal&hl=en


Quando o módulo Bluetooth HC-05  é alimentado (3,3v e GND) ele já aparece na procura Bluetooth do seu celular (o nome pode ser HC-05 ou linvor) e normalmente para parear se usa 1234.

Montagem:



Com a Arduino Leonardo executando o código e conectada ao módulo, quando você abrir o terminal e conectar ao HC-05 (depois de já ter pareado, normalmente a senha de pareamento é 1234), o terminal irá apresentar o texto "teste"a cada segundo, este teste indica a transmissão da Arduino para o Celular.

Agora para testar o sentido ao contrário, é necessário abrir o monitor serial da IDE da Arduino
e no Terminal do celular digitar um carácter e enviar (botão Send), este mesmo carácter irá aparecer no monitor serial da Arduino, indicando a transmissão do Celular para a Arduino.


Todos arquivos necessários para fazer a PCB e o código da Arduino estão disponíveis aqui:
https://github.com/Marchanjo/SimpleBreakoutHC-05











quarta-feira, 11 de março de 2015

Atualização do bootloader da Pro Micro

 A placa de desenvolvimento Pro Micro (compatível Arduino Leonardo)  é pequena, tem conector micro USB, tem baixo consumo de energia, em suma: ideal para de eletrônica vestível e de internet das coisas. 




Ela pode vir de fábrica com um bootloader desatualizado, que tem como característica demorar 8 segundos para executar o seu sketch depois de energizar a placa e depois do reset.

Dependendo do tipo de projeto isto é um problema, felizmente a nova versão do bootloader da Arduino Leonardo (que pode ser usado na Pro Micro) tem um comportamento um pouco diferente, mas que resolve este problema, quando você energiza a placa ela carrega seu sketch quase que imediatamente, e somente espera 8 segundos depois do reset. Mais informações aqui.

Para atualizar o bootloader em uma Arduino UNO ou Leonardo é fácil pois já existe um conector ICSP:




Neste link mais detalhes: atualização do bootloader


Mas para atualizar o bootloader da Pro Micro é necessário preparar um cabo que acesse os seguintes pinos:



Pinout da Pro Micro:


Eu resolvi soldar um cabo para que não ocorresse mal contato, mas de maneira superficial para facilitar a posterior retirada. A outra ponta deste cabo é um conector fêmea, para encaixar na no conector ICSP da USBtinyISP.




Com uma placa como a USBtinyISP (que você pode montar com as instruções deste link) é possível carregar o novo bootloader.

A posição do conector ICSP na USBtinyISP é indicada na figura abaixo:





Já com o cabo conectado é indicado o Vcc e o sinal MISO (pino1)



Agora basta abrir a IDE da Arduino (eu fiz este procedimento com a versão 1.6.0 e 1.6.1). Quando fiz no Linux (com root) funcionou perfeitamente.

Executando a IDE:

[user@localhost arduino-1.6.1]$ su root

[root@localhost arduino-1.6.1]# ./arduino 

Em Ferramentas, selecione a placa Arduino Leonardo:



Em Ferramentas, selecione o programador USBtinyISP:

Em Ferramentas, selecione Gravar Bootloader:



 irá demorar um pouco... mais ou menos um minuto para atualizar.

Gravação com sucesso:



IMPORTANTE: Tentei várias vezes este processo no Windows e sempre dava erro ao gravar o bootloader:

avrdude: verifying ...
avrdude: verification error, first mismatch at byte 0x7080
         0x00 != 0x30
avrdude: verification error; content mismatch


avrdude done.  Thank you.

Ainda não descobri a causa deste erro.