上期分享的《Arduino+MLX90640实现带有图像记录功能的热成像仪详解(上篇)》受到许多知友的关注和热烈评论,由于知乎的篇幅限制,所以文章没有一次分享完。本期继续为大家分享。

在本期内容中,我们将使用一种叫Processing的编程语言,将您捕获的图像变成引人入胜的、可以动起来的热图像电影。

Processing语言

单凭上期制作的相机就可以做很多事情,但是借助它捕获的数据,您可以探索更多神奇的事。 探索热图像的一种方法是使用在成熟的计算机上运行的Processing编程语言中的示例。 有关在Raspberry Pi上安装处理环境的Adafruit教程,其网站上也有Mac和Windows用户的链接。

Processing是一个灵活的软件速写本和一种学习如何在视觉艺术的背景下编程的语言。

自2001年以来,Processing已经促进了视觉艺术中的软件素养和技术中的视觉素养。

有成千上万的学生、艺术家、设计师、研究人员和爱好者使用Processing来学习和制作原型。

下载地址:https://processing.org/

循环检测示例

安装并设置了处理包之后,可以下载并尝试一下。打开一个新的草图窗口,复制下面的代码并粘贴到那里,并将其保存为ConvertBMPinspector01。确保您在Processing编程环境中工作,而不是在Arduino环境中。这是很容易犯的错误。

