接上回帖子https://forum.eepw.com.cn/thread/394067/1 驱动了模块,获得了温度矩阵,接下来就是如何将矩阵数据转换成可以直观观看的图像显示了。
一、插值:
从传感器获得的数据为32X24个点的温度矩阵,算算数据量其实也不小了,但是对图像来说,还是像素点太少了。为了显示的更加细腻,首先对数据进行插值处理。插值算法有很多种,这里列出关联比较密切的三种:
最近邻法(Nearest Interpolation):计算速度最快,但是效果最差。
双线性插值(Bilinear Interpolation):双线性插值是用原图像中4(2*2)个点计算新图像中1个点,效果略逊于双三次插值,速度比双三次插值快,属于一种平衡美,在很多框架中属于默认算法。
双三次插值(Bicubic interpolation):双三次插值是用原图像中16(4*4)个点计算新图像中1个点,效果比较好,但是计算代价过大。这里使用了双线型内插值算法。双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。
双线性内插值算法描述如下:对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)。其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。
float BilinearInterpolation(float q11, float q12, float q21, float q22, float x1, float x2, float y1, float y2, float x, float y) { float x2x1, y2y1, x2x, y2y, yy1, xx1; x2x1 = x2 - x1; y2y1 = y2 - y1; x2x = x2 - x; y2y = y2 - y; yy1 = y - y1; xx1 = x - x1; return 1.0 / (x2x1 * y2y1) * (q11 * x2x * y2y + q21 * xx1 * y2y + q12 * x2x * yy1 + q22 * xx1 * yy1); } // 插值算法 使用插值方式 放大点阵 void zoommix() { // 取4个点的值 float q11, q12, q21, q22, val; uint8_t x11, x22, y11, y22; for (uint8_t row = 0; row < imgwidth; row++) { // 遍历原始数据的每一个点 for (uint8_t col = 0; col < imgheight; col++) { q11 = pixels[row][col]; x11 = row * INSERT + row; y11 = col * INSERT + col; q12 = pixels[row][col + 1 == imgheight ? imgheight - 1 : col + 1]; q21 = pixels[row + 1 == imgwidth ? imgwidth - 1 : row + 1][col]; q22 = pixels[row + 1 == imgwidth ? imgwidth - 1 : row + 1][col + 1 == imgheight ? imgheight - 1 : col + 1]; x22 = (row + 1) * INSERT + row + 1; y22 = (col + 1) * INSERT + col + 1; // 插入值 for (uint8_t xpos = 0; xpos <= INSERT; xpos++) { for (uint8_t ypos = 0; ypos <= INSERT; ypos++) { if (x11 + xpos < dispwidth && y11 + ypos < dispheight) { val = BilinearInterpolation(q11, q12, q21, q22, x11, x22, y11, y22, x11 + xpos, y11 + ypos); disppixels[x11 + xpos][y11 + ypos] = GetColor(val); // image[(x11 + xpos)*(dispwidth)+y11 + ypos] =GetColor(BilinearInterpolation(q11, q12, q21, q22, x11, x22, y11, y22, x11 + xpos, y11 + ypos)); } } } } } }
这里将原来的32X24的点阵,扩大到156X120的矩阵。即每两个点之间插入4个模拟点数据。
二、伪彩色:
插值后,获得的是一个156X120的浮点数矩阵。将每个点的数据,使用颜色着色,用于屏幕显示。
有两个方案:1、取矩阵数据中的最大值、和最小值。然后将这个范围内的温度映射到颜色表上。这样的优点是细节表现强,但是颜色和温度无法对应。
2、取固定的温度做映射。这里将18℃~40℃这个范围映射到颜色表上,低于或高于这个温度的值,都使用最小值或最大值的颜色代替。这样优点是温度显示比较直观。
这就是使用伪彩色去观察空调获得的图像。
float MinTemp = 18.0, MaxTemp = 40.0, a, b, c, d, curmax, curmin, avg; uint16_t GetColor(float val); uint8_t lockflag = 0; // 按键锁 判断是否锁定温度与颜色的映射 void Getabcd(float mint, float maxt) { a = mint + (maxt - mint) * 0.2121; b = mint + (maxt - mint) * 0.3182; c = mint + (maxt - mint) * 0.4242; d = mint + (maxt - mint) * 0.8182; } // 从温度的矩阵中寻找最高温度和最低温度 void findmaxtemp() { curmax = curmin = pixels[0][0]; avg = 0; for (uint8_t i = 0; i < imgwidth; i++) { for (uint8_t j = 0; j < imgheight; j++) { if (pixels[i][j] > curmax) curmax = pixels[i][j]; if (pixels[i][j] < curmin) curmin = pixels[i][j]; avg += pixels[i][j]; } } avg /= (imgwidth * imgheight); if (lockflag) { Getabcd(MinTemp, MaxTemp); } else { Getabcd(curmin, curmax); } } // 浮点数转颜色 伪彩色 uint16_t GetColor(float val) { byte red = 0, green = 0, blue = 0; 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); } // use the displays color mapping function to get 5-6-5 color palet (R=5 bits, G=6 bits, B-5 bits) return M5.Lcd.color565(red, green, blue); }
三、屏幕显示:
通过插值、伪彩色最后得到一个156X120的位图图片。单片机使用的是M5StickC Plus。M5StickC Plus带有一块135X240的屏幕。通过对图片的旋转后最终做了显示。和伪彩色图片一并显示的还有最高温度、最低温度、以及平均温度。
void loop() { M5.update(); if (M5.BtnA.wasReleased()) { // If the button A is pressed. 如果按键 A 被按下 lockflag = !lockflag; } while (MLX90642_IsReadWindowOpen(SA_90642_DEFAULT)) { if (MLX90642_GetImage(SA_90642_DEFAULT, s_temp) == 0) { // 成功获得到温度矩阵 temp2pixels(); findmaxtemp(); // 计算最高温度和最低温度 zoommix(); //插值,扩大矩阵,获得伪彩色位图 // Serial.printf("[ %.1f , %.1f ] \r\n", MinTemp, MaxTemp); M5.Lcd.drawBitmap(7, 8, dispheight, dispwidth, pImage); // 屏幕上显示最高温度和最低温度 M5.Lcd.setCursor(182, 7); if (lockflag) M5.Lcd.print("LOCK"); else M5.Lcd.print("UNLO"); M5.Lcd.setCursor(182, 28); M5.Lcd.printf("%.1f", curmax); M5.Lcd.setCursor(182, 48); M5.Lcd.printf("%.1f", curmin); M5.Lcd.setCursor(182, 78); M5.Lcd.printf("%.1f", avg); } } // delay(10000); }
四、温度核对:
这里使用桌面温度计对比温度,桌面温度计显示为27.1℃,MLX90642测得的温度为27.0℃。还是挺准确的。
五、效果展示:
使用热水器接热水。
观察自己脑袋的图像,眼镜的镜片温度明显低于体温,旁边的方块是拍照用的手机。
不过动态图像速度有点点慢,每秒的帧率不高,但是作为日常工具使用,是足够了。