Multispectral térmica (PAG7936)

La variante PAG7936 del módulo de cámara Multispectral Thermal combina un sensor de color de 1 MP de obturador global (global shutter) con un núcleo térmico FLIR Lepton, de modo que la OpenMV Cam puede ejecutar simultáneamente cadenas de visión de color y cadenas térmicas.

Multispectral térmica (PAG7936)

Para ver la hoja de datos completa, fotos e información de compra, consulta la página del producto Multispectral Thermal.

Nota

Compatible únicamente con la OpenMV N6.

Aspectos destacados

  • PAG7936: obturador global de 1 MP

  • Admite núcleos térmicos FLIR Lepton 1.x / 2.x / 3.x

  • Procesamiento térmico + color simultáneo en un solo módulo

  • Ve en completa oscuridad y admite la medición de temperatura

  • El obturador global maneja el movimiento rápido sin artefactos de obturador rodante

Uso

El sensor de color y el FLIR Lepton obtienen cada uno su propia instancia de csi.CSI. La primera llamada toma por defecto el sensor principal (el PAG7936); la segunda se enlaza al Lepton pasando cid= csi.LEPTON. Reinicia físicamente el sensor de color con csi.CSI.reset (hard=True) para activar el riel de alimentación, y configura el Lepton con hard=False para que su controlador solo reprograme el chip sin volver a conmutar el reinicio.

csi.CSI.framesize ( csi.QVGA ) ajusta la salida del Lepton a la cámara de color, de modo que cada snapshot() devuelve un fotograma de 320x240. El controlador del Lepton aumenta internamente la escala de su fotograma nativo de 80x60 (1.x/2.x) o 160x120 (3.x) al tamaño solicitado: en QVGA cada píxel del Lepton cubre una celda de 4x4 o 2x2 en el fotograma de color.

Dos búferes auxiliares permanecen constantes durante el bucle de fotogramas: una paleta alfa de 256x1 almacenada como una image.Image para que los píxeles fríos del Lepton se vuelvan transparentes y los píxeles calientes se vuelvan opacos (la rampa cuadrática suprime el detalle de fondo sin aplastar el rango medio), y un búfer de fotogramas del Lepton preasignado con image.Image para que csi.CSI.snapshot (blocking=False, image=...) pueda rellenarlo en el sitio en cada iteración sin reasignar memoria:

import time
import csi
import image
import math

alpha_pal = image.Image(256, 1, image.GRAYSCALE)
for i in range(256):
    alpha_pal[i] = int(math.pow((i / 255), 2) * 255)

# Setup the color camera sensor.
csi0 = csi.CSI()
csi0.reset(hard=True)  # force hardware reset.
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

csi1 = csi.CSI(cid=csi.LEPTON)
csi1.reset(hard=False)  # no hardware reset - just configure lepton
csi1.pixformat(csi.GRAYSCALE)
csi1.framesize(csi.QVGA)

# Optional temperature range controls for the LEPTON.
# csi1.ioctl(csi.IOCTL_LEPTON_SET_MODE, True, False)
# csi1.ioctl(csi.IOCTL_LEPTON_SET_RANGE, 20.0, 40.0)

clock = time.clock()

img1 = image.Image(csi1.width(), csi1.height(), csi1.pixformat())

while True:
    clock.tick()
    img0 = csi0.snapshot()
    csi1.snapshot(blocking=False, image=img1)
    img0.draw_image(img1, 0, 0, color_palette=image.PALETTE_IRONBOW,
                    alpha_palette=alpha_pal,
                    hint=image.BILINEAR)
    print(clock.fps())

Cada iteración toma una captura de color bloqueante y una captura del Lepton no bloqueante: el Lepton funciona a 9 Hz, por lo que bloquearse en él limitaría la cadena de color. A continuación, Image.draw_image compone las dos: color_palette= image.PALETTE_IRONBOW asigna la escala de grises del Lepton a una rampa de color cálido de estilo FLIR, alpha_palette= mezcla cada píxel usando el mapa alfa cuadrático, y hint= image.BILINEAR suaviza el aumento de escala.

Medición de temperatura

