Beta · Estamos validando o produto. Pode ter instabilidades. Saiba mais

Voltar para o blog
·11 min·Israel Oriente

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.

fluttermobileandroidiosocralpr

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 O com 0, I com 1, B com 8.
  • Não tenta isolar a região da placa — você vai pegar texto da carroceria, adesivos, sinalizações.
  • iOS exige você adicionar NSCameraUsageDescription no Info.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

  1. iOS exige Info.plist permissions: NSCameraUsageDescription e NSPhotoLibraryUsageDescription.
  2. Compressão antes de enviar: foto de 12MP do iPhone tem ~4-6MB. Use imageQuality: 85 no image_picker ou comprima manualmente — leituras com >2MB ficam mais lentas sem ganho de precisão.
  3. Orientação EXIF: Android e iOS marcam orientação no metadado. Algumas APIs não respeitam EXIF e processam a imagem de lado. A leituradeplaca lida com EXIF automaticamente, mas se for outra API, faça bake_orientation antes.
  4. 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 (await natural).

Comparativo

CritérioML Kit on-deviceTFLite customAPI gerenciada
Precisão (placa BR)~75%~90% (com treino)95%+
Funciona offlineSimSimNão
Tamanho do app+5MB+30-60MB+50KB
Latência200-500ms200-400ms1-4s
Custo recorrente00R$ 6-50 / 1000
Mercosul out-of-the-boxParcialNãoSim
Tempo de integração2h2-4 semanas1h

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.