Detección de anomalías por medio de imágenes térmicas con TinyML

En esta nota podés ver como creé un modelo de aprendizaje automático de detección de anomalías con Edge Impulse basado en imágenes térmicas, con datos enviados por celular a la nube a través de Notecard.

Historia

Si es así, es una señal de algún problema interno que únicamente mi compañía de HVAC debería abordar. Así que me dispuse a construir un sistema que pudiera monitorear las lecturas térmicas de la mayoría de las tuberías que ve y advertirme si aparecían “puntos calientes” donde no deberían.

NOTA: ¿Viste todas las tuberías de cobre? El cobre tiene un índice de emisividad extremadamente bajo, lo que dificulta su lectura en una cámara térmica. Afortunadamente, hay suficientes componentes de hierro mezclados para que este proyecto funcione lo suficientemente bien como para mi prueba de concepto.

Las piezas del rompecabezas

Con el problema bastante bien entendido, era hora de juntar las piezas del rompecabezas de hardware que necesitaba para que esto funcionara.

Dentro del sótano de una casa de más de 100 años, el acceso Wi-Fi es irregular en el mejor de los casos y no hay suficientes tomacorrientes estándar para alimentar cualquier dispositivo sin usar un cable de extensión largo. Por lo tanto, construir una solución de bajo consumo con LTE-M celular estaba en la parte superior de mi lista.

Empecé con Blues Wireless Notecard, que brinda acceso celular global prepago que incluye 500 MB de datos y 10 años de servicio. El modelo que elegí funciona con los protocolos LTE-M y NB-IoT, por lo que incluso con solo “una barra” de servicio celular en mi sótano, había suficiente ancho de banda para enviar los datos que necesitaba a la nube. También es un dispositivo de muy bajo consumo de energía por una suma de ~ 8uA cuando está inactivo.

A continuación, necesitaba una cámara termográfica. Elegí la cámara térmica IR MLX90640 de Adafruit . Genera una imagen térmica con una resolución de 32×24 que, aunque no parezca gran cosa, puede proporcionar una imagen térmica razonablemente precisa:

A continuación, necesitaba una cámara termográfica. Elegí la cámara térmica IR MLX90640 de Adafruit. Genera una imagen térmica con una resolución de 32×24 que, aunque no parezca gran cosa, puede proporcionar una imagen térmica razonablemente precisa:

Elegir un microcontrolador host o una computadora de placa única se convirtió en el siguiente obstáculo. Y debo confesar que fue dictado en gran medida por el ejemplo exclusivo de Linux proporcionado por Adafruit para la cámara térmica.

Elegí una Raspberry Pi Zero 2 W, ya que es probablemente la mejor opción para la informática de bajo consumo que todavía es compatible con una distribución completa de Linux (Raspbian).

El Notecarrier-Pi HAT de Blues Wireless hace que agregar datos móviles a cualquier Raspberry Pi sea muy sencillo:

Para una fuente de alimentación opté por un gran banco de energía USB-C de 30 000 mAh. Tiempo de confesión: no hice un buen trabajo al medir el consumo de energía, pero funcionó lo suficientemente bien como para mantener mi Pi funcionando durante unos días, con energía de sobra.

Entonces llegó el momento de comenzar el ensamblaje de un tipo diferente: construir un modelo ML desde cero y conectarlo con algún código de Python y servicios en la nube.

Construcción de un modelo térmico 🔥 TinyML

Lo que al principio puede parecer el aspecto más intimidante de todo este proyecto (construir un modelo de Machine Learning desde cero) posiblemente resultó ser el más fácil gracias a Edge Impulse .

Edge Impulse es una plataforma de desarrollo utilizada para construir e implementar modelos ML en dispositivos informáticos de borde.

Así es como construí un modelo ML “pequeño” en muy poco tiempo, utilizando la interfaz de usuario basada en web proporcionada por Edge Impulse Studio para crear un nuevo proyecto de clasificación de imágenes 

NOTA: Si bien Edge Impulse admite la creación de verdaderos modelos de detección de anomalías con datos de audio o acelerómetro, aún no admiten imágenes. ¡Es por eso que me quedé con un modelo de clasificación de imágenes para construir más un modelo de detección de anomalías de prueba de concepto!

1) Adquisición de datos

Con mi hardware ensamblado, al menos estaba listo para comenzar a tomar fotografías con la cámara térmica y guardarlas en la RPi Zero. Al configurar una función de Python simple para que se ejecutara cada 10 minutos, generé un conjunto de ~120 imágenes que eran una buena aproximación de los diferentes estados del funcionamiento adecuado de la caldera (por ejemplo, frío, tibio y caliente).