Los Lepton radiométricos (Lepton 2.5 / 3.5) informan datos de temperatura calibrados por píxel. Habilita el modo de medición mediante csi.CSI.ioctl con csi.IOCTL_LEPTON_SET_MODE, y luego acota la ventana de temperatura con csi.IOCTL_LEPTON_SET_RANGE (min_celsius, max_celsius). El controlador del Lepton asigna linealmente el valor de píxel en escala de grises 0 a min_celsius y 255 a max_celsius, de modo que cada píxel se convierte en una consulta de temperatura dentro de la ventana configurada. Los píxeles más fríos que min_celsius se saturan en 0 y los píxeles más calientes que max_celsius se saturan en 255.

csi.IOCTL_LEPTON_SET_MODE toma dos indicadores. El primero activa la medición; el segundo selecciona el rango de temperatura del sensor:

  • Rango bajo(True, False) — alcance del sensor de -10 °C a +140 °C (escenas a escala de habitación). Acota la ventana al área de interés, p. ej. (20.0, 40.0) para el seguimiento del calor corporal:

    csi1.ioctl(csi.IOCTL_LEPTON_SET_MODE, True, False)
    csi1.ioctl(csi.IOCTL_LEPTON_SET_RANGE, 20.0, 40.0)
    
  • Rango alto(True, True) — alcance del sensor de -10 °C a ~+450 °C típico (~+400 °C a temperatura ambiente) para objetos calientes. Acota a, p. ej., (0.0, 400.0) para el seguimiento de hornos o elementos calientes:

    csi1.ioctl(csi.IOCTL_LEPTON_SET_MODE, True, True)
    csi1.ioctl(csi.IOCTL_LEPTON_SET_RANGE, 0.0, 400.0)
    

Para convertir un píxel en escala de grises de nuevo a grados Celsius:

def p_to_temp(p, min_t, max_t):
    return (p * (max_t - min_t)) / 255.0 + min_t

Esto funciona en píxeles individuales o en estadísticas agregadas (p. ej. stats.mean() de Image.get_statistics) dentro de una ROI al localizar regiones calientes/frías con Image.find_blobs.

Alineación acelerada por GPU

Image.draw_image acepta un argumento transform=: una matriz de homografía de 3x3 como un array bidimensional de ulab.numpy. En la OpenMV N6, la GPU ejecuta la proyección por píxel durante el mismo dibujo, de modo que el fotograma del Lepton se puede realinear con la perspectiva de la cámara de color sin una pasada de deformación independiente. Calibra la matriz por cámara con la herramienta thermal-overlay-calibration:

import time
import csi
import image
from ulab import numpy as np
import math

# Calibration matrix from the thermal-overlay-calibration tool.
m = np.array([
    [3.704807, 0.257018, 37.260564],
    [0.052147, 3.609977, -7.831831],
    [0.000294, 0.000552, 1.000000],
])

alpha_pal = image.Image(256, 1, image.GRAYSCALE)
for i in range(256):
    alpha_pal[i] = int(math.pow((i / 255), 2) * 255)

# Setup the color camera sensor.
csi0 = csi.CSI()
csi0.reset(hard=True)  # force hardware reset.
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)

csi1 = csi.CSI(cid=csi.LEPTON)
csi1.reset(hard=False)  # no hardware reset - just configure lepton
csi1.pixformat(csi.GRAYSCALE)
csi1.framesize(csi.QQVGA)

# Optional temperature range controls for the LEPTON.
# csi1.ioctl(csi.IOCTL_LEPTON_SET_MODE, True, False)
# csi1.ioctl(csi.IOCTL_LEPTON_SET_RANGE, 20.0, 40.0)

clock = time.clock()

img1 = image.Image(csi1.width(), csi1.height(), csi1.pixformat())

while True:
    clock.tick()
    img0 = csi0.snapshot()
    csi1.snapshot(blocking=False, image=img1)
    img0.draw_image(img1, 0, 0, color_palette=image.PALETTE_IRONBOW,
                    alpha_palette=alpha_pal,
                    hint=image.BILINEAR,
                    transform=m)
    print(clock.fps())

Ten en cuenta que esta variante ejecuta la cámara de color a csi.VGA (640x480) y el Lepton a csi.QQVGA (160x120): la homografía proyecta el fotograma más pequeño del Lepton dentro del fotograma de color más grande como parte del dibujo, de modo que el factor de aumento de escala queda integrado en la propia matriz en lugar de aplicarse por separado.