// ConvertBMPinspector01 – Read and enlarge a modified 32×24 24-bit gray BMP file,// display an upscaled 256×192 BMP image in 256 colors.// Ver. 1 – Fetch filenames and display BMPs in sequence.// Add nav buttons and mouseover pixel temperatures// This sketch does no checking for file compatibility.// Only frm_____.bmp images from the thermal camera sketch will work.// Any other files in the data folder will fail.importjava.util.Date;byteb[],colorPal[];// Buffers for input file bytes and for colorsinti,fileCount=0,BGcolor=48,colorMap=1,butnsX=30,butnsY=290,offsetX=153,offsetY=6,// These value pairs control where the onscreen features appearnumbersX=40,numbersY=48,probeX=190,probeY=210;booleancelsiusFlag=false;floatfixedPoint[];String[]filenames;voidsetup(){size(480,360);// Size must be the first statementbackground(BGcolor);// Clear the screen with a gray backgroundcolorPal=newbyte[1024];// Prepare a 1K color tableloadColorTable(colorMap,0);// Load color table, 1 == ironbow palettefixedPoint=newfloat[5];// A buffer for appended fixed point valuesStringpath=sketchPath()+“/data”;// Read from the “/data” subdirectoryfilenames=listFileNames(path);fileCount=filenames.length;i=0;if(fileCount<1){println(“No files found. Stopping.”);noLoop();}else{loadBMPscreen(i);// Read in the first frame for inspection}}voiddraw(){intsampleX,sampleY,pixelVal;floatsampleTemp;sampleX=(mouseXoffsetX)>>3;// Map mouse position to BMP pixel spacesampleY=23((mouseYoffsetY)>>3);noStroke();smooth();fill(BGcolor+16);rect(probeX,probeY,180,40);// Clear the interactive window spaceif((sampleX>=0)&&(sampleX<32)&&(sampleY>=0)&&(sampleY<24)){// Mouse within BMP image bounds?pixelVal=b[54+(32*sampleY+sampleX)*3]&0xff;// Read the 8-bit pixel valuefill(colorPal[4*pixelVal+2]&0xFF,colorPal[4*pixelVal+1]&0xFF,colorPal[4*pixelVal+0]&0xFF);rect(probeX,probeY,180,40);fill(BGcolor);rect(probeX+10,probeY+10,160,20);// Draw a colorized frame for the interactive temp readoutsampleTemp=(float(pixelVal)+1.0)/257.0*(fixedPoint[3]fixedPoint[1])+fixedPoint[1];if(!celsiusFlag)sampleTemp=sampleTemp*1.8+32.0;fill(255);// Ready to display white interactive texttextSize(11);text(sampleX,probeX+154,probeY+19);// Display X Y positiontext(sampleY,probeX+154,probeY+29);textSize(15);text(sampleTemp,probeX+60,probeY+25);// Display temperatureif(pixelVal==0&&fixedPoint[0]<fixedPoint[1])// Pixel values clipped at bottom limit?text(“<“,probeX+40,probeY+25);// Show out-of-range indicatorif(pixelVal==255&&fixedPoint[4]>fixedPoint[3])// Clipped at top?text(“>”,probeX+40,probeY+25);// Same}noSmooth();// Clear any highlighted buttonsstroke(0);noFill();for(sampleX=0;sampleX<8;++sampleX)rect(butnsX+sampleX*52,butnsY,51,24);sampleX=mouseXbutnsX;sampleY=mouseYbutnsY;if(sampleX>=0&&sampleX<416&&sampleY>=0&&sampleY<24){// Mouse over buttons?sampleX=sampleX/52;// Map mouse X to button X spacestroke(BGcolor+64);rect(butnsX+sampleX*52,butnsY,51,24);// Highlight border around a button}}voidkeyPressed(){// Load a different thermal BMP image based on keystrokeswitch(key){case.:// Next imagei=(i+1)%fileCount;break;case,:// Prev Imagei=(i+fileCount1)%fileCount;break;case>:// 16 images forwardi=i+16<fileCount?i+16:fileCount1;break;case<:// 16 images backi=i16<0?0:i16;break;case/:// Last imagei=fileCount1;break;casem:// First imagei=0;break;}loadBMPscreen(i);}voidmousePressed(){intsampleX,sampleY;sampleX=mouseXbutnsX;sampleY=mouseYbutnsY;if(sampleX>=0&&sampleX<416&&sampleY>=0&&sampleY<24){// Is mouse over button row?sampleX=sampleX/52;// Map mouse X to button X spaceswitch(sampleX){case1:// First imagei=0;break;case2:// 16 images backi=i16<0?0:i16;break;case3:// Prev Imagei=(i+fileCount1)%fileCount;break;case4:// Next imagei=(i+1)%fileCount;break;case5:// 16 images forwardi=i+16<fileCount?i+16:fileCount1;break;case6:// Last imagei=fileCount1;break;case7:// Change color maploadColorTable(colorMap=(colorMap+1)%5,0);// Load color tablebreak;default:// Toggle C/FcelsiusFlag=!celsiusFlag;break;}loadBMPscreen(i);}}voidloadBMPscreen(intfileIndex){intx,y;b=loadBytes(filenames[fileIndex]);// Open a file and read its 8-bit databackground(BGcolor);// Clear screenenlarge8bitColor();// Place colored enlarged image on screenfor(x=0;x<5;++x){// Rebuild 5 float values from next 4*n bytes in the filefixedPoint[x]=expandFloat(b[2360+(x*4)+0],b[2360+(x*4)+1],b[2360+(x*4)+2],b[2360+(x*4)+3]);}y=((b[2387]&0xff)<<24)+((b[2386]&0xff)<<16)+((b[2385]&0xff)<<8)+(b[2384]&0xff);// Reassemble a milliseconds time stamptextSize(10);// Print text labels for the frame statssmooth();fill(255);text(filenames[fileIndex],numbersX+5,numbersY+40);// Show current filenameif(celsiusFlag)text(“Frame\n\n\nSeconds\n\nDegrees C”,numbersX+5,numbersY+8);elsetext(“Frame\n\n\nSeconds\n\nDegrees F”,numbersX+5,numbersY+8);text(“Approximate temperatures based on 8-bit pixel values”,probeX42,probeY+52);// Show approximation disclaimertextSize(15);text(fileIndex,numbersX+5,numbersY+25);// Print frame numbertext(float(y)*0.001,numbersX,numbersY+74);// Print time stamp in secondsif(celsiusFlag){// Show 3 temps in Celsiusfill(255,128,64);text(fixedPoint[4],numbersX,numbersY+108);fill(255,200,64);text(fixedPoint[2],numbersX,numbersY+128);fill(128,128,255);text(fixedPoint[0],numbersX,numbersY+148);}else{// or show them in Farenheitfill(255,128,64);text(fixedPoint[4]*1.8+32.0,numbersX,numbersY+108);fill(255,200,64);text(fixedPoint[2]*1.8+32.0,numbersX,numbersY+128);fill(128,128,255);text(fixedPoint[0]*1.8+32.0,numbersX,numbersY+148);}noSmooth();stroke(0);fill(BGcolor+24);for(x=0;x<8;++x)// Draw 8 button rectanglesrect(butnsX+x*52,butnsY,51,24);for(x=0;x<50;++x){// Paint a mini colormap gradient within last buttony=int(map(x,0,50,0,255));stroke(colorPal[4*y+2]&0xFF,colorPal[4*y+1]&0xFF,colorPal[4*y+0]&0xFF);line(butnsX+365+x,butnsY+1,butnsX+365+x,butnsY+23);}smooth();// Add text labels to buttonsfill(255);textSize(15);text(“|< << < > >> >|”,butnsX+70,butnsY+17);if(celsiusFlag)text(“C”,butnsX+20,butnsY+18);elsetext(“F”,butnsX+20,butnsY+18);}voidenlarge8bitColor(){// Convert a small gray BMP array and plot an enlarged colormapped versionintx,y;noStroke();for(y=0;y<24;++y){// Count all source pixelsfor(x=0;x<32;++x){intpixMid=b[54+((32*y+x)+0)*3]&0xFF;fill(colorPal[4*pixMid+2]&0xFF,colorPal[4*pixMid+1]&0xFF,colorPal[4*pixMid+0]&0xFF);// Get color from tablerect(offsetX+8*x,offsetY+8*(23y),8,8);// Draw a square pixel, bottom up}}}voidloadColorTable(intchoiceNum,intoffset){inti,x;switch(choiceNum){case1:// Load 8-bit BMP color table with computed ironbow curvesfor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(1.02(fleX0.72)*(fleX0.72)*1.96);fleG=(fleG>255.0)||(fleX>0.75)?255.0:fleG;// Truncate curvei=(int)fleG;colorPal[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=fleX*fleX*255.9;i=(int)fleG;colorPal[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=255.9*(14.0*(fleX*fleX*fleX)20.0*(fleX*fleX)+7.0*fleX);fleG=fleG<0.0?0.0:fleG;// Truncate curvei=(int)fleG;colorPal[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case2:// Compute quadratic “firebow” palettefor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(1.00(fleX1.0)*(fleX1.0));i=(int)fleG;colorPal[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=fleX<0.25?0.0:(fleX0.25)*1.3333*255.9;i=(int)fleG;colorPal[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=fleX<0.5?0.0:(fleX0.5)*(fleX0.5)*1023.9;i=(int)fleG;colorPal[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case3:// Compute “alarm” palettefor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):1.0);i=(int)fleG;colorPal[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):(fleX0.875)*8.0);i=(int)fleG;colorPal[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):0.0);i=(int)fleG;colorPal[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case4:// Grayscale, black hotfor(x=0;x<256;++x){colorPal[offset+x*4+2]=byte(255x&0xFF);// Red valscolorPal[offset+x*4+1]=byte(255x&0xFF);// Grn valscolorPal[offset+x*4+0]=byte(255x&0xFF);// Blu vals}break;default:// Grayscale, white hotfor(x=0;x<256;++x){colorPal[offset+x*4+2]=byte(x&0xFF);// Red valscolorPal[offset+x*4+1]=byte(x&0xFF);// Grn valscolorPal[offset+x*4+0]=byte(x&0xFF);// Blu vals}}}// Rebuild a float from a fixed point decimal value encoded in 4 bytesfloatexpandFloat(bytem1,bytem2,bytee1,bytee2){intfracPart;floatfloatPart;fracPart=((e2&0xff)<<8)+(e1&0xff);// Reassemble 16-bit valuefloatPart=(float)fracPart/49152.0;// Convert into fractional portion of floatfracPart=((m2&0xff)<<8)+(m1&0xff);// Reassemble 16-bit valuereturn((float)fracPart+floatPart)1000.0;// Complete reconstructing original float}String[]listFileNames(Stringdir){// Return the filenames from a directory as an array of StringsFilefile=newFile(dir);if(file.isDirectory()){Stringnames[]=file.list();returnnames;}else// Its not a directoryreturnnull;}

同样,Processing和Arduino的编程接口非常相似,很容易将另一个错误地混淆。 实际上,两种环境都将示例程序存储在单独的Sketchbook文件夹中,有时在保存新示例时会出现错误的文件夹。 每次都要仔细检查。 您可能必须手动导航到正确的文件夹。