Tenga en cuenta que el código de Python para generar imágenes se presenta un poco más abajo en este tutorial, pero también está disponible en este repositorio de GitHub .

Con el conjunto completo de imágenes cargadas en mi proyecto en el estudio Edge Impulse, tuve que etiquetar las imágenes individuales:

“Frío” – lo que significa que el sistema no está encendido en absoluto:

“Caliente”: significa que el sistema se está calentando o enfriando:

“Caliente” – lo que significa que el sistema está calentando activamente la casa:

Y también “Anomalía”, lo que significa que se reconoció un estado anómalo (es decir, incierto o desconocido).

Las imágenes se cargaron en el estudio Edge Impulse, en la pestaña Adquisición de datos:

Pero espera, ¿cómo podría identificar estados anómalos sin registrar ningún comportamiento anómalo? ¡A través de la magia de Photoshop, por supuesto!

Al iniciar este proyecto, sabía que quería equiparar el comportamiento anómalo con puntos calientes que ocurrían lejos de cualquier punto caliente conocido (como el área de alivio de presión antes mencionada). Al ‘comprar en algunos puntos calientes, creé un conjunto adicional de imágenes que podrían representar anomalías, dándome un conjunto de cuatro estados:

2) Crea un Impulso

En Edge Impulse, un impulso “toma datos sin procesar, usa el procesamiento de señales para extraer características y luego usa un bloque de aprendizaje para clasificar nuevos datos”. Mi impulso estaba compuesto por los siguientes bloques de procesamiento aprendizaje:

3) Entrena al modelo

Usando las opciones predeterminadas proporcionadas, entrené mi modelo ML en función del conjunto de datos y la configuración mencionados anteriormente. El resultado inicial de este proceso me mostró que mi modelo iba a ser notablemente preciso (la prueba se basa en la preasignación de Edge Impulse de un subconjunto de imágenes a un conjunto de datos de “entrenamiento”).

Un fantástico “explorador de características” también lo ayuda a identificar cualquier imagen mal etiquetada antes de utilizar el modelo en cualquier entorno del mundo real.

¡Y eso fue todo lo que necesité para inicializar y entrenar un modelo TinyML con Edge Impulse!

A continuación, necesitaba configurar Raspberry Pi Zero, instalar algunos paquetes de software necesarios, descargar el modelo ML y ¡escribir algo de Python para conectarlo todo!

Configurando el Pi

Cada vez que comienzo un nuevo proyecto en mi Pi, me gusta asegurarme de que todos los paquetes instalados estén actualizados con:

sudo apt update
sudo apt full-upgrade
sudo apt clean

Configuración del Pi para el aprendizaje automático

Luego necesitaba instalar algunas dependencias para usar el corredor Edge Impulse en el Pi. Tenga en cuenta que las instrucciones proporcionadas son para Raspberry Pi 4, ¡pero funcionaron sin problemas para mí en Pi Zero 2!

Para conectar este dispositivo a Edge Impulse (que es necesario para descargar el archivo de modelo ML generado), necesitaba ejecutar:

edge-impulse-linux --disable-camera

La --disable-camerabandera se usa si su Pi no tiene un módulo de cámara conectado.

Instalé el SDK de Linux Python para Edge Impulse. Por alguna razón, en Pi Zero tuve que instalar las dependencias enumeradas en la sección “Raspberry Pi” por separado, así que YMMV 🤷.

El último paso de Edge Impulse consistió en ejecutar este comando para compilar y descargar el archivo del modelo de Machine Learning ( modelfile.eim) a la RasPi:

edge-impulse-linux-runner --download modelfile.eim

Sabía que todo estaría bien en el mundo cuando vi este resultado en la terminal:

[RUN] Downloading model...

Configuración del Pi para la cámara térmica

Un truco con el uso del MLX90640 en Raspberry Pi es la adafruit_blinkabiblioteca. Esto traduce la API de hardware de CircuitPython a cualquier biblioteca que proporcione Pi. ¡La ventaja aquí es que cualquier código que se ejecute en CircuitPython se ejecutará sin problemas en una Raspberry Pi!

Puede (y debe) revisar las instrucciones de configuración completas proporcionadas por Adafruit. Sin embargo, se pueden reducir a los siguientes comandos de terminal:

cd ~
sudo pip3 install --upgrade adafruit-python-shell
wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py
sudo python3 raspi-blinka.py

Luego adafruit-circuitpython-mlx90640se puede instalar a través de pip:

sudo pip3 install adafruit-circuitpython-mlx90640

Configuración del Pi para celular

Las únicas otras dependencias que se instalaron fueron Python SDK para Notecard celular (y python-peripherypara que Pi se comunique con Notecard a través de I2C):

