No guia da câmera térmica de celular mostramos que a TOPDON TC001 e a InfiRay P2 Pro são webcams UVC: o Linux as enxerga sem driver nenhum. Este post vai um passo além do que o aplicativo faz: gravar a matriz de temperatura bruta (192 × 256 pontos, 25 vezes por segundo) e extrair, por um script Python de 50 linhas, o ponto mais quente de cada segundo, com posição e temperatura, num CSV que você abre no gnuplot ou no Calc.
Última verificação: 15 de outubro de 2026. O formato do quadro vem do código-fonte dos projetos citados, aberto por nós nesta data; o script foi testado aqui com uma gravação sintética de demonstração no mesmo formato (não tínhamos a câmera ligada neste Linux, e dizemos isso em cada número que vem dela). Não vendemos nenhuma dessas câmeras.
O que o Linux recebe da câmera
Ligada na porta USB, a câmera aparece como um dispositivo de vídeo comum. Numa issue aberta em fevereiro de 2025 no repositório do PyThermalCamera, um usuário de Debian 12 colou o lsusb da TC001: ID 3474:4321 “Thermal Cam Co.,Ltd Camera”, classe 239/2/1 (dispositivo composto UVC), autoalimentada com 100 mA declarados. A P2 Pro usa outro par: o código do P2Pro-Viewer procura por 0bda:5830. Nos dois casos o módulo do kernel é o uvcvideo, o mesmo do microscópio USB, e o nó é /dev/videoN.
O segredo está nos modos de vídeo. O autor do PyThermalCamera deixou no próprio arquivo tc001-RAW.py a saída do v4l2-ctl --list-formats-ext da sua máquina: formato YUYV, em dois tamanhos, 256 × 192 e 256 × 384, ambos a 25 quadros por segundo. O primeiro é só a imagem. O segundo é a imagem com um “porão”: a metade de cima são os 192 × 256 pixels de vídeo, a metade de baixo são os mesmos 192 × 256 pontos como temperatura em 16 bits, little-endian. A fórmula, reconstruída por engenharia reversa por LeoDJ no fórum EEVblog e usada nos dois projetos, é:
T (°C) = valor / 64 − 273,15
Ou seja, 1 unidade equivale a 1/64 de kelvin (0,0156 °C) e o máximo de 16 bits (65.535) daria 750,8 °C, folga de sobra para a faixa de 550 °C da câmera. Cada quadro de 256 × 384 ocupa 196.608 bytes; a 25 por segundo são 4,9 MB/s, 295 MB por minuto, 17,7 GB por hora. Logo adiante mostramos como comprimir sem perder nada.