一旦您将这个示例以文件名保存在Sketchbook文件夹中,Processing就会将它放在同名的子文件夹中。当导入热图像数据集时,最好知道在哪里可以找到这个子文件夹。编辑器窗口可以通过在菜单栏中启动并选择Sketch>Show Sketch文件夹来为您快速打开,新文件将出现在该文件夹中。到目前为止,一切都很好。还有两幅草图要画。

更多的Processing示例

这是Processing中三个示例中的第二个。 打开另一个新窗口,将此代码粘贴到其中,然后将其另存为ConvertBMPto8bit。

// ConvertBMPto8bit – Read and enlarge a modified 32×24 24-bit gray BMP file,// write an upscaled 256×192 BMP image with a 256 color table.// Ver. 2 – Fetch filenames and convert all suitable BMPs we find.// Builds sequences suitable for online animated GIF convertersimportjava.util.Date;// BMP File Header, little end firstintBmpPSPHead[]={0x42,0x4D,// “BM” in hex0x36,0xC4,0x00,0x00,// File size, 502300x00,0x00,// reserved for app data 10x00,0x00,// reserved for app data 20x36,0x04,0x00,0x00// Offset of pixel 0, 1078};// BMP 8-bit DIB Header, little end firstintDIBHeadPSP1[]={0x28,0x00,0x00,0x00,// Header size, 400x00,0x01,0x00,0x00,// pixel width, 2560xC0,0x00,0x00,0x00,// pixel height, 1920x01,0x00,// color planes, 10x08,0x00,// bits per pixel, 80x00,0x00,0x00,0x00,// Compression method, 0==none0x00,0x00,0x00,0x00,// Raw bitmap data size, dummy 00x12,0x0B,0x00,0x00,// Pixels per meter H, 28340x12,0x0B,0x00,0x00,// Pixels per meter V, 28340x00,0x00,0x00,0x00,// Colors in palette, 0==default 2^n0x00,0x00,0x00,0x00// Number of important colors, 0};byteoutBytes[],b[];// Buffer for the input file bytesPImageimg;// Declare variable of type PImageintfileCount=0,imageIndex=0;String[]filenames;// “paletteChoice” selects a false color palette:// 0 == Grayscale, white hot// 1 == Ironbow// 2 == Firebow// 3 == Hot alarm// 4 == Grayscale, black hotintpaletteChoice=1;voidsetup(){inti,j,x,y;StringnameHead,nameTail;size(256,192);// Size must be the first statement// noStroke();frameRate(5);background(0);// Clear the screen with a black backgroundoutBytes=newbyte[50230];// 54 header + 1K colors + 12K pixelsStringpath=sketchPath()+“/data”;// Read from the “/data” subdirectoryprintln(“Listing filenames: “);filenames=listFileNames(path);println(filenames);fileCount=filenames.length;println(fileCount+” entries”);if(fileCount<1){println(“No images found. Stopping.”);}else{// Filenames exist in the directoryfor(i=0;i<fileCount;++i){// Test each namenameHead=filenames[i].substring(0,3);nameTail=filenames[i].substring(8);j=int(filenames[i].substring(3,8));if(nameHead.equals(“frm”)&&nameTail.equals(“.bmp”)&&j!=0)// Source “frm_____.bmp” found?enlarge8bit(i);// Process and write an enlarged 8-bit version}}noLoop();}voiddraw(){intcountX,countY;noSmooth();for(countY=0;countY<192;++countY){for(countX=0;countX<256;++countX){stroke(0xFF&outBytes[1078+(countY*256+countX)]);// Color from BMP bufferpoint(countX,191countY);// Draw a pixel, bottom up}}}voidenlarge8bit(intfileNumber){// Read a small gray “frm” BMP image and write an enlarged colormapped “out” BMPinti,x,y;b=loadBytes(filenames[fileNumber]);// Open a file and read its 8-bit datafor(i=0;i<14;++i)outBytes[i]=byte(BmpPSPHead[i]&0xFF);// Copy BMP header 1 into output bufferfor(i=0;i<40;++i)outBytes[i+14]=byte(DIBHeadPSP1[i]&0xFF);// Copy header 2loadColorTable(paletteChoice,54);// Load color table, 54 byte BMP header offsetfor(y=0;y<23;++y){// Bilinear interpolation, count the source pixels less onefor(x=0;x<31;++x){for(intyLirp=0;yLirp<9;++yLirp){intcorner0=b[54+((32*y+x)+32)*3]&0xFF;intcorner1=b[54+((32*y+x)+0)*3]&0xFF;intpixLeft=(corner0*yLirp+corner1*(8yLirp))>>3;// Lirp 1 endpoint from 2 L pixels,intcorner2=b[54+((32*y+x)+33)*3]&0xFF;intcorner3=b[54+((32*y+x)+1)*3]&0xFF;intpixRight=(corner2*yLirp+corner3*(8yLirp))>>3;// and the other from 2 R pixelsfor(intxLirp=0;xLirp<9;++xLirp){intpixMid=(pixRight*xLirp+pixLeft*(8xLirp))>>3;// Lirp between lirped endpoints, bilinear interpoutBytes[1078+y*2048+x*8+yLirp*256+xLirp+771]=byte(pixMid&0xFF);}}}}for(y=0;y<192;++y){// Pad out the empty side pixelsfor(x=0;x<4;++x){outBytes[1078+(3x)+256*y]=outBytes[1082+256*y];outBytes[1330+x+256*y]=outBytes[1329+256*y];}}for(x=0;x<256;++x){// Pad out the empty above/below pixelsfor(y=0;y<4;++y){outBytes[1078+256*(3y)+x]=outBytes[2102+x];outBytes[49206+256*y+x]=outBytes[48950+x];}}saveBytes(“data/out”+filenames[fileNumber].substring(3),outBytes);// Save a recolored 8-bit BMP as “out_____.bmp”}voidloadColorTable(intchoiceNum,intoffset){inti,x;switch(choiceNum){case1:// Load 8-bit BMP color table with computed ironbow curvesfor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(1.02(fleX0.72)*(fleX0.72)*1.96);fleG=(fleG>255.0)||(fleX>0.75)?255.0:fleG;// Truncate curvei=(int)fleG;outBytes[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=fleX*fleX*255.9;i=(int)fleG;outBytes[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=255.9*(14.0*(fleX*fleX*fleX)20.0*(fleX*fleX)+7.0*fleX);fleG=fleG<0.0?0.0:fleG;// Truncate curvei=(int)fleG;outBytes[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case2:// Compute quadratic “firebow” palettefor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(1.00(fleX1.0)*(fleX1.0));i=(int)fleG;outBytes[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=fleX<0.25?0.0:(fleX0.25)*1.3333*255.9;i=(int)fleG;outBytes[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=fleX<0.5?0.0:(fleX0.5)*(fleX0.5)*1023.9;i=(int)fleG;outBytes[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case3:// Compute “alarm” palettefor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):1.0);i=(int)fleG;outBytes[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):(fleX0.875)*8.0);i=(int)fleG;outBytes[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):0.0);i=(int)fleG;outBytes[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case4:// Grayscale, black hotfor(x=0;x<256;++x){outBytes[offset+x*4+2]=byte(255x&0xFF);// Red valsoutBytes[offset+x*4+1]=byte(255x&0xFF);// Grn valsoutBytes[offset+x*4+0]=byte(255x&0xFF);// Blu vals}break;default:// Grayscale, white hotfor(x=0;x<256;++x){outBytes[offset+x*4+2]=byte(x&0xFF);// Red valsoutBytes[offset+x*4+1]=byte(x&0xFF);// Grn valsoutBytes[offset+x*4+0]=byte(x&0xFF);// Blu vals}}}String[]listFileNames(Stringdir){// Return the filenames from a directory as an array of StringsFilefile=newFile(dir);if(file.isDirectory()){Stringnames[]=file.list();returnnames;}else// Its not a directoryreturnnull;}

