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

Voltar para o blog
·10 min·Israel Oriente

Como ler placa de carro em Node.js em 2026

Guia prático para integrar OCR de placa veicular em Node.js. Comparativo de bibliotecas, código pronto com fetch e Express, e quanto custa rodar em produção no Brasil.

nodejsocralprtutorialjavascript

Reconhecer a placa de um carro a partir de uma foto dentro de uma aplicação Node.js parece simples até você esbarrar em três coisas: (1) Tesseract via wrapper consome muita memória, (2) modelos próprios em ONNX exigem GPU pra latência aceitável, (3) qualquer solução open-source out-of-the-box ignora completamente o padrão Mercosul brasileiro.

Este post é o guia que eu queria ter encontrado quando comecei a integrar leitura de placa em Node.js no Brasil. Vou cobrir as três abordagens viáveis em 2026, com código pronto pra rodar, comparação de precisão, e o custo real de cada uma.

TL;DR

  • tesseract.js (Tesseract via WASM): zero custo de servidor, ~70% de precisão depois de muito tuning. Bom pra protótipo, ruim pra produção.
  • onnxruntime-node com modelo YOLOv8 + OCR: 90% de precisão, mas você vai gastar pelo menos uma semana montando a pipeline e depois pagar GPU em produção.
  • API gerenciada via fetch (leituradeplaca, Plate Recognizer, etc.): 95%+ de precisão, sem infra, custa entre R$ 0,003 e R$ 0,80 por leitura. Pra entregar produto, é o caminho.

Se você quer ir direto pro código com a API, pula pra abordagem 3.

O problema na prática

A placa veicular brasileira tem dois formatos:

  • Padrão antigo: ABC1234 — 3 letras + 4 dígitos
  • Mercosul (a partir de 2018): ABC1D34 — 3 letras + 1 dígito + 1 letra + 2 dígitos

A pipeline de OCR precisa distinguir os dois padrões e normalizar a saída. E precisa lidar com:

  • Iluminação variável (foto à noite, foto contra o sol)
  • Ângulo da câmera (raramente perpendicular)
  • Resolução baixa (foto de celular comprimida pelo WhatsApp)
  • Reflexo de farol e sujeira na placa
  • Partes ocluídas pela traseira de outro veículo

Em Node.js especificamente, o desafio extra é que o ecossistema de visão computacional é mais magro do que em Python. As ferramentas existem, mas são menos maduras e menos documentadas em PT-BR.

Abordagem 1: tesseract.js (gratuito, baixa precisão)

tesseract.js é o port WASM do Tesseract. Roda 100% no Node sem dependências nativas.

npm install tesseract.js sharp
import Tesseract from 'tesseract.js'
import sharp from 'sharp'

async function readPlateTesseract(imagePath) {
  // Pré-processamento essencial: grayscale, contraste, threshold
  const buffer = await sharp(imagePath)
    .greyscale()
    .normalize()
    .threshold(128)
    .toBuffer()

  const { data } = await Tesseract.recognize(buffer, 'eng', {
    tessedit_char_whitelist: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
  })

  // Limpa whitespace e extrai grupos plausíveis de 7 caracteres
  const clean = data.text.replace(/\s+/g, '').toUpperCase()
  const match = clean.match(/[A-Z]{3}\d[A-Z0-9]\d{2}/) // Mercosul ou antigo
  return match ? match[0] : null
}

Precisão real: em fotos de celular bem enquadradas, ~70%. Em fotos de câmera de segurança real (ângulo, baixa luz), cai pra ~40%. Você vai precisar de muito tuning de threshold por ambiente.

Custo: zero (CPU only).

Quando faz sentido: protótipo de fim de semana, hackathon, prova de conceito interna. Não use em produção.

Abordagem 2: onnxruntime-node + modelo customizado

Se você quer rodar inferência local com qualidade, o caminho moderno é exportar um modelo treinado (YOLOv8 pra detecção de placa + um OCR head próprio) pra ONNX e rodar com onnxruntime-node.

npm install onnxruntime-node sharp
import * as ort from 'onnxruntime-node'
import sharp from 'sharp'

const detector = await ort.InferenceSession.create('./yolov8-plate.onnx')
const recognizer = await ort.InferenceSession.create('./crnn-plate-br.onnx')

async function readPlateONNX(imagePath) {
  // 1. Detecta a região da placa
  const inputTensor = await preprocessForYolo(imagePath)
  const detectorOut = await detector.run({ images: inputTensor })
  const bbox = parseYoloOutput(detectorOut.output0)

  if (!bbox) return null

  // 2. Recorta e passa para o reconhecedor
  const plateCrop = await sharp(imagePath)
    .extract({ left: bbox.x, top: bbox.y, width: bbox.w, height: bbox.h })
    .resize(160, 32)
    .toBuffer()

  const ocrTensor = bufferToTensor(plateCrop, [1, 1, 32, 160])
  const ocrOut = await recognizer.run({ input: ocrTensor })
  return decodeCTC(ocrOut.output)
}

