Skip to main content

使用 Wio Terminal 构建红外热成像相机

概述

使用 Grove - 红外温度传感器阵列 (AMG8833)Wio Terminal,我们可以轻松构建一个低成本的类似 FLIR™ 的热成像相机!需要说明的是,Grove - 红外温度传感器阵列 (AMG8833) 的分辨率只有 8 x 8(64 像素),在某些情况下这已经足够了。因此代码中使用了线性插值将其扩展到 70 x 70(4900 像素),以获得更好的显示效果。

这个演示受到了 Kris Kasprzak 的视频 的启发。为了使其与 Wio Terminal 和 Grove - 红外温度传感器阵列 (AMG8833) 兼容,进行了几项修改。大部分图形现在首先绘制到 TFT LCD 精灵图中,以提高整体性能和更快的帧率。还在屏幕中央添加了十字准线,并显示十字准线处的温度。

零件清单

功能特性

  • 显示十字准线处的精确温度

  • 红外热成像相机感应热物体

  • 右按钮启用网格开/关功能

所需的 Arduino 库

  • 安装 LCD 屏幕库 Seeed_Arduino_LCD,请访问 Wio Terminal LCD 获取更多信息。

  • 访问 Seeed_AMG8833 仓库,将整个仓库下载到本地驱动器。

    • 现在可以将 Seeed_AMG8833 库安装到 Arduino IDE 中。打开 Arduino IDE,点击 sketch -> Include Library -> Add .ZIP Library,选择刚刚下载的 Seeed_AMG8833 文件。

Arduino 说明

  • 将 Grove - 红外温度传感器阵列 (AMG8833) 插入 Wio Terminal 的 Grove I2C 接口。

  • 这里下载完整代码或复制以下内容。

  • 上传代码。

完整代码

注意: 为了提升这个红外热成像相机的性能和帧率,您可以将 Wio Terminal CPU 速度提升到 200MHz。选择 Tools -> CPU Speed -> 200MHz(Overclock)


/*

这个程序用于将8 x 8热像仪读数数组放大
它将放大10倍并显示到240 x 320的屏幕上
插值是线性的,考虑到显示器是5-6-5色彩调色板,这已经"足够好"了
最终的总数组是一个70 x 70的内部点数组

修订版本
1.0 Kasprzak 初始代码
1.1 Anson(Seeed Studio) 适配到Wio Terminal与Grove - 红外传感器(AMG8833)

*/

#include <Seeed_AMG8833_driver.h>
#include <TFT_eSPI.h> // 包含图形库(这包括精灵函数)

TFT_eSPI tft = TFT_eSPI();
TFT_eSprite Display = TFT_eSprite(&tft); // 创建精灵对象"img",指向"tft"对象
// 指针被pushSprite()用来将其推送到TFT上

unsigned long CurTime;

uint16_t TheColor;
// 从一些初始颜色开始
uint16_t MinTemp = 25;
uint16_t MaxTemp = 35;

// 插值颜色的变量
byte red, green, blue;

// 行/列插值的变量
byte i, j, k, row, col, incr;
float intPoint, val, a, b, c, d, ii;
byte aLow, aHigh;

// 显示"像素"的大小
byte BoxWidth = 3;
byte BoxHeight = 3;

int x, y;
char buf[20];

// 切换显示网格的变量
int ShowGrid = -1;

// 8 x 8测量像素的数组
float pixels[64];

// 插值数组
float HDTemp[80][80];

// 创建摄像头对象
AMG8833 ThermalSensor;

//切换网格开关
void toggleGrid() {
ShowGrid = ShowGrid *-1;
Display.fillRect(15, 15, 210, 210, TFT_BLACK);
yield();
}