第三个:ConvertBMPtoSeq01

只剩一个示例了!执行与前面相同的操作,将此代码保存为另一个名为ConvertBMPtoSeq01的新程序。

// ConvertBMPtoSeq01 – Read and enlarge a modified 32×24 24-bit gray BMP file,// saving 256×192 BMP images in 256 colors for converting to MOV.// Ver. 1 – Fetch filenames and scan all suitable BMPs we find for their time/temp data,// to set the scale for graphing these numbers through the MOV.importjava.util.Date;bytecolorPal[],b[];// Buffers for a color palette, and reading bytes from filesPImageimg;inti,fileCount=0,frameTotal=0,earlyFrame=0,lastFrame=0,hotLowFrame,hotHighFrame,coldLowFrame,coldHighFrame,targLowFrame,targHighFrame,framX1,framX2,coldY1,coldY2,targY1,targY2,hotY1,hotY2,offsetX=153,offsetY=6,numbersX=40,numbersY=30,graphX=8,graphY=342,histoX=410,histoY=342,histoH=140,histoW=64,BGcolor=48;floathottestLow,hottestHigh,coldestLow,coldestHigh,targetLow,targetHigh;String[]filenames;// Change the following values to customize the output images.// “paletteChoice” selects a false color palette:// 0 == Grayscale, white hot// 1 == Ironbow// 2 == Firebow// 3 == Hot alarm// 4 == Grayscale, black hotintpaletteChoice=1;booleanmarkersVisible=true,celsiusFlag=false,lirpSmoothing=true;voidsetup(){intx,y;floatfixedPoint[];StringnameHead,nameTail;size(480,360);// Size must be the first statementbackground(BGcolor);// Clear the screen with a gray backgroundnoSmooth();colorPal=newbyte[1024];// Reserve a 1K color tableloadColorTable(paletteChoice,0);// Load color tablefixedPoint=newfloat[5];// Buffer for added fixed point valuesStringpath=sketchPath()+“/data”;// Read from the “/data” subdirectory// println(“Listing filenames: “);filenames=listFileNames(path);// println(filenames);fileCount=filenames.length;// println(fileCount + ” entries”);if(fileCount<1){println(“No images found. Stopping.”);}else{// Filenames exist in the directory, convert what we can// First pass: Read the embedded times/temps and find maxes/mins for graphingprint(“Counting through files: “);for(i=0;i<fileCount;++i){// Test each filename for conformityif((i&0x3F)==0)print(i+“, “);nameHead=filenames[i].substring(0,3);nameTail=filenames[i].substring(8);if(nameHead.equals(“frm”)&&nameTail.equals(“.bmp”)&&int(filenames[i].substring(3,8))!=0){// Source “frm_____.bmp” found?b=loadBytes(filenames[i]);// Open a file and read its 8-bit datafor(x=0;x<5;++x){// Rebuild float values from next 4*n bytes in the filefixedPoint[x]=expandFloat(b[2360+(x*4)+0],b[2360+(x*4)+1],b[2360+(x*4)+2],b[2360+(x*4)+3]);// 2360 == headers + pixels + 2}y=((b[2387]&0xff)<<24)+((b[2386]&0xff)<<16)+((b[2385]&0xff)<<8)+(b[2384]&0xff);// Reassemble a uint32_t millis() stampif(++frameTotal==1){// First frame found so far?coldestLow=coldestHigh=fixedPoint[0];targetLow=targetHigh=fixedPoint[2];// Initialize all valueshottestLow=hottestHigh=fixedPoint[4];hotLowFrame=hotHighFrame=coldLowFrame=coldHighFrame=targLowFrame=targHighFrame=earlyFrame=lastFrame=y;}else{// Compare everything, update where necessaryif(y<earlyFrame)earlyFrame=y;// These will set the left and right boundselseif(y>lastFrame)// of the temperature over time graphslastFrame=y;if(fixedPoint[0]<coldestLow){// These will define the high and low boundscoldestLow=fixedPoint[0];coldLowFrame=y;}elseif(fixedPoint[0]>coldestHigh){coldestHigh=fixedPoint[0];coldHighFrame=y;}if(fixedPoint[2]<targetLow){targetLow=fixedPoint[2];targLowFrame=y;}elseif(fixedPoint[2]>targetHigh){targetHigh=fixedPoint[2];targHighFrame=y;}if(fixedPoint[4]<hottestLow){hottestLow=fixedPoint[4];hotLowFrame=y;}elseif(fixedPoint[4]>hottestHigh){hottestHigh=fixedPoint[4];hotHighFrame=y;}}}}println(i+“, done.\n”);// The high and low points of three datasets are found, display themprintln(“Frame times “+earlyFrame+” to “+lastFrame+” totaling “+(lastFrameearlyFrame));println(“Cold values “+coldestLow+” at “+coldLowFrame+” to “+coldestHigh+” at “+coldHighFrame);println(“Targ values “+targetLow+” at “+targLowFrame+” to “+targetHigh+” at “+targHighFrame);println(“Hot values “+hottestLow+” at “+hotLowFrame+” to “+hottestHigh+” at “+hotHighFrame);stroke(BGcolor+48);for(y=0;y<=140;y+=35)line(graphX,graphYy,graphX+400,graphYy);// Draw a generic grid for the time graphfor(x=0;x<=400;x+=40)line(graphX+x,graphY140,graphX+x,graphY);noStroke();// Text labels for the top & bottom temp values of the graphtextSize(10);fill(255);if(celsiusFlag){text(hottestHigh,graphX+402,graphY142);text(coldestLow,graphX+402,graphY+12);}else{text(hottestHigh*1.8+32.0,graphX+402,graphY142);text(coldestLow*1.8+32.0,graphX+402,graphY+12);}fill(BGcolor+128);// Predraw 6 little high/low markers in the graph spacerect(graphX+400*(coldLowFrameearlyFrame)/(lastFrameearlyFrame)1,graphYint((coldestLowcoldestLow)/(coldestLowhottestHigh)*140.0)1,3,3);rect(graphX+400*(coldHighFrameearlyFrame)/(lastFrameearlyFrame)1,graphYint((coldestLowcoldestHigh)/(coldestLowhottestHigh)*140.0)1,3,3);rect(graphX+400*(targLowFrameearlyFrame)/(lastFrameearlyFrame)1,graphYint((coldestLowtargetLow)/(coldestLowhottestHigh)*140.0)1,3,3);rect(graphX+400*(targHighFrameearlyFrame)/(lastFrameearlyFrame)1,graphYint((coldestLowtargetHigh)/(coldestLowhottestHigh)*140.0)1,3,3);rect(graphX+400*(hotLowFrameearlyFrame)/(lastFrameearlyFrame)1,graphYint((coldestLowhottestLow)/(coldestLowhottestHigh)*140.0)1,3,3);rect(graphX+400*(hotHighFrameearlyFrame)/(lastFrameearlyFrame)1,graphYint((coldestLowhottestHigh)/(coldestLowhottestHigh)*140.0)1,3,3);}i=0;}// Second pass: Read each frame again, plot color mapped enlarged image, temperature values and graph, save each framevoiddraw(){intx,y,histogram[];floattempY,fixedPoint[];StringnameHead,nameTail;noSmooth();fixedPoint=newfloat[5];// Buffer for appended fixed point valueshistogram=newint[256];// Buffer for color histogramfor(x=0;x<256;++x)histogram[x]=0;// Initialize histogramif(i<fileCount){// Test each filename for conformitynameHead=filenames[i].substring(0,3);nameTail=filenames[i].substring(8);if(nameHead.equals(“frm”)&&nameTail.equals(“.bmp”)&&int(filenames[i].substring(3,8))!=0){// Source “frm_____.bmp” found?b=loadBytes(filenames[i]);// Open a file and read its 8-bit data// println(i + ” ” + filenames[i]);enlarge8bitColor();// Place colored enlarged image on screenfor(x=0;x<5;++x){// Rebuild float values from next 4*n bytes in the filefixedPoint[x]=expandFloat(b[2360+(x*4)+0],b[2360+(x*4)+1],b[2360+(x*4)+2],b[2360+(x*4)+3]);}y=((b[2387]&0xff)<<24)+((b[2386]&0xff)<<16)+((b[2385]&0xff)<<8)+(b[2384]&0xff);// Reassemble a milliseconds time stampsmooth();framX2=graphX+400*(yearlyFrame)/(lastFrameearlyFrame);coldY2=graphYint((coldestLowfixedPoint[0])/(coldestLowhottestHigh)*140.0);// Map data values into graph spacetargY2=graphYint((coldestLowfixedPoint[2])/(coldestLowhottestHigh)*140.0);hotY2=graphYint((coldestLowfixedPoint[4])/(coldestLowhottestHigh)*140.0);if(i==0){framX1=framX2;// Set starting points for 3 graphscoldY1=coldY2;targY1=targY2;hotY1=hotY2;}stroke(128,128,255);line(framX1,coldY1,framX2,coldY2);// Graph cold data pointstroke(255,200,64);line(framX1,targY1,framX2,targY2);// Graph center data pointstroke(255,128,64);line(framX1,hotY1,framX2,hotY2);// Graph hot data pointframX1=framX2;// Remember endpoints of graphed linescoldY1=coldY2;targY1=targY2;hotY1=hotY2;noStroke();// Print key values onscreen for current framefill(BGcolor);rect(numbersX,numbersY,82,152);// Erase number regionfill(BGcolor+32);// A color to highlight any extreme valuesif(y==hotLowFrame||y==hotHighFrame)rect(numbersX,numbersY+95,80,16);if(y==targLowFrame||y==targHighFrame)rect(numbersX,numbersY+115,80,16);if(y==coldLowFrame||y==coldHighFrame)rect(numbersX,numbersY+135,80,16);textSize(10);fill(255);text(filenames[i],numbersX+5,numbersY+40);// Show current filenameif(celsiusFlag)text(“Frame\n\n\nElapsed sec\n\nDegrees C”,numbersX+5,numbersY+8);elsetext(“Frame\n\n\nElapsed sec\n\nDegrees F”,numbersX+5,numbersY+8);textSize(15);text(i,numbersX+5,numbersY+25);// Print frame numbertext(float(yearlyFrame)*0.001,numbersX,numbersY+74);// Print elapsed timeif(celsiusFlag){// Print temps in Celsiusfill(255,128,64);text(fixedPoint[4],numbersX,numbersY+108);fill(255,200,64);text(fixedPoint[2],numbersX,numbersY+128);fill(128,128,255);text(fixedPoint[0],numbersX,numbersY+148);}else{// or print them in Farenheitfill(255,128,64);text(fixedPoint[4]*1.8+32.0,numbersX,numbersY+108);fill(255,200,64);text(fixedPoint[2]*1.8+32.0,numbersX,numbersY+128);fill(128,128,255);text(fixedPoint[0]*1.8+32.0,numbersX,numbersY+148);}for(x=0;x<768;++x)++histogram[b[54+3*x]&0xFF];// Count all colorsframX2=histogram[0];for(x=1;x<256;++x){// Find most numerous colorif(histogram[x]>framX2){framX2=histogram[x];targY2=x;}}fill(BGcolor);rect(histoX,histoY140,histoW,histoH+1);// Erase histogram regionfor(y=0;y<256;++y){if(histogram[y]>0){tempY=float(y)*(fixedPoint[3]fixedPoint[1])/255.0+fixedPoint[1];// Convert a 8-bit value to a temperaturetempY=float(histoH)*(coldestLowtempY)/(coldestLowhottestHigh);// Position it on the graph Y axisstroke(colorPal[4*y+2]&0xFF,colorPal[4*y+1]&0xFF,colorPal[4*y+0]&0xFF);// Color map the strokeline(histoX,histoYint(tempY),histoX+(histoW1)*histogram[y]/framX2,histoYint(tempY));// Draw a line proportional to the pixel count}noStroke();noSmooth();textSize(10);if(targY2<0x80)// Histogram peak in the dark side?fill(255);// Set contrasting test to whiteelsefill(0);tempY=float(targY2)*(fixedPoint[3]fixedPoint[1])/255.0+fixedPoint[1];// Convert a 8-bit value to a temperatureif(celsiusFlag)// Print the Y-positioned float value in C?text(tempY,histoX,histoY+3int(float(histoH)*(coldestLowtempY)/(coldestLowhottestHigh)));elsetext(tempY*1.8+32.0,histoX,histoY+3int(float(histoH)*(coldestLowtempY)/(coldestLowhottestHigh)));}saveFrame(“mov.jpg”);// Save the image into a sequence for Movie Maker}++i;}}voidenlarge8bitColor(){// Convert a small gray BMP array and plot an enlarged colormapped versionintx,y;if(lirpSmoothing){// Bilinear interpolation?for(y=0;y<23;++y){// Count the source pixels less onefor(x=0;x<31;++x){for(intyLirp=0;yLirp<9;++yLirp){intcorner0=b[54+((32*y+x)+32)*3]&0xFF;intcorner1=b[54+((32*y+x)+0)*3]&0xFF;intpixLeft=(corner0*yLirp+corner1*(8yLirp))>>3;// Lirp 1 endpoint from 2 L pixels,intcorner2=b[54+((32*y+x)+33)*3]&0xFF;intcorner3=b[54+((32*y+x)+1)*3]&0xFF;intpixRight=(corner2*yLirp+corner3*(8yLirp))>>3;// and the other from 2 R pixelsfor(intxLirp=0;xLirp<9;++xLirp){intpixMid=(pixRight*xLirp+pixLeft*(8xLirp))>>3;// Lirp between lirped endpoints, bilinear interpstroke(colorPal[4*pixMid+2]&0xFF,colorPal[4*pixMid+1]&0xFF,colorPal[4*pixMid+0]&0xFF);point(offsetX+4+8*x+xLirp,offsetY+188(8*y+yLirp));// Draw a pixel, bottom up}}}}for(y=0;y<192;++y){// Pad out the empty side pixelsstroke(get(offsetX+4,offsetY+y));line(offsetX+0,offsetY+y,offsetX+3,offsetY+y);stroke(get(offsetX+252,offsetY+y));line(offsetX+253,offsetY+y,offsetX+255,offsetY+y);}for(x=0;x<256;++x){stroke(get(offsetX+x,offsetY+4));line(offsetX+x,offsetY+0,offsetX+x,offsetY+3);stroke(get(offsetX+x,offsetY+188));line(offsetX+x,offsetY+189,offsetX+x,offsetY+191);}}else{// Plain square pixelsnoStroke();for(y=0;y<24;++y){// Count all source pixelsfor(x=0;x<32;++x){intpixMid=b[54+((32*y+x)+0)*3]&0xFF;fill(colorPal[4*pixMid+2]&0xFF,colorPal[4*pixMid+1]&0xFF,colorPal[4*pixMid+0]&0xFF);// Get color from tablerect(offsetX+8*x,offsetY+8*(23y),8,8);// Draw a pixel, bottom up}}}if(markersVisible){// Show the green marker crosses?stroke(0,192,0);// Deep greeny=((b[2381]&0xff)<<8)+(b[2380]&0xff);// Reassemble 16-bit addresses of cold / hot pixelsline(offsetX+8*(y&31)+1,offsetY+1888*(y>>5),offsetX+8*(y&31)+7,offsetY+1888*(y>>5));line(offsetX+8*(y&31)+4,offsetY+1858*(y>>5),offsetX+8*(y&31)+4,offsetY+1918*(y>>5));y=((b[2383]&0xff)<<8)+(b[2382]&0xff);line(offsetX+8*(y&31)+1,offsetY+1888*(y>>5),offsetX+8*(y&31)+7,offsetY+1888*(y>>5));line(offsetX+8*(y&31)+4,offsetY+1858*(y>>5),offsetX+8*(y&31)+4,offsetY+1918*(y>>5));y=400;line(offsetX+8*(y&31)+1,offsetY+1888*(y>>5),offsetX+8*(y&31)+7,offsetY+1888*(y>>5));line(offsetX+8*(y&31)+4,offsetY+1858*(y>>5),offsetX+8*(y&31)+4,offsetY+1918*(y>>5));}}voidloadColorTable(intchoiceNum,intoffset){inti,x;switch(choiceNum){case1:// Load 8-bit BMP color table with computed ironbow curvesfor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(1.02(fleX0.72)*(fleX0.72)*1.96);fleG=(fleG>255.0)||(fleX>0.75)?255.0:fleG;// Truncate curvei=(int)fleG;colorPal[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=fleX*fleX*255.9;i=(int)fleG;colorPal[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=255.9*(14.0*(fleX*fleX*fleX)20.0*(fleX*fleX)+7.0*fleX);fleG=fleG<0.0?0.0:fleG;// Truncate curvei=(int)fleG;colorPal[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case2:// Compute quadratic “firebow” palettefor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(1.00(fleX1.0)*(fleX1.0));i=(int)fleG;colorPal[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=fleX<0.25?0.0:(fleX0.25)*1.3333*255.9;i=(int)fleG;colorPal[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=fleX<0.5?0.0:(fleX0.5)*(fleX0.5)*1023.9;i=(int)fleG;colorPal[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case3:// Compute “alarm” palettefor(x=0;x<256;++x){floatfleX=(float)x/255.0;floatfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):1.0);i=(int)fleG;colorPal[offset+x*4+2]=byte(i&0xFF);// Red valsfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):(fleX0.875)*8.0);i=(int)fleG;colorPal[offset+x*4+1]=byte(i&0xFF);// Grn valsfleG=255.9*(fleX<0.875?1.00(fleX*1.1428):0.0);i=(int)fleG;colorPal[offset+x*4+0]=byte(i&0xFF);// Blu vals}break;case4:// Grayscale, black hotfor(x=0;x<256;++x){colorPal[offset+x*4+2]=byte(255x&0xFF);// Red valscolorPal[offset+x*4+1]=byte(255x&0xFF);// Grn valscolorPal[offset+x*4+0]=byte(255x&0xFF);// Blu vals}break;default:// Grayscale, white hotfor(x=0;x<256;++x){colorPal[offset+x*4+2]=byte(x&0xFF);// Red valscolorPal[offset+x*4+1]=byte(x&0xFF);// Grn valscolorPal[offset+x*4+0]=byte(x&0xFF);// Blu vals}}}// Rebuild a float from a fixed point decimal value encoded in 4 bytesfloatexpandFloat(bytem1,bytem2,bytee1,bytee2){intfracPart;floatfloatPart;fracPart=((e2&0xff)<<8)+(e1&0xff);// Reassemble 16-bit valuefloatPart=(float)fracPart/49152.0;// Convert into fractional portion of floatfracPart=((m2&0xff)<<8)+(m1&0xff);// Reassemble 16-bit valuereturn((float)fracPart+floatPart)1000.0;// Complete reconstructing original float}String[]listFileNames(Stringdir){// Return the filenames from a directory as an array of StringsFilefile=newFile(dir);if(file.isDirectory()){Stringnames[]=file.list();returnnames;}else// Its not a directoryreturnnull;}