pip3 install note-python python-periphery

Todo el Python Codez 🐍

Con el hardware ensamblado, el modelo TinyML creado y las dependencias de software instaladas, era hora de escribir algo de Python.

NOTA: Todo el código utilizado para este proyecto está disponible en este repositorio de GitHub.

El primer Python que “escribí” fue en realidad, ejem, tomado de Adafruit y ligeramente editado. Ese es un script utilizado para acceder a la cámara térmica, generar una imagen codificada por colores y guardarla en el sistema de archivos. Aquí hay una versión muy abreviada del código:

térmica.py

MINTEMP = 20.0  # low range of the sensor (deg C)
MAXTEMP = 40.0  # high range of the sensor (deg C)
COLORDEPTH = 1000  # how many color values we can have
INTERPOLATE = 20  # scale factor for final image

mlx = adafruit_mlx90640.MLX90640(board.I2C())

def takePicture():
    # get sensor data
    frame = [0] * 768
    success = False
while not success:
try:
            mlx.getFrame(frame)
            success = True
        except ValueError:
continue

    # create the image
    pixels = [0] * 768
for i, pixel in enumerate(frame):
        coloridx = map_value(pixel, MINTEMP, MAXTEMP, 0, COLORDEPTH - 1)
        coloridx = int(constrain(coloridx, 0, COLORDEPTH - 1))
        pixels[i] = colormap[coloridx]

    # save to file
    img = Image.new("RGB", (32, 24))
    img.putdata(pixels)
    img = img.transpose(Image.FLIP_LEFT_RIGHT)
    img = img.resize((32 * INTERPOLATE, 24 * INTERPOLATE), Image.BICUBIC)
    ts = str(int(time.time()))
    filename = "ir_" + ts + ".jpg"
    img.save("images/" + filename)

return filename

Tenía que asegurarme de establecer las constantes adecuadas , MINTEMPya MAXTEMPque esto proporcionaba la mejor gama de colores fríos/calientes según el escenario que estaba midiendo.

Como puede ver arriba, cuando takePicturese ejecuta el método, se genera una imagen térmica y se guarda como jpeg en un imagessubdirectorio con una marca de tiempo.

El otro script de Python importante en esta aplicación es main.pyel que realiza las siguientes funciones cada 5 minutos :

1) Llame al takePicturemétodo thermal.pyy cargue la imagen en un cv2objeto de imagen:

filename = thermal.takePicture()
img = cv2.imread("images/" + filename)

2) Procese la imagen con Edge Impulse Linux runner para generar una inferencia de clasificación (por ejemplo, anomalía, frío, tibio, caliente):

features = runner.get_features_from_image(img)

    res = runner.classify(features)

    if "classification" in res["result"].keys():
        print('Result (%d ms.) ' % (
            res['timing']['dsp'] + res['timing']['classification']), end='')

El printcomando en este código genera una cadena como esta:

Result (65 ms.) anomaly: 0.12 cold: 0.00 hot: 0.88 warm: 0.00

3) Almacene las inferencias generadas (clasificaciones de imágenes) en JSON y sincronícelas con la nube usando Notecard celular:

note_body = {}
for label in labels:
        score = res['result']['classification'][label]
print('%s: %.2f\t' % (label, score), end='')
        note_body[label] = round(score, 4)

print('', flush=True)

    note.add(nCard,
file="thermal.qo",
body=note_body)

Por ejemplo, el note_bodyobjeto podría verse así, con una imagen que representa un estado “frío”:

{
"anomaly": 0.0,
"cold": 0.99850000000000019,
"hot": 0.0,
"warm": 0.0015
}

En el código anterior mencioné que “sincronizo [los datos JSON] con la nube”. Veamos adónde van exactamente esos datos.

Creación de un panel de control en la nube ☁️

Dado que Notecard es una bomba de datos del dispositivo a la nube, no vive en la Internet pública (lo que la convierte en un dispositivo extremadamente seguro) y, por lo tanto, necesita un proxy con el que sincronizar los datos. ¡Ingrese a Blues Wireless Notehub!

Notehub es un servicio de nube delgada que acepta de forma segura datos de la Notecard celular (fuera de la Internet pública, usando túneles VPN privados) y luego enruta instantáneamente los datos al proveedor de la nube de su elección (ya sea AWS, Azure, Google Cloud o cualquier otro). servicio optimizado para IoT como Ubidots, Datacake, Losant y otros).

¿ Recuerdas el note.addcomando usado main.pyarriba?

note.add(nCard,
file="thermal.qo",
body=note_body)

