Multispektral-Thermal (PAG7936)

Die PAG7936-Variante des Multispectral Thermal Camera Module kombiniert einen 1-MP-Global-Shutter-Farbsensor mit einem FLIR-Lepton-Thermalkern, sodass die OpenMV Cam Farbsicht- und Thermalpipelines parallel ausführen kann.

Multispektral-Thermal (PAG7936)

Vollständiges Datenblatt, Fotos und Bestellinformationen finden Sie auf der Produktseite des Multispectral Thermal.

Bemerkung

Nur auf der OpenMV N6 unterstützt.

Highlights

  • PAG7936: 1-MP-Global-Shutter

  • Nimmt FLIR-Lepton-Thermalkerne der Versionen 1.x / 2.x / 3.x auf

  • Gleichzeitige Thermal- und Farbverarbeitung auf einem Modul

  • Sieht in völliger Dunkelheit, unterstützt Temperaturmessung

  • Der Global-Shutter bewältigt schnelle Bewegungen ohne Rolling-Shutter-Artefakte

Verwendung

Der Farbsensor und das FLIR Lepton erhalten jeweils ihre eigene csi.CSI-Instanz. Der erste Aufruf verwendet standardmäßig den primären Sensor (das PAG7936); der zweite bindet das Lepton durch Übergabe von cid= csi.LEPTON ein. Setzen Sie den Farbsensor mit csi.CSI.reset (hard=True) hart zurück, um die Versorgungsschiene hochzufahren, und konfigurieren Sie das Lepton mit hard=False, damit dessen Treiber den Chip nur neu programmiert, ohne den Reset erneut umzuschalten.

csi.CSI.framesize ( csi.QVGA ) passt die Lepton-Ausgabe an die Farbkamera an, sodass jeder snapshot() ein 320x240-Einzelbild zurückgibt. Der Lepton-Treiber skaliert sein natives 80x60-Einzelbild (1.x/2.x) bzw. 160x120-Einzelbild (3.x) intern auf die angeforderte Größe hoch — bei QVGA deckt jedes Lepton-Pixel eine 4x4- oder 2x2-Zelle auf dem Farbbild ab.

Zwei Scratch-Puffer bleiben über die Frame-Schleife hinweg konstant — eine 256x1-Alpha-Palette, gespeichert als image.Image, sodass kühle Lepton-Pixel transparent und heiße Pixel undurchsichtig werden (die quadratische Rampe unterdrückt Hintergrunddetails, ohne den mittleren Bereich zu stark zu komprimieren), und ein mit image.Image vorab allokierter Lepton-Framebuffer, sodass csi.CSI.snapshot (blocking=False, image=...) ihn bei jeder Iteration an Ort und Stelle füllen kann, ohne neu zu allokieren:

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())

Jede Iteration nimmt einen blockierenden Farb-Schnappschuss und einen nicht blockierenden Lepton-Schnappschuss auf — das Lepton läuft mit 9 Hz, sodass ein Blockieren darauf die Farbpipeline drosseln würde. Image.draw_image setzt die beiden anschließend zusammen: color_palette= image.PALETTE_IRONBOW bildet die Graustufen des Leptons auf eine FLIR-typische warme Farbrampe ab, alpha_palette= blendet jedes Pixel über die quadratische Alpha-Map ein, und hint= image.BILINEAR glättet die Hochskalierung.

Temperaturmessung

Radiometrische Leptons (Lepton 2.5 / 3.5) liefern kalibrierte Temperaturdaten pro Pixel. Aktivieren Sie den Messmodus über csi.CSI.ioctl mit csi.IOCTL_LEPTON_SET_MODE und begrenzen Sie anschließend das Temperaturfenster mit csi.IOCTL_LEPTON_SET_RANGE (min_celsius, max_celsius). Der Lepton-Treiber bildet den Graustufen-Pixelwert 0 linear auf min_celsius und 255 auf max_celsius ab, sodass jedes Pixel zu einer Temperaturzuordnung innerhalb des konfigurierten Fensters wird. Pixel, die kälter als min_celsius sind, sättigen bei 0, Pixel, die heißer als max_celsius sind, sättigen bei 255.

csi.IOCTL_LEPTON_SET_MODE nimmt zwei Flags entgegen. Das erste schaltet die Messung ein; das zweite wählt den Temperaturbereich des Sensors:

  • Niedriger Bereich(True, False) — Sensorbereich -10 °C bis +140 °C (raumgroße Szenen). Begrenzen Sie das Fenster auf den Interessensbereich, z. B. (20.0, 40.0) für die Verfolgung von Körperwärme:

    csi1.ioctl(csi.IOCTL_LEPTON_SET_MODE, True, False)
    csi1.ioctl(csi.IOCTL_LEPTON_SET_RANGE, 20.0, 40.0)
    
  • Hoher Bereich(True, True) — Sensorbereich -10 °C bis typischerweise ~+450 °C (~+400 °C bei Raumtemperatur) für heiße Objekte. Begrenzen Sie z. B. auf (0.0, 400.0) für die Verfolgung von Öfen oder Heizelementen:

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

So wandeln Sie ein Graustufen-Pixel zurück in Grad Celsius:

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

Dies funktioniert für einzelne Pixel oder für aggregierte Statistiken (z. B. stats.mean() aus Image.get_statistics) innerhalb einer ROI, wenn Sie heiße/kühle Regionen mit Image.find_blobs lokalisieren.

GPU-beschleunigte Ausrichtung

Image.draw_image akzeptiert ein transform=-Argument — eine 3x3-Homographiematrix als 2D-ulab.numpy-Array. Auf der OpenMV N6 führt die GPU die Pixel-für-Pixel-Projektion während desselben Zeichenvorgangs aus, sodass das Lepton-Einzelbild ohne separaten Warp-Durchlauf an der Perspektive der Farbkamera neu ausgerichtet werden kann. Kalibrieren Sie die Matrix pro Kamera mit dem Thermal-Overlay-Kalibrierwerkzeug:

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())

Beachten Sie, dass diese Variante die Farbkamera mit csi.VGA (640x480) und das Lepton mit csi.QQVGA (160x120) betreibt — die Homographie projiziert das kleinere Lepton-Einzelbild als Teil des Zeichenvorgangs in das größere Farb-Einzelbild, sodass der Hochskalierungsfaktor in die Matrix selbst eingebacken ist, statt separat angewendet zu werden.