差不多了!我们的下一步是把相机的热图像数据带进来,这样我们就可以用它做很酷的事情。

获取照片

你的相机捕捉到的图像数据写在闪存中,可以是在可移动的microSD卡中,也可以是在内置的QSPI闪存中。microSD卡可以用标准microSD阅读器取出和访问。板上的QSPI闪存应该出现作为一个外部驱动器到一个普通的计算机,当板子被USB连接。无论哪种方式,请继续将您的设备插入处理代码所在的计算机,并准备复制一些数据。

在开始浏览闪存驱动器时,您应该在根目录中看到一个名为MLX90640的文件夹。 这是相机示例程序存储其所有文件的位置。 打开它,查看一下。

在此文件夹中,您可能会发现两件事:BMP文件和更多文件夹,它们的名称均包含序列号。 该文件夹应包含更多编号的BMP文件。 BMP文件是我们一次要复制和粘贴的一组。 无论是在MLX90640文件夹中还是在dir 文件夹之一中,任何序列的图像都是一个不错的选择。

由于摄像机示例处于运行状态,因此可以在连接时捕获更多BMP图像,但是Arcada功能可防止在运行时访问新捕获的图像。 这是正常现象,并且可以防止错误。 只需重新启动相机程序即可打开最新图像。

现在打开名为ConvertBMPinspector01的处理草图,但不要运行它。 而是通过选择Sketch>Show sketch文件夹从菜单中打开其主文件夹。 您的操作系统应显示一个文件管理窗口,其中显示草图本身的文件名。 下一步,如果还不存在,则需要创建另一个名为data的文件夹。 打开该数据文件夹,因为这是BMP映像所在的位置。 复制/粘贴很简单,只有编号为frm 。bmp的编号文件集才是粘贴到数据文件夹中的唯一内容。