void setup() {
Serial.begin(115200);

// 启动显示器并将背景设置为黑色
tft.begin();
tft.fillScreen(TFT_BLACK);

//中断来切换网格开关
pinMode(WIO_KEY_A, INPUT);
attachInterrupt(digitalPinToInterrupt(WIO_KEY_A), toggleGrid, FALLING);

// 设置显示旋转(根据你的显示器,你可能需要改为0
tft.setRotation(3);

// 显示启动画面

tft.setCursor(20, 20);
tft.setTextColor(TFT_BLUE, TFT_BLACK);
tft.print("热像 ");

tft.setTextColor(TFT_RED, TFT_BLACK);
tft.print("摄像头");

// 让传感器启动
bool status = ThermalSensor.init();
delay(100);

if (!status) {
Serial.print("初始化AMG8833失败");
}

// 读取摄像头进行初始测试
ThermalSensor.read_pixel_temperature(pixels);

// 检查状态并显示结果
if (pixels[0] < 0) {
while (1) {
tft.setCursor(20, 40);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.print("读数: 失败");
delay(500);
}
}
else {
tft.setCursor(20, 40);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.print("读数: 正常");
delay(2000);
}

tft.fillScreen(TFT_BLACK);

Display.createSprite(TFT_HEIGHT, TFT_WIDTH);
Display.fillSprite(TFT_BLACK);

// 获取颜色插值例程的截止点
// 注意这个函数在温度刻度改变时被调用
Getabcd();

// 绘制与传感器最大值和最小值匹配的刻度图例
DrawLegend();

}

void loop() {
CurTime = millis();

// 为温度区域绘制一个大的白色边框
Display.fillRect(10, 10, 220, 220, TFT_WHITE);

// 读取传感器
ThermalSensor.read_pixel_temperature(pixels);

// 现在我们有了一个8 x 8传感器数组
// 插值以获得更大的屏幕
// 插值8行(首先在8个传感器像素之间插值70个列点)
for (row = 0; row < 8; row ++) {
for (col = 0; col < 70; col ++) {
// 获取第一个数组点,然后是下一个
// 还需要为后续行增加8
aLow = col / 10 + (row * 8);
aHigh = (col / 10) + 1 + (row * 8);
// 获取每10列的插值量
// 这里我们做简单的线性插值,主要是为了保持高性能
// 显示器是5-6-5色彩调色板,所以精细插值会在低色彩深度中丢失
intPoint = (( pixels[aHigh] - pixels[aLow] ) / 10.0 );
// 确定每列增加多少(基本上是0-9)
incr = col % 10;
// 找到插值
val = (intPoint * incr ) + pixels[aLow];
// 存储在70 x 70数组中
// 由于显示器指向远处,反转行以转置行数据
HDTemp[ (7 - row) * 10][col] = val;

}
}

// 现在我们有了70列的原始数据
// 插值每个70列
// 忘记Arduino..远远不够快..Teensy在> 72 mhz是起点

for (col = 0; col < 70; col ++) {
for (row = 0; row < 70; row ++) {
// 获取第一个数组点,然后是下一个
// 还需要为后续列增加8
aLow = (row / 10 ) * 10;
aHigh = aLow + 10;
// 获取每10列的插值量
// 这里我们做简单的线性插值,主要是为了保持高性能
// 显示器是5-6-5色彩调色板,所以精细插值会在低色彩深度中丢失
intPoint = (( HDTemp[aHigh][col] - HDTemp[aLow][col] ) / 10.0 );
// 确定每列增加多少(基本上是0-9)
incr = row % 10;
// 找到插值
val = (intPoint * incr ) + HDTemp[aLow][col];
// 存储在70 x 70数组中
HDTemp[ row ][col] = val;
}
}


//显示70 x 70数组
DisplayGradient();

//屏幕中央的十字准线
Display.drawCircle(115, 115, 5, TFT_WHITE);
Display.drawFastVLine(115, 105, 20, TFT_WHITE);
Display.drawFastHLine(105, 115, 20, TFT_WHITE);

//将精灵推送到屏幕
Display.pushSprite(0, 0);

//显示屏幕中央的温度
tft.setRotation(3);
tft.setTextColor(TFT_WHITE);
tft.drawFloat(HDTemp[35][35], 2, 90, 20);

//取消注释以打印帧率
Serial.print("帧率: "); Serial.println(1/(0.001*(millis() - CurTime)));

}

