Reconhecer placa de carro em Flutter (Android e iOS) em 2026
Tutorial completo de OCR de placa em Flutter: comparativo entre google_mlkit_text_recognition, modelo TFLite e API. Código pronto para Android e iOS.
App móvel é o caso mais comum de leitura de placa em produção: vistoria veicular, registro de entrada de oficina, app de despachante, garagem com leitura assistida pelo motorista. E Flutter é, hoje, a escolha padrão de quem quer rodar Android e iOS com um codebase só.
Este post cobre as três formas viáveis de fazer OCR de placa em Flutter em 2026, com código pronto, comparação de precisão e o trade-off de cada caminho.
TL;DR
google_mlkit_text_recognition(ML Kit on-device): zero custo de servidor, funciona offline, ~75% de precisão em placas BR sem pós-processamento. Bom pra captura assistida.- Modelo TFLite custom on-device: 90%+ de precisão, mas exige treino em dataset BR e pesa 30-60MB no app.
- API HTTP via
http/dio: 95%+ de precisão, depende de internet, custo por leitura. Caminho mais rápido pra entregar.
A maioria dos apps que vejo em produção usa API gerenciada com fallback offline em ML Kit — combina o melhor dos dois mundos. Vou mostrar como fazer.
A pipeline típica em Flutter
1. Câmera (camera ou mobile_scanner)
↓
2. Captura frame ou foto
↓
3. Pré-processamento (recorte, redimensiona)
↓
4. OCR (on-device OU API)
↓
5. Validação de padrão (regex Mercosul/antigo)
↓
6. Persistência / retorno ao usuário
O ponto crítico é o passo 4. Os outros são padrão.
Abordagem 1: ML Kit (on-device, gratuito)
O google_mlkit_text_recognition é o port Flutter do ML Kit Text Recognition do Google. Roda 100% on-device, sem API key, sem custo.
dependencies:
google_mlkit_text_recognition: ^0.13.0
image_picker: ^1.1.0
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import 'package:image_picker/image_picker.dart';
Future<String?> lerPlacaMLKit(XFile image) async {
final recognizer = TextRecognizer(script: TextRecognitionScript.latin);
final inputImage = InputImage.fromFilePath(image.path);
try {
final result = await recognizer.processImage(inputImage);
// Concatena todo texto detectado e procura padrões válidos
final allText = result.text.toUpperCase().replaceAll(RegExp(r'\s+'), '');
final mercosul = RegExp(r'[A-Z]{3}\d[A-Z]\d{2}');
final antiga = RegExp(r'[A-Z]{3}\d{4}');
return mercosul.firstMatch(allText)?.group(0)
?? antiga.firstMatch(allText)?.group(0);
} finally {
await recognizer.close();
}
}
Pontos fortes:
- Funciona offline, sem latência de rede
- Gratuito, sem limite de uso
- Não vaza imagem pra servidor (vantagem em LGPD)
Pontos fracos:
- ~75% de precisão em placa BR. Confunde
Ocom0,Icom1,Bcom8. - Não tenta isolar a região da placa — você vai pegar texto da carroceria, adesivos, sinalizações.
- iOS exige você adicionar
NSCameraUsageDescriptionnoInfo.plist.
Quando faz sentido: captura assistida onde o usuário enquadra a placa de propósito (foto manual, não automática).
Abordagem 2: modelo TFLite custom
Você treina (ou compra) um modelo TensorFlow Lite específico pra placa brasileira e roda on-device com tflite_flutter.
dependencies:
tflite_flutter: ^0.10.0
image: ^4.0.0
import 'package:tflite_flutter/tflite_flutter.dart';
import 'package:image/image.dart' as img;
class PlateRecognizer {
late final Interpreter _detector;
late final Interpreter _ocr;
Future<void> load() async {
_detector = await Interpreter.fromAsset('assets/yolov8n-plate.tflite');
_ocr = await Interpreter.fromAsset('assets/crnn-plate-br.tflite');
}
Future<String?> read(String imagePath) async {
final raw = img.decodeImage(File(imagePath).readAsBytesSync())!;
// 1. Detecção
final detectorInput = _preprocessForYolo(raw);
final detectorOutput = List.filled(1 * 84 * 8400, 0.0).reshape([1, 84, 8400]);
_detector.run(detectorInput, detectorOutput);
final bbox = _parseYolo(detectorOutput);
if (bbox == null) return null;
// 2. Crop e OCR
final crop = img.copyCrop(raw, x: bbox.x, y: bbox.y, width: bbox.w, height: bbox.h);
final ocrInput = _preprocessForCRNN(crop);
final ocrOutput = List.filled(1 * 32 * 38, 0.0).reshape([1, 32, 38]);
_ocr.run(ocrInput, ocrOutput);
return _ctcDecode(ocrOutput);
}
}
Pontos fortes:
- 90%+ de precisão se treinado bem
- Offline, baixa latência (~300ms em iPhone moderno)
- Sem custo recorrente
Pontos fracos:
- O modelo treinado pesa 30-60MB → seu APK fica gordo
- Você precisa ter o modelo treinado (e os exemplos do GitHub mais comuns são treinados em placas EU/US, não BR)
- Manutenção: drift, retraining, conversão de versões TFLite quando o pacote atualiza
Quando faz sentido: apps offline-first (frotista que entra em zona sem 4G), ou quando você já tem ML engineer pra cuidar.
Abordagem 3: API gerenciada com dio ou http
Caminho mais simples e direto pra produção.
dependencies:
dio: ^5.5.0
image_picker: ^1.1.0
flutter_dotenv: ^5.1.0
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class LeituraPlacaService {
late final Dio _dio;
LeituraPlacaService() {
_dio = Dio(BaseOptions(
baseUrl: 'https://leituradeplaca.com.br/api/v1',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Authorization': 'Bearer ${dotenv.env['LDP_API_KEY']}',
},
));
}
Future<LeituraResultado> lerImagem(String imagePath) async {
final formData = FormData.fromMap({
'image': await MultipartFile.fromFile(imagePath),
});
final response = await _dio.post('/read-plate', data: formData);
return LeituraResultado.fromJson(response.data);
}
}
class LeituraResultado {
final String? plate;
final double confidence;
final int latencyMs;
LeituraResultado({required this.plate, required this.confidence, required this.latencyMs});
factory LeituraResultado.fromJson(Map<String, dynamic> json) => LeituraResultado(
plate: json['plate'],
confidence: (json['confidence'] ?? 0).toDouble(),
latencyMs: json['latency_ms'] ?? 0,
);
}
Uso na UI:
final picker = ImagePicker();
final foto = await picker.pickImage(source: ImageSource.camera, imageQuality: 85);
if (foto == null) return;
setState(() => _carregando = true);
try {
final resultado = await _service.lerImagem(foto.path);
setState(() => _placa = resultado.plate);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erro: $e')),
);
} finally {
setState(() => _carregando = false);
}
Padrão híbrido: API + fallback offline
A combinação mais robusta na prática:
Future<String?> lerPlaca(String imagePath) async {
// Tenta API primeiro (mais precisa)
if (await _temConexao()) {
try {
final r = await _service.lerImagem(imagePath);
if (r.plate != null && r.confidence > 0.7) return r.plate;
} catch (_) {
// Fallback silencioso
}
}
// Sem internet ou API falhou: usa ML Kit on-device
return await lerPlacaMLKit(XFile(imagePath));
}
Isso cobre o "frotista entra na zona sem 4G" sem você ter que embutir um modelo TFLite de 50MB.
Pegadinhas específicas de Flutter
- iOS exige
Info.plistpermissions:NSCameraUsageDescriptioneNSPhotoLibraryUsageDescription. - Compressão antes de enviar: foto de 12MP do iPhone tem ~4-6MB. Use
imageQuality: 85noimage_pickerou comprima manualmente — leituras com >2MB ficam mais lentas sem ganho de precisão. - Orientação EXIF: Android e iOS marcam orientação no metadado. Algumas APIs não respeitam EXIF e processam a imagem de lado. A
leituradeplacalida com EXIF automaticamente, mas se for outra API, façabake_orientationantes. - Background isolates: o ML Kit e o TFLite bloqueiam a UI thread se você não usar
Isolate/compute. Pra API isso não é problema (awaitnatural).
Comparativo
| Critério | ML Kit on-device | TFLite custom | API gerenciada |
|---|---|---|---|
| Precisão (placa BR) | ~75% | ~90% (com treino) | 95%+ |
| Funciona offline | Sim | Sim | Não |
| Tamanho do app | +5MB | +30-60MB | +50KB |
| Latência | 200-500ms | 200-400ms | 1-4s |
| Custo recorrente | 0 | 0 | R$ 6-50 / 1000 |
| Mercosul out-of-the-box | Parcial | Não | Sim |
| Tempo de integração | 2h | 2-4 semanas | 1h |
Qual escolher
- App pessoal / MVP: ML Kit. Sai grátis, integra em 2 horas.
- App comercial sério: API + fallback ML Kit. É o que eu rodaria em produção.
- App offline-first crítico (frotas, fiscalização rural): TFLite custom, mas só se você tiver budget pra modelo treinado.
Próximos passos
Crie uma conta em leituradeplaca.com.br/signup e teste a API direto do Flutter com R$ 4,99 de trial. Documentação com exemplos de Flutter, Swift e Kotlin em /docs.
Pronto para integrar?
Crie uma conta e ganhe acesso à API em menos de 60 segundos.