(这些步骤也将在使用其他两个处理程序之前进行。)

使用ConvertBMPinspector01

继续并运行ConvertBMPinspector01程序。 您应该会看到一个打开的窗口,其中显示了一些数字值,放大的彩色热图像以及下面的一些控制按钮。

屏幕现在由您来浏览。 左侧的统计信息显示文件和温度详细信息,例如文件名,序列中的帧数,时间戳显示捕获时已运行的秒数,以及传感器检测到的三个确切温度(最热像素,最冷像素和中心像素[ 16、12])。

使用指针,您可以将鼠标悬停在图像本身上,并使任何像素的颜色和近似温度显示在下面的矩形框中。 同时显示了像素的XY位置。 尽管左侧温度显示的是精确测量的值,但交互式像素温度是近似值,因为它们是根据8位颜色值重新计算的。 您可以通过比较pixel [16,12]看到这一点。 返回的温度不一样,但是非常接近。

有时会使用预设的颜色范围来捕获图像,但实际温度可能会超出该范围。 显示屏将使用>或<符号指示颜色达到最大(或最小化)的颜色。 左侧数字不受此影响。

底部的控制按钮非常简单。 最左侧在以华氏度或摄氏度显示的温度之间切换。 最右边将在几个错误的调色板之间循环,以增强细节的可见性。 两者之间的按钮用于以小步或大步逐步浏览图像文件序列。 键盘也可以使用,来管理这些控件。 <> m /键。 (程序可用于检查单个图像,但是一系列图像更有趣。)