// 显示结果的函数
void DisplayGradient() {

tft.setRotation(4);

// 遍历70行
for (row = 0; row < 70; row ++) {

// 绘制无闪烁网格的快速方法--只需将每10个像素设为2x2而不是3x3
// 在网格后绘制线条会闪烁太多
if (ShowGrid < 0) {
BoxWidth = 3;
}
else {
if ((row % 10 == 9) ) {
BoxWidth = 2;
}
else {
BoxWidth = 3;
}
}
// 然后遍历每70列
for (col = 0; col < 70; col++) {

// 绘制无闪烁网格的快速方法--只需将每10个像素设为2x2而不是3x3
if (ShowGrid < 0) {
BoxHeight = 3;
}
else {
if ( (col % 10 == 9)) {
BoxHeight = 2;
}
else {
BoxHeight = 3;
}
}
// 最后我们可以绘制每个70 x 70点,注意调用获取插值颜色
Display.fillRect((row * 3) + 15, (col * 3) + 15, BoxWidth, BoxHeight, GetColor(HDTemp[row][col]));
}
}

}

// 我的快速而有效的颜色插值例程
uint16_t GetColor(float val) {

/*
传入值并计算R G B
有几种已发布的方法来做这件事,我基本上绘制了R G B图并开发了简单的线性方程
再次,5-6-5颜色显示器不需要精确的温度到R G B颜色计算

基于以下方程
http://web-tech.ga-usa.com/2012/05/creating-a-custom-hot-to-cold-temperature-color-gradient-for-use-with-rrdtool/index.html

*/

red = constrain(255.0 / (c - b) * val - ((b * 255.0) / (c - b)), 0, 255);

if ((val > MinTemp) & (val < a)) {
green = constrain(255.0 / (a - MinTemp) * val - (255.0 * MinTemp) / (a - MinTemp), 0, 255);
}
else if ((val >= a) & (val <= c)) {
green = 255;
}
else if (val > c) {
green = constrain(255.0 / (c - d) * val - (d * 255.0) / (c - d), 0, 255);
}
else if ((val > d) | (val < a)) {
green = 0;
}

if (val <= b) {
blue = constrain(255.0 / (a - b) * val - (255.0 * b) / (a - b), 0, 255);
}
else if ((val > b) & (val <= d)) {
blue = 0;
}
else if (val > d) {
blue = constrain(240.0 / (MaxTemp - d) * val - (d * 240.0) / (MaxTemp - d), 0, 240);
}

// 使用显示器的颜色映射函数获取5-6-5色彩调色板(R=5位,G=6位,B=5位)
return Display.color565(red, green, blue);

}

// 获取温度与RGB图中截止点的函数
void Getabcd() {

a = MinTemp + (MaxTemp - MinTemp) * 0.2121;
b = MinTemp + (MaxTemp - MinTemp) * 0.3182;
c = MinTemp + (MaxTemp - MinTemp) * 0.4242;
d = MinTemp + (MaxTemp - MinTemp) * 0.8182;

}

// 绘制图例的函数
void DrawLegend() {

//带有最大值和最小值文本的颜色图例
j = 0;

float inc = (MaxTemp - MinTemp ) / 160.0;

for (ii = MinTemp; ii < MaxTemp; ii += inc) {
tft.drawFastHLine(260, 200 - j++, 30, GetColor(ii));
}

tft.setTextSize(2);
tft.setCursor(245, 20);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
sprintf(buf, "%2d/%2d", MaxTemp, (int) (MaxTemp * 1.8) + 32);
tft.print(buf);

tft.setTextSize(2);
tft.setCursor(245, 210);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
sprintf(buf, "%2d/%2d", MinTemp, (int) (MinTemp * 1.8) + 32);
tft.print(buf);

}

// 代码结束
Loading Comments...