Русский · English · Deutsch · Español · 中文
Una aplicación sencilla que permite usar la cámara térmica UNI-T UTi260M en un Mac. El módulo se vende como accesorio para teléfonos Android (conector Type-C), pero es una cámara USB corriente, y a macOS también se le puede sacar temperaturas reales.
| Característica | Valor |
|---|---|
| Resolución del sensor | 256 x 192 |
| Rango de temperatura | -20 ... 550 °C |
| Campo de visión (FOV) | 56° x 42° |
| Conector | USB Type-C (pensado para Android) |
| Cómo lo ve el sistema | USB Camera, VID 0x0BDA, PID 0x5830 |
macOS trata ese módulo como una webcam normal, así que Photo Booth muestra una silueta gris sin temperatura alguna. Esta aplicación lee los datos térmicos en bruto y construye con ellos un mapa de temperaturas calibrado, con escala en °C, marcadores del punto más caliente y más frío y medición por clic.
La Thermal.app compilada va adjunta en las releases - no hay que compilar nada, Python y todas las bibliotecas ya están dentro.
- macOS 13 o posterior, solo Apple Silicon (M1 en adelante)
- descarga el archivo, descomprímelo y arrastra
Thermal.appa Aplicaciones - en el primer arranque haz clic derecho en el icono y elige «Abrir», si no macOS se niega a ejecutarla: la firma es ad-hoc, sin certificado de pago de Apple. Se pregunta una sola vez.
Lo mismo desde la terminal:
xattr -dr com.apple.quarantine /Applications/Thermal.appPara un Mac Intel, o para tu propia compilación, tienes build_app.sh más abajo.
Todo se escribió y depuró con una única UNI-T UTi260M. Otros módulos con el mismo núcleo (serie UNI-T UTi, InfiRay P2 Pro, Topdon TC001) están construidos de forma parecida y lo más probable es que funcionen, pero no se han probado - si tienes uno, abre un issue y lo añado a la lista.
La cámara expone dos modos UVC:
| Modo | Contenido |
|---|---|
256x192 yuvs |
solo la imagen del sensor tras el AGC, una silueta gris, sin temperaturas |
256x384 yuvs |
imagen más el mapa de temperaturas en bruto |
En el modo 256x384 el fotograma son dos bloques apilados:
- filas
0..191- imagen del sensor de 8 bits (YUYV, luminancia en los bytes pares); - filas
192..383- 16 bits en bruto little-endian por píxel.
Temperatura: T(°C) = raw / 64 - 273.15
Photo Booth y FaceTime eligen 256x192, de ahí la silueta sin cifras.
Los scripts ya hechos como PyThermalCamera o P2Pro-Viewer se apoyan en OpenCV. En
macOS cv2.VideoCapture pasa por AVFoundation, que convierte YUYV a RGB en silencio:
los 16 bits en bruto se pierden sin remedio y solo quedan colores bonitos sin relación
con la temperatura. Aquí se evita abriendo AVCaptureVideoDataOutput con el formato de
píxel 'yuvs' (0x79757673) solicitado de forma explícita y leyendo los bytes de
CVPixelBuffer tal cual, respetando bytesPerRow.
- macOS 13 o posterior
- Python 3 en compilación framework de python.org (el backend macosx de matplotlib la necesita)
- La propia UTi260M
git clone https://github.com/mamapapa1/uti260m-macos.git
cd uti260m-macos
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
./venv/bin/python -m unithermalConstruir una app que se abre con doble clic:
./build_app.sh # → ~/Desktop/Thermal.app
./build_app.sh /ApplicationsEn el primer arranque macOS pide acceso a la cámara, hay que concederlo. La aplicación lo solicita de forma explícita: sin eso el bundle no recibe ni fotogramas ni diálogo.
También puedes ver la aplicación sin módulo alguno:
./venv/bin/python -m unithermal --demoEl modo demo dibuja fotogramas sintéticos, pero los empaqueta en el mismo formato de transmisión y los pasa por la misma decodificación que los reales, así que se ejecuta el programa entero y no una rama recortada.
La cámara no hace falta al arrancar: la ventana se abre enseguida y avisa de que espera el módulo. Al conectarlo la imagen aparece sola; al desconectarlo la ventana no se cierra, vuelve a esperar. La captura se reinicia sola, así que puedes enchufar y desenchufar el USB las veces que quieras.
Las capturas con s van a ~/Pictures/Thermal/ si la app se ejecuta desde el bundle (allí
el directorio de trabajo es /), y si no, al directorio actual. Cada captura es un PNG
más un CSV con la matriz completa de 192x256 en grados Celsius.
r / R |
girar 90° en sentido horario / antihorario |
m |
espejo horizontal |
espacio |
congelar / soltar la imagen |
f |
pantalla completa (Esc para salir) |
p |
siguiente paleta |
a |
rango de temperatura automático / fijo |
c |
temperaturas / imagen propia del sensor |
| clic | colocar un punto de medición |
s |
guardar PNG + CSV |
l |
siguiente idioma de la interfaz |
h |
mostrar / ocultar la ayuda |
q |
salir |
Abajo del todo siempre hay una línea de ayuda: al arrancar desde el bundle no hay terminal, así que la lista de teclas tiene que vivir en la propia ventana.
Funciona cualquier distribución. matplotlib entrega un carácter y no una tecla, así que
con la distribución rusa la R física llega como к. La aplicación le pregunta al
propio sistema cuál es la distribución activa (Text Input Services) y con UCKeyTranslate
averigua qué carácter da cada tecla física, para luego traducir la pulsación por
posición de la tecla. No hay tablas por idioma: el ucraniano, el griego, el armenio, el
hebreo y cualquier otra distribución funcionan igual, y un cambio de distribución en
caliente se detecta en medio segundo.
Se conserva el uso de mayúsculas, porque distingue el sentido del giro, y también los
modificadores (ctrl+к → ctrl+r). Si la API del sistema no está disponible, queda una
tabla ЙЦУКЕН incorporada. Las distribuciones latinas como AZERTY o QWERTZ no necesitan
traducción: las letras son las mismas, solo están en otro sitio.
La interfaz sigue el idioma de macOS y está traducida al ruso, inglés, alemán, español,
francés y chino. Se puede forzar con --lang:
./venv/bin/python -m unithermal --lang zhDentro de la ventana el idioma se elige pulsando el botón con el código de idioma de la
esquina superior izquierda: despliega una lista donde cada idioma está escrito en sí
mismo. La tecla l los recorre en círculo. La elección se recuerda: queda en
~/Library/Application Support/Thermal/settings.json y se aplica en el siguiente
arranque.
Para el chino la aplicación cambia a una fuente CJK (PingFang SC y alternativas): la fuente por defecto de matplotlib, DejaVu Sans, no tiene ideogramas y dibujaría cuadros vacíos sin dar ningún error.
--snapshot PATH guardar un solo fotograma en PNG y salir
--offset DELTA_C corrección en °C sobre todos los valores (calibración con una referencia)
--device UNIQUE_ID elegir cámara si hay más de una
--list listar las cámaras compatibles y salir
--demo funcionar con fotogramas sintéticos, sin cámara
--lang CODE en, ru, de, es, fr, zh- La resolución en temperatura es de 1/64 K, pero la precisión absoluta de estos módulos ronda los ±2 °C, y la calibración de fábrica supone una emisividad de ε ≈ 0.95.
- El metal brillante, el vidrio y el plástico pulido dan lecturas muy bajas: ahí se mide radiación reflejada y no la superficie en sí. La solución habitual es un trozo de cinta mate sobre el punto a medir.
- El sensor hace FFC de vez en cuando (un clic del obturador, la imagen se congela un instante). Es normal, sin eso la imagen deriva.
--offsetaplica una corrección lineal al comparar con un termómetro de referencia.
La cámara se busca por la presencia del modo 256x384 y no por el uniqueID, porque este
último cambia según el puerto USB.
Cuatro trampas costaron aquí un tiempo notable; todas están resueltas en build_app.sh y
en el código. Las dos primeras dan el mismo síntoma: el doble clic no hace nada, no hay
error, no se genera crash report, y sin embargo ese mismo lanzador desde el terminal
funciona.
- Un symlink que sale del bundle.
python -m venvdejabin/python3.xcomo enlace a/Library/Frameworks/.... La firma de código prohíbe los symlinks hacia fuera (invalid destination for symbolic link in bundle), la firma queda rota y Gatekeeper bloquea el arranque. Se arregla copiando el binario real dentro. - Rosetta. El ejecutable principal del bundle es un script de shell, que no tiene
arquitectura, así que LaunchServices lanza el proceso como x86_64 bajo Rosetta
mientras que las ruedas de numpy/pyobjc son solo arm64. Cerrado por tres vías a la vez:
lipo -thinpara el intérprete,LSArchitecturePriorityenInfo.plistyexec /usr/bin/archen el lanzador. - Acceso a la cámara. Una app empaquetada no recibe el diálogo automáticamente, la
sesión simplemente no entrega fotogramas. Hay que llamar explícitamente a
AVCaptureDevice.requestAccessForMediaType_completionHandler_. - Composición.
colorbar(ax=...)yset_titlequitan espacio a los ejes y aprietan el fotograma hasta unos dos tercios de la ventana; en pantalla completa la imagen ocupaba solo el 77 % de la altura. Ahora los ejes se extienden por todo el lienzo y todas las etiquetas son superposiciones, con lo que se llega al 100 %.
Para depurar un bundle que muere en silencio, redirige la salida del lanzador a un fichero
(exec >> ~/thermal-launch.log 2>&1 como primera línea), si no el traceback se pierde.
spctl -a devuelve rejected con una firma ad-hoc. Es lo esperado sin notarización y no
impide arrancar: un bundle compilado localmente no lleva atributo de cuarentena.
- «Cámara térmica no encontrada» → el módulo está ocupado por otra app (Photo Booth,
FaceTime, Zoom, una copia antigua de esta misma app). Ciérralas y comprueba con
--list. - La ventana se abre pero no llegan fotogramas → no se concedió acceso a la cámara: Ajustes del Sistema → Privacidad y seguridad → Cámara, actívala para «Thermal».
- Cuadros vacíos en lugar de ideogramas → el sistema no tiene ninguna de las fuentes
CJK de la lista, instala PingFang SC o arranca con
--lang en. - El doble clic en la .app no hace nada → mira las trampas de empaquetado de arriba.
./venv/bin/pip install pytest
./venv/bin/python -m pytest tests -qLa batería cubre la traducción de distribuciones (incluidos todos los atajos escritos en ruso), la integridad de las traducciones y sus sustituciones para los seis idiomas, la cobertura de glifos de la fuente elegida, la geometría del giro (la relación de aspecto del píxel debe quedarse exactamente en 1.0 en las cuatro orientaciones), la parte de la pantalla que ocupa el fotograma en pantalla completa, la congelación, el funcionamiento sin módulo conectado con reconexión en caliente y el cambio de idioma en la ventana.
Un proyecto aficionado, no afiliado a UNI-T. La aplicación solo lee lo que entrega el módulo y no es un instrumento de medida: las lecturas dependen de la emisividad de la superficie, de la distancia y de lo caliente que esté la propia cámara. Donde las medidas importen, hace falta un instrumento calibrado.
GNU GPL v3 © 2026 Alexey Verhogladov.
El proyecto es gratis y hecho por entusiasmo. Si te sirvió - gracias por el apoyo (USDT):
| Red | Dirección |
|---|---|
| USDT · TRC20 | TYLmBvdxL8t9ziyZiFp3jcHwQbAsZR5haZ |
| USDT · TON | UQCnVND-uBgkWIAD1UP14tsN8239KE5BTOfKSJmg-0XqrN-k |
| USDT · ERC20 | 0xa61dcA98A86D84883Ddb3d62aA7F008f157c8eFF |