检测图像动起来

随着时间的推移记录一系列热图像意味着可以进行基于时间的分析,但是有趣的部分是看到图片移动! 让我们将一系列BMP图像转换成这样的彩色运动GIF格式图像。

使用ConvertBMPto8bit

这个程序将与最后一个程序一起开始。 在处理中打开程序,但不要运行它。 使用Sketch>Show sketch、文件夹打开主文件夹,然后在其中定位或创建数据文件夹。 从相机的闪存中选择一系列BMP图像,然后将其复制/粘贴到数据文件夹中。 然后运行ConvertBMPto8bit。

发生什么事了吗? 一些文本出现在底部主窗口的输出部分,然后一个灰色的热图像出现在微型窗口中,此后没有其他变化。 是坏了吗?

没有。 那正是应该发生的事情。 您可以停止程序。

这是本程序的作用。 它读取每个微小的灰色BMP图像,使用双线性插值法放大并平滑像素,从256调色板中为图像着色,并使用新的配色方案将其保存为新的8位BMP文件在数据文件夹中。 现在,该文件夹应包含的BMP文件数量是以前的两倍。

修改颜色

看看其中一些新的BMP图像。 如果调色板不适合您,则ConvertBMPto8bit还有其他选项。 在处理窗口中查找这些行。

// “paletteChoice” selects a false color palette: // 0 == Grayscale, white hot // 1 == Ironbow // 2 == Firebow // 3 == Hot alarm // 4 == Grayscale, black hot int paletteChoice = 1;