El JSON creado ( note_body), se guarda en la Notecard como una Nota. ¿ Ves el fileparámetro? Eso es un archivo de notas: un archivo de notas puede almacenar varias notas. Tan pronto como la Notecard establece una conexión con Notehub, los archivos de notas almacenados se sincronizan con la nube junto con algunos datos de la sesión:

NOTA: (Juego de palabras, ¡ja!) Las API de Notecard y Notehub están basadas en JSON. Obtenga un adelanto de la API y vea todos los recursos para desarrolladores proporcionados en dev.blues.io.

Con mis datos sincronizados con Notehub, era hora de darle sentido y crear un tablero donde pudiera ver una representación visual de los datos. En este caso, usé Ubidots, que he usado varias veces en el pasado para crear tableros realmente atractivos:

Siguiendo el completo tutorial de enrutamiento proporcionado por Blues Wireless, creé una ruta simple de Notehub a Ubidots pasando una URL de punto final, un encabezado de autorización y una expresión JSONata para transformar mis datos sobre la marcha:

{
    "cold": {"value": body.cold, "timestamp": when * 1000},
    "warm": {"value": body.warm, "timestamp": when * 1000},
    "hot": {"value": body.hot, "timestamp": when * 1000},
    "anomaly": {"value": body.anomaly, "timestamp": when * 1000}
}

La expresión anterior convirtió una parte relativamente grande de JSON…

{
    "event": "5ffa7e24-c559-40a0-8228-46ff8426b387",
    "session": "9afdece2-9fd2-48ff-8e7d-5e66ce118b3c",
    "best_id": "dev:864475",
    "device": "dev:864475",
    "product": "product:com.blues.xxx:xxx",
    "received": 1644340365.888508,
    "routed": 1644340370,
    "req": "note.add",
    "when": 1644340339,
    "file": "thermal.qo",
    "body": {
        "anomaly": 0.0,
        "cold": 0.99850000000000019,
        "hot": 0.0,
        "warm": 0.0015
    },
    "best_location_type": "tower",
    "best_lat": 43.073787499999995,
    "best_lon": -89.44367187499999,
    "best_location": "Shorewood Hills WI",
    "best_country": "US",
    "best_timezone": "America/Chicago",
    "tower_when": 1644340365,
    "tower_lat": 43.073787499999995,
    "tower_lon": -89.44367187499999,
    "tower_country": "US",
    "tower_location": "Shorewood Hills WI",
    "tower_timezone": "America/Chicago",
    "tower_id": "310,410,17169,77315601"
}

…en la siguiente carga útil optimizada para Ubidots:

{
    "cold": {"value": 0.99850000000000019, "timestamp": 1644340339000},
    "warm": {"value": 0.0015, "timestamp": 1644340339000},
    "hot": {"value": 0.0, "timestamp": 1644340339000},
    "anomaly": {"value": 0.0, "timestamp": 1644340339000}
}

Obtenga más información sobre JSONata en una guía proporcionada por Blues Wireless.

En el lado de Ubidots, vi que los datos aparecían casi instantáneamente, lo que me permitió crear un gráfico de líneas simple pero efectivo de los estados registrados de las imágenes térmicas:

¡Alerta! 🚨 ¡Anomalía detectada!

¡¿Qué es un sistema de detección de anomalías sin alertas en tiempo real?!

Notehub también proporciona una integración con Twilio para permitir el envío de mensajes de correo electrónico o SMS cuando se cumple una condición específica. En mi caso, solo quería recibir una alerta si mi sistema detectaba una anomalía (un mensaje SMS funciona bien en este caso).

Para enviar un SMS con Twilio, simplemente tuve que crear una nueva ruta en Notehub siguiendo el tutorial de enrutamiento de Twilio. Sin embargo, solo quería que esta ruta se activase en determinadas circunstancias (por ejemplo, cuando se detectaba una anomalía).

Al extender el código existente provisto en main.py, creé una declaración condicional y activé la otra ruta, así, cuando se encontró una etiqueta de anomalía con un valor> 0.7:

if anomaly > 0.7:
    note.add(nCard,
	    file="alert.qo",
	    body=note_body)

Nuevamente, puede seguir la guía de enrutamiento de Twilio proporcionada para obtener más información sobre el envío de mensajes SMS a través de Notehub.

Códigos

https://github.com/rdlauer/pizero-thermal

https://github.com/blues/note-python

Acerca de Rob Lauer 2 Articles
Rob es líder de relaciones con desarrolladores en Blues Wireless (blues.io). Rob, un hacker y creador de corazón, ha pasado un tiempo como ingeniero de stack con un amor por la web abierta.

Sé el primero en comentar

Deja un comentario

Tu dirección de correo no será publicada.


*