2020-11-29 18:23:05 [显示全部楼层]
7098浏览
查看: 7098|回复: 11

[项目] 【光谱传感器测评】家庭常用光源对比

[复制链接]

引言

看过《石纪元》的小伙伴可能会喜欢里面的这样一句话:“在我们的时代里,不存在黑暗!”。自从爱迪生于1879年发明了世界上第一支具有实用价值的电灯泡,电灯已经有140多年的发展历史。对于人类历史来说,这只是短短的一瞬,但是这并不妨碍电灯成为人类历史上最重要的发明。在此期间,人类对电灯进行了多次的迭代,很多技术层出不穷。在这篇文章里我将用DFRobot出品的 AS7341 可见光谱传感器对家庭常用的几种灯泡进行详细对比。

原理

首先,我们要了解关于光的一些基本知识。众所周知,太阳光是由赤橙黄绿青蓝紫其中颜色的光组成,然而这种表述并不准确,太阳光是由一段连续的光谱组成,能完整地覆盖人眼可见光的范围。


人类之所以能看到这个五彩缤纷的世界,全要感谢眼睛里的视杆细胞和视锥细胞,视杆细胞对弱光敏感,擅长在黑暗的环境下看清物体轮廓,夜行动物的眼睛多富有视杆细胞;相对的视锥细胞则能对不同颜色的光产生反应,还原物体的颜色。人类视网膜上存在着红、绿、蓝三种视锥细胞,一束光会对这三种视锥细胞造成不同程度的刺激,这个刺激值在大脑中就会被还原成光的颜色。也就是说,颜色是人大脑中的概念,如果你将红绿蓝三原色以正确的比例混合,就能够得到任何你想要的颜色,你眼前手机的、电脑的屏幕正是利用这个原理才能显示出各种各样的颜色的。

接下来是色温。从理论上说,任何有温度的物体都会主动向外界辐射电磁波,且温度越高,波长越短,看起来就是从黑变红,再到黄、白,最后发出蓝色光,打铁时铁受热会发红,煤气灶点然后发出的蓝光都是因为这个原理。普通白炽灯正常工作时,灯丝可达2500摄氏度,转换成绝对温标就是2773K,这就是白炽灯产生的光的色温,是一种黄白色的光。现代的很多灯泡像是荧光灯、LED灯采用了不同的发光原理,但是灯泡的包装上可能会写着“色温5000K”之类的字样,这并不是说灯泡在工作时,内部产生了那么高的温度,而是指光的颜色等效于同等温度下黑体产生的光的颜色,也就是相关色温的概念。
要理解计算色温非常复杂,但是这里我采用公式法拟合,将过程简化成了下面几个步骤:

1.为了计算光的色温,我们需要利用传感器获得这个光的光谱,以及人眼三种视锥细胞对不同波长的光的敏感程度——三刺激函数。三刺激函数可以通过查表获得。

  1. 光谱分别与三刺激函数相乘再进行积分就能得到三刺激值(X、Y、Z)。
  2. 通过公式转换为色坐标(x-y),任何一种颜色都能在色坐标中找到。
  3. 最后再通过色坐标计算色温。

代码实现

为了能更直观地看到光谱数据,我用Processing写了一个程序。

/***************************************************************************
Created by dbc0301
***************************************************************************/

import processing.serial.*;
Serial port;

PFont myFont;

int tmp;
//int begin='$';//begin
int end='\r';//end
char rev[] = new char[15];//datas
int revFlag=0;

int[] data=new int[10];//F1,F2,F3,F4,F5,F6,F7,F8,Clear,NIR
//int F1,F2,F3,F4,F5,F6,F7,F8,Clear,NIR;

//刺激函数
float[] Fx={0.07763, 0.34806, 0.09564, 0.02910, 0.51205, 1.02630, 0.64240, 0.04677};
float[] Fy={0.00218, 0.02980, 0.13902, 0.60820, 1.00000, 0.75700, 0.26500, 0.01700};
float[] Fz={0.37130, 1.78260, 0.81295, 0.11170, 0.00575, 0.00110, 0.00005, 0.00000};

int textHight=25;
float rt=1;//长度缩放比例

void receiveDatas(){
    for(int i=0;port.available()>0;i++){
      tmp=port.read();
      if(tmp!=end){
        rev=char(tmp);
      }else{
        rev=char(tmp);
        revFlag=1;
        tmp=port.read();
        break;
      }
    }
}

void setup(){
  //size(1074,241);
  size(1250,301);
  background(0);//white255 black0
  noStroke();
  myFont = createFont("微软雅黑", 20);
  textFont(myFont);

  println(Serial.list()[0]);
  port = new Serial(this,Serial.list()[0],115200);
}