最后一行将变量paletteChoice设置为1,但还有其他四个选项可用。 两个是灰度,一个是暖色调中的从暗到亮的渐变,热警报是从浅到暗的灰度,在顶部切换为红黄色高光,强调最热的颜色。 为变量尝试不同的值,然后查看您喜欢的颜色。

熟悉Processing的编码人员甚至可以修改名为loadColorTable()的函数并生成自己的自定义调色板。

对新的BMP图像满意后,可以将其上传到http://ezgif.com之类的在线GIF创建网站,在该网站上可以将它们组合为动态GIF,并对其大小,速度,压缩和特殊效果进行调整。 然后,您可以发送或发布您的动态影像。 用您的火舞动作让您的小伙伴们眼花缭乱吧!

Using ConvertBMPtoSeq01

在这里,您的热图像有了非常强的科技感。 创建基于时间的图形和统计信息的彩色运动,准备进行详细的检查,这让我感觉就像Spock先生一样。

你知道现在的练习了吧? 打开程序,然后将下载的BMP粘贴到其数据文件夹中,然后再运行。 你说对了。 这次,随着示例程序完成其工作,图像在图形窗口中闪烁。 到底在做什么 它像以前一样对图像进行放大和着色,然后在其周围放置数字和图形以增加酷炫感。

! 与其他示例不同,此示例至少能处理2个BMP文件。 另外,不是来自录制序列的随机文件也不会很好。 您可以尝试使用它们,但是它们会产生紊乱的图。

出现每个图像时,它会以JPG文件的形式保存在示例文件夹中(不在数据文件夹中,而是在其旁边)。 源BMP的序列产生JPG的序列。 示例在序列的末尾冻结,最后一张图像可见。 然后,您可以停止示例(但不要关闭它)。

在图像管理器中打开mov .jpg文件,并查看它。 线条和颜色可提供有关图片中温度的真实信息,但接下来让我们将其放入QuickTime电影中,然后再仔细看一看。

图像生成影视

Processing语言在其工具集中具有QuickTime电影制作功能。 您可以通过在工具菜单下找到它来打开它。 这将打开一个界面窗口。 这是显示我用于手印电影的设置的一个。

! 这些说明不建议使用JPG序列作为创建QuickTime的来源,但是无论如何我还是这样做的,我发现质量还可以接受。 您可以根据需要修改示例以输出TIFF或PNG图像。 这个由你来决定。

在界面中有一个文本输入框,用于选择包含JPG图像的文件夹的路径。 示例文件夹的路径就是其中的路径,您可以使用选择…按钮进行查找。 您可以尝试其他选择。 查看最适合您的方法。

准备就绪后,单击创建电影…,为文件输入一个名称,然后观看其工作情况。 (您创建的电影可能会在其旁边带有TMP文件。不再需要它,您可以将其丢弃。)完成后,QuickTime播放器可以打开新电影并以华丽的动画播放。 现在您就是热图像电影摄影师!

数据挖掘

QuickTime播放器可以轻松地一次将影片一帧一帧呈的现,以进行更仔细的观察。 这是添加到每个记录的BMP中的那些额外字节传递其信息的地方。 将电影暂停在有趣的帧上,然后再看一看。

数值列的右侧是彩色图像,在它们下方是两个图表,一个基本的时间图和一个柱状图。

红色/金色/蓝色三个数字值显示了我们最喜爱的三个温度,表示每帧图像中最热/适中/最冷。 该图显示了这些值如何随时间变化。 六个小点标记每行的最高点和最低点,到达该点的每个时刻,都会用一个简短的灰色框突出显示该数字。 这使得在电影中很容易找到最热或最冷的东西。

柱状图显示了图像中8位像素值的聚集计数,聚集最多的像素值被转换回温度并叠印在图上。整个都是自动缩放的,所以没有值会跳出图表。(说读数超出了图表听起来太夸张了。对不起,我不允许你这么做。)

编辑您的电影

ConvertBMPtoSeq01在开头附近有一小段代码,就像前面描述的那样。 与ConvertBMPto8bit一样,它允许将不同的假色调色板用于颜色输出。

// Change the following values to customize the output images. // “paletteChoice” selects a false color palette: // 0 == Grayscale, white hot // 1 == Ironbow // 2 == Firebow // 3 == Hot alarm // 4 == Grayscale, black hot int paletteChoice = 1; boolean markersVisible = true, celsiusFlag = false, lirpSmoothing = true;

接下来的三个布尔值也可以修改你的输出结果。 这些选项可以隐藏标记出极限像素的绿色十字,以摄氏度显示温度,并禁用图像像素的双线性平滑。

在此代码块上方是甚至可以更改的预定义值,这些值控制图像中各个元素的位置。 如果需要,您可以定义自己的屏幕数据显示安排。探索你自己的热电影世界!

未来工作

这个项目已经为我们提供了很多的乐趣和学习机会,但这只是一个基本工具,正在等待有创造力的人进行扩展。 会变成什么?

我可以告诉你我的计划。 我计划添加相机内像素平滑处理,就像处理示例所提供的那样。 我计划将相机设置存储在配置文件中,并使其以记住的最后设置启动。 我计划学习如何控制发射率值,以从散热不良的表面(如薄板和有光泽的金属)上感应更准确的温度。 我打算研究Arcada设备和MLX90640的Adafruit库,看看是否可以加快某些相机功能。 简而言之,我的计划是持续改善已有的工作。

看到这里,我们的分享内容就暂告一个段落了。

您有什么计划? 您是想利用ESP32 FeatherWing并制作一个无线热图像网络摄像头吗? 您可以通过USB串行接口将图像数据发送到用于科学展览的大屏幕热像仪或热照相亭的常规计算机吗? 热成像仪安全哨兵或婴儿监护人? 相信许多人的创造力胜过一个人的创造力。

感谢您抽出宝贵的时间来读完这篇长达2万多字的分享文篇,来探索这个小项目,如果您确实创建了自己的热成像仪并开始捕获有趣的图像和电影,请记得和我们一起分享哦。 进行一次伟大的冒险以激励其他人!