O problema: as funções preprocessForYolo, parseYoloOutput, bufferToTensor, decodeCTC são ~300 linhas de código que você precisa escrever (ou copiar de um repo desatualizado). E o modelo crnn-plate-br.onnx precisa ser treinado por você num dataset brasileiro — os modelos prontos do GitHub geralmente foram treinados em placas EU/US e erram feio em Mercosul.

Precisão real: com modelo bem treinado, ~90%. Mas chegar nesses 90% leva pelo menos 2 semanas de trabalho.

Custo: uma máquina com GPU custa ~R$ 600/mês na DigitalOcean. Sem GPU, latência sobe pra 4-6 segundos por imagem em CPU decente.

Quando faz sentido: você tem volume gigante (>500k leituras/mês), tem ML engineer no time, e o custo unitário da API começa a doer. Abaixo disso, é prejuízo de tempo.

Abordagem 3: API gerenciada {#abordagem-3-api-gerenciada}

Aqui é onde 95% dos projetos Node.js que precisam de leitura de placa terminam — e por bom motivo: você integra em 5 minutos, não mantém infra, e a precisão é maior que qualquer modelo open-source out-of-the-box.

Exemplo com leituradeplaca + fetch nativo

import { readFileSync } from 'node:fs'

async function readPlate(imagePath) {
  const imageBuffer = readFileSync(imagePath)
  const base64 = imageBuffer.toString('base64')

  const res = await fetch('https://leituradeplaca.com.br/api/v1/read-plate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.LDP_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ image_base64: base64 }),
  })

  if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`)
  return res.json()
}

// Uso
const result = await readPlate('./carro.jpg')
console.log(result)
// → { plate: 'INO1102', confidence: 0.94, error: null, latency_ms: 2410 }

Sem SDK, sem dependência. fetch é nativo no Node 18+.

Variante com multipart (arquivo direto, sem base64)

Se você está recebendo upload via Express/Fastify e quer evitar o overhead do base64:

import express from 'express'
import multer from 'multer'

const app = express()
const upload = multer({ storage: multer.memoryStorage() })

app.post('/leitura', upload.single('foto'), async (req, res) => {
  const formData = new FormData()
  formData.append('image', new Blob([req.file.buffer]), req.file.originalname)

  const apiRes = await fetch('https://leituradeplaca.com.br/api/v1/read-plate', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${process.env.LDP_API_KEY}` },
    body: formData,
  })

  res.json(await apiRes.json())
})

app.listen(3000)

Tratamento de erros e retry

Em produção, sempre encapsule em retry com backoff exponencial — qualquer API HTTP vai ter um 502 ocasional:

async function readPlateWithRetry(imagePath, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await readPlate(imagePath)
    } catch (err) {
      if (attempt === maxAttempts) throw err
      await new Promise(r => setTimeout(r, 2 ** attempt * 200))
    }
  }
}

Precisão real (leituradeplaca): 95%+ em fotos com placa visível, mesmo com ângulo torto e baixa luz. Suporta Mercosul e padrão antigo nativamente.

Latência: entre 1.000 e 4.200 ms. Se a sua aplicação não pode bloquear, processa numa fila (BullMQ, SQS) e responde via webhook.

Custo: R$ 0,05 por leitura no plano Trial (100 leituras = R$ 4,99), descendo até R$ 0,003/leitura no Diamond (150k/mês). Pra contexto: o equivalente self-hosted custa R$ 600+/mês de GPU antes de bater na precisão equivalente.

Comparativo

Critériotesseract.jsONNX próprioAPI gerenciada
Precisão (real)~70%~90% (com treino)95%+
Latência800-2000 ms200-800 ms (GPU) / 4-6s (CPU)1000-4200 ms
Custo R$/1000 leituras0 (só CPU)~R$ 50 (GPU amortizada)R$ 6 a R$ 50
Tempo até produção1 dia2-4 semanas1 hora
ManutençãoTuning constanteRetreino, monitor de driftZero
Mercosul out-of-the-boxNãoNão (precisa treinar)Sim

Qual escolher

  • Pra prototipar / brincar: tesseract.js. Você descobre rápido o que funciona e o que não.
  • Pra produção até ~50k leituras/mês: API gerenciada. Custo previsível, sem dor de cabeça operacional.
  • Pra produção acima de 500k leituras/mês com ML engineer disponível: considere ONNX self-hosted, mas faça as contas honestas — incluindo o tempo de quem mantém.

Próximos passos

Se você quer testar a API agora, crie uma conta em leituradeplaca.com.br/signup e ganhe 100 leituras de trial por R$ 4,99. A documentação completa está em /docs com exemplos em Node.js, Python, PHP e cURL.

Para pegar o código de exemplo deste post como repositório completo (com testes e CI), me avisa em contato@leituradeplaca.com.br — posso publicar como referência.

Pronto para integrar?

Crie uma conta e ganhe acesso à API em menos de 60 segundos.