O que instalar (tudo pelo gerenciador de pacotes)
Nada vem de site de terceiros. Os três pacotes são os da sua distribuição, nas versões que conferimos hoje nos repositórios oficiais:
| Pacote | Para quê | Ubuntu 24.04 | Debian 12 | Debian 13 |
|---|---|---|---|---|
v4l-utils |
v4l2-ctl: listar modos e gravar os quadros crus |
1.26.1 | 1.22.1 | 1.30.1 |
ffmpeg |
gravar e comprimir sem perda | 6.1.1 | 5.1.9 | 7.1.5 |
python3-opencv + python3-numpy |
ler a câmera ao vivo pelo script | OpenCV 4.6.0 | 4.6.0 | 4.10.0 |
sudo apt install v4l-utils ffmpeg python3-opencv python3-numpy
sudo usermod -aG video $USER # e saia/entre de novo na sessão
Se preferir o pip, o pacote oficial é o opencv-python-headless (PyPI): a versão atual é a 5.0.0.93, de 2 de julho de 2026, wheel manylinux2014 de 56,6 MB, SHA-256 começando em 09a872a1. Nosso teste rodou com a 4.12.0.88 e numpy 2.2.6, dentro de um venv. Para só analisar gravações, o script dispensa o OpenCV: basta numpy.
Passo 1: confirmar o modo 256 × 384
v4l2-ctl --list-devices
v4l2-ctl -d /dev/video0 --list-formats-ext
O primeiro comando lista as câmeras e o nó de cada uma (se houver webcam embutida no notebook, a térmica costuma ser /dev/video2, não a 0). O segundo precisa mostrar o YUYV em 256×384; se mostrar só 256×192 ou outra coisa, você está no dispositivo errado. O ffmpeg -f v4l2 -list_formats all -i /dev/video0 dá a mesma lista.
Passo 2: gravar os quadros crus
Duas receitas, as duas sem Python. Pelo v4l2-ctl, que grava os buffers exatamente como saem da câmera (as opções são as do manual da versão 1.26: --stream-mmap captura por mmap com 3 buffers, --stream-count é o número de quadros, --stream-to o arquivo):
v4l2-ctl -d /dev/video0 --set-fmt-video=width=256,height=384,pixelformat=YUYV
--stream-mmap --stream-count=1500 --stream-to=grav.yuv # 1500 quadros = 60 s
Pelo ffmpeg, que permite copiar sem recodificar (a opção -c:v copy com saída rawvideo; conferimos aqui que o arquivo de saída sai byte a byte igual ao de entrada):
ffmpeg -f v4l2 -input_format yuyv422 -video_size 256x384 -framerate 25
-i /dev/video0 -t 60 -c:v copy -f rawvideo grav.yuv
Para gravar horas, o truque é o do P2Pro-Viewer: ele separa a metade térmica como vídeo gray16le e comprime com FFV1, um codec sem perda que o ffmpeg traz de fábrica. Os 16 bits de cada ponto voltam intactos na leitura, e uma placa parada comprime muito, porque quase nada muda entre dois quadros.
Passo 3: o script que acha o ponto mais quente
Salve como ponto-quente.py. Ele lê uma gravação (--arquivo) ou a câmera ao vivo (--dispositivo N), agrupa os quadros por segundo e guarda, para cada segundo, o quadro em que a temperatura máxima foi maior: segundo, temperatura e posição (x, y) do ponto mais quente, além do mínimo, da média e do centro da imagem.
#!/usr/bin/env python3
# ponto-quente.py: TC001 / P2 Pro, quadros 256x384 (metade de baixo = 16 bits LE)
import argparse, csv, sys
import numpy as np
W, H = 256, 192 # sensor
QUADRO = W * (2 * H) * 2 # bytes por quadro YUYV 256x384 = 196.608
def temperaturas(quadro):
"""Matriz 192x256 em °C a partir dos bytes de um quadro 256x384."""
bruto = np.frombuffer(quadro, dtype='<u2', offset=W * H * 2, count=W * H)
return bruto.reshape(H, W).astype(np.float32) / 64.0 - 273.15
def quadros_arquivo(caminho):
with open(caminho, 'rb') as f:
while (q := f.read(QUADRO)) and len(q) == QUADRO:
yield q
def quadros_camera(n):
import cv2
cap = cv2.VideoCapture(f'/dev/video{n}', cv2.CAP_V4L2)
cap.set(cv2.CAP_PROP_CONVERT_RGB, 0) # bytes YUYV crus, sem converter em BGR
while True:
ok, fr = cap.read()
if not ok:
return
if fr.size != QUADRO:
sys.exit(f'quadro com {fr.size} bytes, esperava {QUADRO}: o modo 256x384 não abriu')
yield fr.tobytes()
ap = argparse.ArgumentParser()
ap.add_argument('--arquivo', help='gravação .yuv (256x384 YUYV)')
ap.add_argument('--dispositivo', type=int, help='número N de /dev/videoN')
ap.add_argument('--fps', type=int, default=25)
ap.add_argument('--saida', default='ponto-quente.csv')
a = ap.parse_args()
fonte = quadros_arquivo(a.arquivo) if a.arquivo else quadros_camera(a.dispositivo)
with open(a.saida, 'w', newline='') as f:
w = csv.writer(f)
w.writerow(['segundo', 't_max', 'x', 'y', 't_min', 't_media', 't_centro'])
seg_atual, melhor = -1, None
for i, q in enumerate(fonte):
t = temperaturas(q)
seg = i // a.fps
if seg != seg_atual and melhor is not None:
w.writerow(melhor); f.flush(); print(*melhor, sep='t')
melhor = None
seg_atual = seg
y, x = np.unravel_index(t.argmax(), t.shape)
linha = [seg, round(float(t[y, x]), 2), int(x), int(y), round(float(t.min()), 2),
round(float(t.mean()), 2), round(float(t[H // 2, W // 2]), 2)]
if melhor is None or linha[1] > melhor[1]:
melhor = linha # guarda o quadro mais quente do segundo
if melhor is not None:
w.writerow(melhor); print(*melhor, sep='t')
Três decisões de projeto, cada uma por causa de um erro real relatado nos programas prontos (lista mais abaixo): a temperatura é lida direto como inteiro de 16 bits, nunca como dois bytes multiplicados; o máximo é procurado na matriz já convertida, não no byte alto; e o script aborta com mensagem clara se o quadro não tiver 196.608 bytes.
python3 ponto-quente.py --arquivo grav.yuv # analisa a gravação
python3 ponto-quente.py --dispositivo 2 --saida bancada.csv # ou ao vivo
O que saiu no nosso teste
Geramos uma gravação de demonstração no formato exato da câmera: 30 segundos, 750 quadros, 147.456.000 bytes. A cena simula uma placa a 24 °C com um regulador fixo em 41 °C na posição (60, 120) e um MOSFET na posição (170, 80) que aquece de 26 °C até 78 °C com constante de tempo de 12 s, mais o ruído de 0,04 °C que a ficha da câmera declara (NETD 40 mK). Os números abaixo são dessa simulação, não de uma placa real; o formato dos bytes, sim, é o real.
O script processou os 147 MB em 0,24 s nesta máquina e escreveu 30 linhas. Nos segundos 0 a 3, o ponto mais quente é o regulador: 41,4 °C em (60, 120). No segundo 4 a posição salta para (170, 80), com 44,4 °C: o MOSFET passou o regulador. No segundo 29 ele chega a 74,6 °C. Essa coluna x,y é a informação que o aplicativo do celular não registra: ela diz qual componente venceu e quando.


Os 5 problemas mais comuns e a correção
- “OverflowError: Python integer 256 out of bounds for uint8”. Relatado em novembro de 2024 na issue 21 do PyThermalCamera: o código original multiplica o byte alto por 256 ainda como uint8, e o numpy 2 passou a recusar isso. A correção sugerida lá é converter para uint16 antes; o nosso script evita o problema lendo os dois bytes de uma vez como
'<u2'. - “IndexError: index 96 is out of bounds for axis 0 with size 0”. Issue 15 (fevereiro de 2024, com relatos até 2025): o quadro chegou vazio. Num relato a causa foi um cabo OTG caseiro; noutro, a conversão para RGB que o OpenCV faz por padrão. Mantenha o
CAP_PROP_CONVERT_RGBem 0 e ligue a câmera direto, sem hub. - “IndexError: index 480 is out of bounds for axis 0 with size 192”. Issue 27 (fevereiro de 2026, Raspberry Pi 4, OpenCV 4.6): o quadro recebido não tinha 256 × 384, porque o número do dispositivo era o de outra câmera ou o modo aberto foi o de 192 linhas. O script acima para na hora com “quadro com N bytes, esperava 196608”.
- Mínimo travado em −209,17 °C e uma linha de artefatos no rodapé. Issue 22 (fevereiro de 2025, Debian 12). A conta explica: −209,17 °C corresponde ao valor bruto 4.095, ou seja, a última linha do quadro veio com dado inválido. Se isso aparecer na sua câmera, calcule o mínimo e a média ignorando a última linha (
t[:-1]); o máximo não é afetado, porque o lixo é um valor baixo. - Temperatura máxima errada de vez em quando. Issue 1, julho de 2023: o programa procurava o máximo só no byte alto e depois lia o byte baixo daquele pixel, o que erra quando dois pixels empatam no byte alto. A solução proposta na própria issue é combinar os 16 bits antes de procurar; é o que o
argmaxsobre a matriz convertida faz.
E um aviso que não é bug: o estalo periódico da câmera é o obturador de calibração. Como o script guarda o quadro mais quente de cada segundo, um quadro estranho desse instante aparece como um ponto fora da curva; se incomodar, troque o critério por mediana do segundo.
Perguntas frequentes
A temperatura do CSV é a mesma que o aplicativo mostra? É a temperatura que a própria câmera calcula e envia nos 16 bits, com a emissividade e a distância que estão configuradas nela. O script não corrige nada; um dissipador de alumínio polido continua “mentindo para baixo”, como no post anterior.
Funciona no Raspberry Pi? O PyThermalCamera foi escrito para ele e o Thermal-Camera-Redux relata testes em modelos do Zero 2 W ao 5. O nosso script usa só numpy (e OpenCV para o modo ao vivo), ambos disponíveis no apt do Raspberry Pi OS.
Precisa de regra udev? Não, diferente do analisador lógico: a câmera é UVC e o nó /dev/videoN já nasce acessível ao grupo video. Se der “Permission denied”, é o seu usuário que não está no grupo.
Dá para fazer o mesmo no Windows? O formato é o mesmo, mas os relatos de quadro vazio nas issues vêm justamente do Windows; o fork PyThermalCamera-Windows, citado no post anterior, é o caminho mais curto lá.
Onde comprar: as duas câmeras são vendidas no Brasil pelos revendedores oficiais e pela Amazon; para deixar a câmera parada sobre a placa por minutos, um mini tripé resolve.
Como Associados da Amazon, recebemos por compras qualificadas feitas pelos links acima — você não paga nada a mais por isso, e ajuda o site a continuar gratuito.
Veredito honesto
Com três pacotes do apt e 50 linhas de Python, a câmera térmica de celular vira um registrador: você alimenta a placa, deixa gravando e o CSV diz qual ponto esquentou primeiro e em que segundo passou os outros. É a prova que falta no defeito que só aparece quente. A parte que ninguém conta: o formato dos 16 bits é fruto de engenharia reversa, não de documentação do fabricante (a TOPDON nunca respondeu ao pedido de especificação, segundo o autor do Redux), e os nossos números de teste vieram de uma gravação sintética. Na placa de verdade, confirme o componente apontado com o método do multímetro antes de trocar qualquer coisa.
Veja também
Quando o CSV apontar o componente e a pergunta virar “o que esse estágio da placa faz”, o Mapa da Placa é o nosso material de leitura de placa; vale conhecer.