void draw(){
  receiveDatas();
  if(revFlag==1){
    String[] m=match(new String(rev), "(.*?):(.*?)\r");//正则表达式匹配
    //printArray(m);
    try{
      if(m[1].equals("F1")){
        data[0]=int(m[2]);
      }else if(m[1].equals("F2")){
        data[1]=int(m[2]);
      }else if(m[1].equals("F3")){
        data[2]=int(m[2]);
      }else if(m[1].equals("F4")){
        data[3]=int(m[2]);
      }else if(m[1].equals("F5")){
        data[4]=int(m[2]);
      }else if(m[1].equals("F6")){
        data[5]=int(m[2]);
      }else if(m[1].equals("F7")){
        data[6]=int(m[2]);
      }else if(m[1].equals("F8")){
        data[7]=int(m[2]);
      }else if(m[1].equals("Clear")){
        data[8]=int(m[2]);
      }else if(m[1].equals("NIR")){
        data[9]=int(m[2]);
      }else{
        print("Wrong datas!");
      }
    }catch(NullPointerException e){
      println(rev);
      printArray(m);
    }finally{}
    revFlag=0;
    //printArray(data);
    //delay(10);
  }

  /*显示*/
  //rectMode(CORNER);
  background(0);
  textSize(20);

  fill(#8b3dc5);
  rect(0,0,data[0]*rt,30);//F1
  text(data[0], data[0]*rt, 0+textHight);//textHeight是文字的垂直高度

  fill(#00528e);
  rect(0,30,data[1]*rt,30);//F2
  text(data[1], data[1]*rt, 30+textHight);

  fill(#00b1ed);
  rect(0,60,data[2]*rt,30);//F3
  text(data[2], data[2]*rt, 60+textHight);

  fill(#01ffcd);
  rect(0,90,data[3]*rt,30);//F4
  text(data[3], data[3]*rt, 90+textHight);

  fill(#00af50);
  rect(0,120,data[4]*rt,30);//F5
  text(data[4], data[4]*rt, 120+textHight);

  fill(#ffff01);
  rect(0,150,data[5]*rt,30);//F6
  text(data[5], data[5]*rt, 150+textHight);

  fill(#ffc000);
  rect(0,180,data[6]*rt,30);//F7
  text(data[6], data[6]*rt, 180+textHight);

  fill(#c10005);
  rect(0,210,data[7]*rt,30);//F8
  text(data[7], data[7]*rt, 210+textHight);

  fill(#ffffff);
  rect(0,240,data[8]*rt,30);//Clear
  text(data[8], data[8]*rt, 240+textHight);

  fill(#888888);
  rect(0,270,data[9]*rt,30);//F8
  text(data[9], data[9]*rt, 270+textHight);

  //delay(1);

  /*色温*/
  float X,Y,Z,x,y,n,temp;
  X=(data[0]*Fx[0] + data[1]*Fx[1] + data[2]*Fx[2] + data[3]*Fx[3] + data[4]*Fx[4] + data[5]*Fx[5] + data[6]*Fx[6] + data[7]*Fx[7]);//20/1000;//20是积分区间,1000是为了将数值转换成0-1之间的实数
  Y=(data[0]*Fy[0] + data[1]*Fy[1] + data[2]*Fy[2] + data[3]*Fy[3] + data[4]*Fy[4] + data[5]*Fy[5] + data[6]*Fy[6] + data[7]*Fy[7]);//20/1000;//但在这里乘上这个数没什么意义,反正在下一步就约掉了
  Z=(data[0]*Fz[0] + data[1]*Fz[1] + data[2]*Fz[2] + data[3]*Fz[3] + data[4]*Fz[4] + data[5]*Fz[5] + data[6]*Fz[6] + data[7]*Fz[7]);//20/1000;

  x=X/(X+Y+Z);
  y=Y/(X+Y+Z);

  n=(x-0.3320)/(0.1858-y);
  temp=437*n*n*n+3601*n*n+6831*n+5517;
  print(temp);
  println("K\n");

}

以及对应的Arduino程序

#include "DFRobot_AS7341.h"

DFRobot_AS7341 as7341;

void setup(void)
{
  Serial.begin(115200);
  //Detect if IIC can communicate properly 
  while (as7341.begin() != 0) {
    Serial.println("IIC init failed, please check if the wire connection is correct");
    delay(1000);
  }
//  //Integration time = (ATIME + 1) x (ASTEP + 1) x 2.78µs
//  //Set the value of register ATIME, through which the value of Integration time can be calculated. The value represents the time that must be spent during data reading.
  as7341.setAtime(29);
//  //Set the value of register ASTEP, through which the value of Integration time can be calculated. The value represents the time that must be spent during data reading.
  as7341.setAstep(599);
//  //Set gain value(0~10 corresponds to X0.5,X1,X2,X4,X8,X16,X32,X64,X128,X256,X512)
  as7341.setAGAIN(2);//测试时用2
//  //Enable LED
//  //as7341.enableLed(true);
//  //Set pin current to control brightness (1~20 corresponds to current 4mA,6mA,8mA,10mA,12mA,......,42mA)
//  //as7341.controlLed(10);
}
void loop(void)
{
  DFRobot_AS7341::sModeOneData_t data1;
  DFRobot_AS7341::sModeTwoData_t data2;

  //Start spectrum measurement 
  //Channel mapping mode: 1.eF1F4ClearNIR,2.eF5F8ClearNIR
  as7341.startMeasure(as7341.eF1F4ClearNIR);
  //Read the value of sensor data channel 0~5, under eF1F4ClearNIR
  data1 = as7341.readSpectralDataOne();

  Serial.print("F1:");//(405-425nm)
  Serial.println(data1.ADF1);
  Serial.print("F2:");//(435-455nm)
  Serial.println(data1.ADF2);
  Serial.print("F3:");//(470-490nm)
  Serial.println(data1.ADF3);
  Serial.print("F4:");//(505-525nm)
  Serial.println(data1.ADF4);
  Serial.print("Clear:");
  Serial.println(data1.ADCLEAR);
  Serial.print("NIR:");
  Serial.println(data1.ADNIR);
  //delay(50);

  as7341.startMeasure(as7341.eF5F8ClearNIR);
  //Read the value of sensor data channel 0~5, under eF5F8ClearNIR 
  data2 = as7341.readSpectralDataTwo();
  Serial.print("F5:");//(545-565nm)
  Serial.println(data2.ADF5);
  Serial.print("F6:");//(580-600nm)
  Serial.println(data2.ADF6);
  Serial.print("F7:");//(620-640nm)
  Serial.println(data2.ADF7);
  Serial.print("F8:");//(670-690nm)
  Serial.println(data2.ADF8);
  Serial.print("Clear:");
  Serial.println(data2.ADCLEAR);
  Serial.print("NIR:");
  Serial.println(data2.ADNIR);
  delay(50);
}

其中的as7341.setAGAIN(2)可以调节数值的增益。光线较暗的环境下也可以通过调整as7341.setAtime(29)as7341.setAstep(599)里面的数值来增加积分时间。

下面是测试的效果图,从上到下的十根色柱分别是传感器的F1-F8、无滤镜通道,以及红外通道。同时下面还在不停打印计算出来的色温值,由于传感器本身精度,而且也未经过专业仪器校准,可能会有几百开尔文的误差,结果仅供参考

测评部分

用快递箱改装的试验箱

下面是参赛选手

已知数据

选手 单价 额定功率 色温
1号白炽灯 ¥4.10 25W 未标注
2号LED ¥2.10 3W 6000K
3号LED ¥4.50 3W 未标注
4号LED ¥5.50 5W 未标注
5号荧光灯 ¥7.50 5W 6400K
6号LED ¥7.90 5W 2700K

以下测试是在该配置下进行的
as7341.setAtime(29);
as7341.setAstep(599);
as7341.setAGAIN(2);

首先上场的是1号选手——25W白炽灯,可以看出光谱的曲线非常平滑,而且红外的成分非常高,看起来超出了传感器的测量范围,色温大致在2650K左右波动。

测试后,整个测试箱都是温热的,可见25W的功率绝大部分都被转换为了热量。

接下来是2号至4号LED,之所以放在一起是因为它们都出自同一个厂家。从光谱上可以看到这组灯泡在435-455nm的波段不约而同的有一个小尖刺。2号的色温大致在4700K,和标注的6000K差一大段距离,3号色温在4750K左右浮动,4号则4830K上下,可见它们很有可能采用了同一种LED灯珠。
2号

3号

4号

下面是5号荧光灯,虽然标注着5W,然而从光谱上看好像还不如3W的LED亮,能效比堪忧啊。然而荧光灯刚打开时数值不是很稳定,看起来还没有进入状态,过了3-5分钟后,亮度稍有提升,但还是远低于3W的LED。
未进入状态

3-5分钟后

最后一名选手是宜家的5W2700K的LED,实测2680K左右,看来还是挺准的。作为选手中最贵的灯泡,它有着最平滑的光谱曲线。

闪烁频率

AS7341传感器还具有测量光源闪烁频率的功能,不过只能得出无闪烁、50HZ、60HZ和位置频率闪烁这四种结果,我用传感器分别测量了每个灯泡的闪烁频率,发现它们都以50Hz在闪烁,这是因为我国交流电是50Hz的,然而同样是在闪烁,但它们给人的感受是不同的。用手机调高快门或是开启慢镜头摄影后发现,最便宜的2号LED灯闪的厉害,而且偏冷的色温让人有炫光的感觉,最好的是6号LED,基本上察觉不到在闪烁。这是因为2号LED采用的是阻容降压,几乎没有过滤掉交流电里的交流分量,而6号LED采用专业的恒流驱动芯片,可以将交流分量控制在很小的范围。而普通的白炽灯由于没有任何滤波电路,在手机镜头下也是在快速闪烁,但是却没有2号LED闪烁那么刺眼。(以下图片均是在x32的慢镜头下拍摄)

2号LED

6号LED

1号白炽灯

综上所述,LED光能转化率完爆其它灯泡,但为了节省成本,不少厂家都会选用显色率低的普通灯珠,而且没有恒流电路,结果LED产生很大的闪烁,一般选择大厂生产的灯泡即可,不要贪图便宜。另一方面,日常使用时可以选择色温偏暖的灯泡,虽然这样会让周围的一切看起来发黄发红,但是对人眼刺激小,不易产生炫光。
假如是摄影之类,为了还原物体的真实色彩,也可以选用5600K高显色指数的灯泡。

参考资料

1nm间隔的三刺激函数
色温所对应的RGB颜色表


下载附件光谱传感器.rar

微笑的rockets  NPC

发表于 2020-11-30 10:51:37

专业!
知道怎么选灯泡了。
回复

使用道具 举报

二哈哈哈哈  中级技师

发表于 2020-12-2 09:53:11

宜家记得给广告费
回复

使用道具 举报

1013705990  见习技师

发表于 2020-12-4 01:10:39

666大佬牛6666666
回复

使用道具 举报

gray6666  初级技神

发表于 2020-12-5 14:50:08

测试箱体为什么用黑色的纸壳?白色是否可以
回复

使用道具 举报

dbc0301  高级技匠
 楼主|

发表于 2020-12-7 13:07:49

gray6666 发表于 2020-12-5 14:50
测试箱体为什么用黑色的纸壳?白色是否可以

试试无妨,我虽然用的黑色卡纸,但是这种黑色也是不能完全吸收所有波长的光,我测试过卡纸发射LED发出的白光,好像会反射蓝紫色的光多一些,也可能是LED本身发出的蓝紫色光含量很多造成;同样所谓白色,也很难做到均匀反射各种波段的光,很多摄影师都会准备专业的色卡,其中就有用来纠正白平衡的白色卡,应该可以还原出均匀反射各个波段光的真正白色,但这样的色卡一般价格不菲。
我们做实验总是说理想状态,可是现实中充满了非理想,想要更好的实验环境,就要付出不成比例的时间、精力和金钱,毕竟追求完美的代价总是昂贵的。
回复

使用道具 举报

TuTu  高级技师

发表于 2020-12-9 11:19:09

回复

使用道具 举报

 初级技匠

发表于 2020-12-16 12:38:00

奇怪的知识又增加了
回复

使用道具 举报

温柔的投降  初级技师

发表于 2020-12-30 10:49:04

很好很好 学习了
回复

使用道具 举报

葱油饼  学徒

发表于 2021-1-6 11:12:33

你好,请教一下,如何转换成rgb值呢?
回复

使用道具 举报

youyusj  学徒

发表于 2021-1-6 12:44:19

同求,如何转RGB
回复

使用道具 举报

dbc0301  高级技匠
 楼主|

发表于 2021-1-10 23:55:48

@葱油饼 @youyusj
这是很好的问题,实际上要转换为RGB非常简单,因为在之前的例子里已经通过光谱数据转换为三刺激值,再转换为色坐标,而三刺激值的物理含义就是三种颜色的光对相应视细胞的刺激程度,那么为了还原这种颜色,只要将三原色以三刺激值的比例来调配每种光的含量就好了,也可以考虑通过色坐标和RGB坐标转换来实现,这也是一样的道理。
不过话又说回来,想获得纯的三原色光还是很困难的,我们接触到的五颜六色的LED,有些是利用各种颜色的荧光粉来调节光的成分的,为了更准确地模拟颜色的真实情况,可以考虑联立一下求方程组
已知光的三刺激值为R0、G0、B0;
现有一枚RGB发光二极管,通过这篇文章的方法可以获知:
红灯亮其他灯不亮时,该灯的三刺激值为Rr、Gr、Br;
绿灯亮其他灯不亮时,该灯的三刺激值为Rg、Gg、Bg;
蓝灯亮其他灯不亮时,该灯的三刺激值为Rb、Gb、Bb。
设三种灯的比例为kr、kg、kb,kr+kg+kb=1
kr*Rr+kg*Rg+kb*Rb=k*R0
kr*Gr+kg*Gg+kb*Gb=k*G0
kr*Br+kg*Bg+kb*Bb=k*B0
kr+kg+kb=1
四个方程,四个未知数,求出来的kr、kg、kb就是三种灯的PWM参数。
通俗点说就是红灯绿灯蓝灯的对人视网膜的刺激应该和原来光谱的三刺激值对应成比例。简单应用的话,第一种方法即可。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail