Desenvolvendo um App para classificar imagens com Flutter e TensorflowLite utilizando Teachable Machine
Se você é como eu, entusiasta em Machine Learning, mas não tem muito traquejo em treinar um modelo, então vou te mostrar passo a passo como desenvolver um Aplicativo para classificação de imagens sem digitar um alinha de código no tensorflow ;)
Objetivo
Desenvolver um Aplicativo Mobile em Flutter simples para identificar e classificar Pets com rótulos de Cachorro, Gato ou pássaro.
Fundamentando nosso App:
- Entenda um pouco sobre Machine Learning;
- Obter o dataset para treino (plataforma Kaggle);
- Preparação do Modelo e exportar depois de treinado ;
- Estrutura em Flutter (UI) para ler e interpretar o modelo;
- Testando o Aplicativo ;)
1) Entenda o que é Machine Learning(ML)?
Antes de preparar o modelo precisamos entender o que é Machine Learning, de forma bem simples e resumida para sabermos o que realmente de fato estamos fazendo e porque estamos fazendo.
Machine Learning ML ou Aprendizado de Maquina, é um subcampo da Inteligência Artificial, que evoluiu com o estudo de reconhecimento de padrões e da teoria de aprendizado computacional em inteligência artificial.
Os algoritmos operam construindo um modelo a partir de Inputs amostrais a fim de fazer previsões ou decisões guiadas pelos dados ao invés de simplesmente seguindo inflexíveis e estatísticas instruções programadas.
Aprendizado de Máquina pode ser classificado em Aprendizado Não Supervisionado, Aprendizado Supervisionado e Aprendizado por Reforço. O que faremos é um Aprendizado Supervisionado onde podemos efetuar a classificação de imagens que é a premissa base desta proposta.
Os dados de treinamento são rotulados (leia etiqueta como identificação de produtos numa prateleira de supermercado) para que o algoritmo avalie as correlações. Com o aprendizado supervisionado, temos a vantagem do aproveitamento de experiências anteriores para coletar dados ou produzir saídas.
Classificação de imagens funciona da seguinte forma: um modelo de classificação de imagens é treinado para reconhecer várias classes de imagens. O TensorFlow Lite oferece modelos pré-treinados otimizados que você pode implantar em seus aplicativos móveis. A imagem a seguir mostra a saída do modelo de classificação de imagem no Android, ele dá o tipo do objeto analisado e a probabilidade, por exemplo 98.82% de ser uma banana. Então é basicamente o que vamos fazer, vamos para a parte mais prática.
2) Obter o dataset para treino
Mas, o que é dataset?
Datasets são conjuntos de dados organizados em um formato similar ao das tabelas, com linhas e colunas que contém informações sobre determinado tema, lembra daquelas planilhas em excel? pronto, facinho de associar.
Existem algumas plataformas que disponibilizam dataset para treinamento gratuitamente, uma delas é a Kaggle famosa plataforma de competição de Machine Learning que a Google adquiriu.
Como obter odataset do Kaggle? Cadastre-se no site do Kaggle https://www.kaggle.com/ ;
Após o aceite e confirmações, clique no balão do canto superior direito para acessar suas configurações. Role a página para baixo e vá até Create New API Token.
Crie uma conta no Kaggle:
- Se você ainda não tiver uma conta no Kaggle, vá para o site Kaggle e crie uma conta.
Faça login na sua conta Kaggle.
Encontre o conjunto de dados desejado:
Navegue até a página do conjunto de dados que você deseja baixar. Você pode pesquisar conjuntos de dados específicos usando a barra de pesquisa.
- Baixe o conjunto de dados:
Na página do conjunto de dados, você verá um botão “Download” ou uma opção semelhante. Clique nesse botão para baixar um arquivo compactado (geralmente um arquivo ZIP) contendo o conjunto de dados.
2. Aceite os termos de uso (se aplicável):
Alguns conjuntos de dados no Kaggle podem ter termos de uso específicos. Certifique-se de ler e aceitar os termos, se houver, antes de baixar o conjunto de dados.
3. Usando a API Kaggle (opcional):
Uma alternativa é usar a API Kaggle para baixar conjuntos de dados diretamente do terminal ou script. Para isso, você precisará configurar a API key em sua conta Kaggle e instalá-la localmente. Você pode consultar a documentação.
3) Preparar o Modelo e Exportar
Efetue o Download do modelo que você deseja treinar, há muitas opções e você pode personalizar posteriormente, no meu caso escolhi o conjunto de dados Cats and Dogs para treinar o modelo (LINK AQUI).
Chegou a hora da diversão em? Se prepare.
Treinando com o Teachable Machine
A classificação de Imagens é realizada no Teachable Machine, sendo essa uma plataforma da Google também é gratuita (pelo menos até a data de publicação deste artigo)e torna a criação de modelos de aprendizado de máquina rápida, fácil e acessível a todos.
Clique em começar
Inicie um novo Projeto de Imagem
E então escolha o Modelo de imagem padrão, que no nosso caso vamos trabalhar com mobile, lá estará toda a configuração necessária para um dispositivo mobile. No caso de você usar um microcontrolador escolha o outro modelo.
Nesta etapa daremos nomes as nossas classes e faremos a inclusão do modelo que efetuamos download do kaggle. Classe 1 rotulado como Cachorro, Classe 2 rotulada como Gato e Classe 3 Pássaro.
Prepare o modelo, aqui você pode treinar quantas classes desejar, mas tome cuidado pois no final iremos exportar esse modulo para o dispositivo mobile.
Exporte seu modelo
No flutter
Crie uma pasta de assets e coloque seu arquivo de labels e arquivo de model nela. (Aquele que foi gerado lá no Teachable Machine, é através dele que o app vai fazer o mapeamento).
Em pubspec.yaml, adicione:
name: iwdpets
description: App Machine Learning para classificaçáo de imagens.
publish_to: "none" # Remove this line if you wish to publish to pub.devversion: 1.0.0+1environment:
sdk: ">=2.15.1 <3.0.0"dependencies:
flutter:
sdk: flutter
image_picker: ^0.8.0+3
cupertino_icons: ^1.0.2
percent_indicator: ^3.0.1
tflite: ^1.1.2
intro_slider: ^4.2.1
flutter_launcher_icons: ^0.13.1flutter_launcher_icons:
android: true
ios: true
image_path: "assets/logo.png"dev_dependencies:
flutter_test:
sdk: flutter flutter_lints: ^1.0.0flutter:
uses-material-design: true # To add assets to your application, add an assets section, like this:
assets:
- assets/
- assets/images/
- assets/qual_pet_logo.png
TfLiteModel: camada de modelo
Home: classe que apresenta a IU;
Controller: todo o controle do app está distribuído nas classes ClassificationService, TfLiteService e home;
TfLiteService: estrutura de dados utilizada pelo plugin tflite.
Configure o Graddle para TensorFlowLite
Instale TensorFlow Lite no Flutter
Pra que o modelo seja reconhecido é necessário instalar
Execute este comando (instale o pacote TensorFlow Light):
TFlite Model, ele vai interpretar o label que precisamos para o mapeamento:
class TfLiteModel {
double? confidence;
int? id;
String? label;
TfLiteModel(this.confidence, this.id, this.label); TfLiteModel.fromModel(dynamic model) {
confidence = model['confidence'];
id = model['index'];
label = model['label'];
}
}
Home do nosso APP
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:iwdpets/models/tflite_model.dart';
import 'package:iwdpets/pages/sobre.dart';
import 'package:iwdpets/services/classification_service.dart';
import 'package:iwdpets/services/tflite_service.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key); @override
State<HomePage> createState() => _HomePageState();
}class _HomePageState extends State<HomePage> {
// final bool _loading = true;
File? _image;
List<TfLiteModel> _outputs = []; @override
void initState() {
super.initState();
TfLiteService.loadModel();
} @override
void dispose() {
TfLiteService.dispose();
super.dispose();
} @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('WTM Flutter + Machine Learn'),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(color: Colors.cyan),
child: Text(
'WTM Flutter ML',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
ListTile(
leading: Icon(Icons.home),
title: const Text('Home'),
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => HomePage()));
},
),
ListTile(
leading: Icon(Icons.menu_book_rounded),
title: const Text('Instruçoes de Uso'),
onTap: () {},
),
ListTile(
leading: Icon(Icons.developer_mode),
title: const Text('Sobre o App'),
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SobrePage()));
},
),
ListTile(
leading: Icon(Icons.update_sharp),
title: const Text('Versao'),
subtitle: Text("1.0.0"),
onTap: () {},
)
],
),
),
body: SafeArea(
child: Column(children: <Widget>[
_buildLogo(),
_buildResult(),
_buildImage(),
_buildButtons(),
]),
),
);
} _buildButtons() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 360,
child: ElevatedButton.icon(
onPressed: _pickImage,
icon: const Icon(Icons.camera_alt),
label: const Text('Imagem da Camera ')),
),
SizedBox(
width: 360,
child: ElevatedButton.icon(
onPressed: _pickGalleryImage,
icon: const Icon(Icons.image),
label: const Text('Imagem da Galeria ')),
),
],
);
} _buildLogo() {
return Expanded(
child: SizedBox(
width: 200,
child: Column(
children: [
const SizedBox(
height: 10,
),
Image.asset('assets/qual_pet.png'),
],
),
),
);
} _buildImage() {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 92.0),
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
border: Border.all(
color: const Color.fromARGB(255, 39, 169, 176),
width: 1,
),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: _image == null
? const Text('Pra comecar, vamos scanear uma imagem?')
: Image.file(
_image!,
fit: BoxFit.cover,
),
),
),
),
);
} _pickImage() async {
final image = await ClassificationService.pickImage();
if (image == null) {
return null;
} final outputs = await TfLiteService.classifyImage(image); setState(() {
_image = image;
_outputs = outputs;
});
} _pickGalleryImage() async {
var image = await ClassificationService.pickGalleryImage();
if (image == null) return null;
final outputsGalery = await TfLiteService.classifyImage(image); setState(() {
_image = image;
_outputs = outputsGalery;
});
} _buildResult() {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
child: _buildResultList(),
);
} _buildResultList() {
if (_outputs.isEmpty) {
return const Center(
child: Text('Qual pet?!'),
);
} return Center(
child: ListView.builder(
itemCount: _outputs.length,
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[
Text(
'${_outputs[index].label}( ${(_outputs[index].confidence! * 100.0).toStringAsFixed(1)} % )',
),
const SizedBox(
height: 10.0,
),
LinearPercentIndicator(
lineHeight: 16.0,
percent: _outputs[index].confidence!,
),
],
);
},
),
);
}
}
ClassificationService
Crie esse serviço, é bem tranquilo, ele apenas encapsula a lógica para selecionar imagens da câmera ou da galeria em métodos reutilizáveis.
import 'dart:io';
import 'package:image_picker/image_picker.dart';class ClassificationService {
static final _picker = ImagePicker(); static Future<File> pickImage() async {
var pickedFile = await _picker.pickImage(source: ImageSource.camera);
return File(pickedFile!.path);
} static Future<File> pickGalleryImage() async {
var image = await _picker.pickImage(source: ImageSource.gallery);
// if(image == null)
return File(image!.path);
}
}
TfLite_service
Como este serviço é possível carregar, executar e descartar modelos TFLite é basicamente o coração do nosso aplicativo. Nele é definido o modelo, label e numThreads.
O parâmetro
numThreads
especifica quantas threads de CPU podem ser usadas para executar a inferência do modelo. Definimos onumThreads: 1
o que limita o TensorFlow Lite a usar apenas uma thread para a inferência do modelo. Isso pode ser útil em dispositivos com recursos limitados ou quando a inferência do modelo não é a única tarefa executada.
import 'dart:async';
import 'dart:io';
import 'package:iwdpets/models/tflite_model.dart';
import 'package:tflite/tflite.dart';
class TfLiteService {
static Future loadModel() async {
await Tflite.loadModel(
model: "assets/model_unquant.tflite",
labels: "assets/labels.txt",
numThreads: 1,
);
} static void dispose() {
Tflite.close();
} static Future<List<TfLiteModel>> classifyImage(File image) async {
List<TfLiteModel> outputs = [];
var output = await Tflite.runModelOnImage(
path: image.path,
imageMean: 0.0,
imageStd: 255.0,
numResults: 2,
threshold: 0.2,
asynch: true,
); output?.forEach((value) {
final element = TfLiteModel.fromModel(value);
outputs.add(element);
}); print(outputs); return outputs;
}
}
Por último o nosso main
import 'package:flutter/material.dart';
import 'package:iwdpets/pages/home.dart';
import 'package:iwdpets/pages/intro.dart';
import 'package:iwdpets/pages/splash_screen.dart';
void main() {
runApp(MyApp());
}class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'WTM Lauro de Freitas',
theme: _buildTheme(),
home: SplashScreen(),
routes: {
'/home': (context) => const HomePage(),
},
);
} _buildTheme() {
return ThemeData(
scaffoldBackgroundColor: const Color(0xFFD9D9D9),
brightness: Brightness.light,
primaryColor: const Color(0xFFD9D9D9),
primarySwatch: Colors.cyan,
);
}
}
<script src=”https://gist.github.com/dannyserena/1a12e6f5b208c179ae3833c9626bd354.js"></script>
Referências
- https://teachablemachine.withgoogle.com/
- https://www.youtube.com/watch?v=dy1e7H8U2oo
- https://www.simplilearn.com/tutorials/machine-learning-tutorial/classification-in-machine-learning
- https://www.youtube.com/watch?v=knTjhPmW5FQ
- https://github.com/dannyserena/iwdpets