引言
看过《石纪元》的小伙伴可能会喜欢里面的这样一句话:“在我们的时代里,不存在黑暗!”。自从爱迪生于1879年发明了世界上第一支具有实用价值的电灯泡,电灯已经有140多年的发展历史。对于人类历史来说,这只是短短的一瞬,但是这并不妨碍电灯成为人类历史上最重要的发明。在此期间,人类对电灯进行了多次的迭代,很多技术层出不穷。在这篇文章里我将用DFRobot出品的 AS7341
可见光谱传感器对家庭常用的几种灯泡进行详细对比。
原理
首先,我们要了解关于光的一些基本知识。众所周知,太阳光是由赤橙黄绿青蓝紫其中颜色的光组成,然而这种表述并不准确,太阳光是由一段连续的光谱组成,能完整地覆盖人眼可见光的范围。
人类之所以能看到这个五彩缤纷的世界,全要感谢眼睛里的视杆细胞和视锥细胞,视杆细胞对弱光敏感,擅长在黑暗的环境下看清物体轮廓,夜行动物的眼睛多富有视杆细胞;相对的视锥细胞则能对不同颜色的光产生反应,还原物体的颜色。人类视网膜上存在着红、绿、蓝三种视锥细胞,一束光会对这三种视锥细胞造成不同程度的刺激,这个刺激值在大脑中就会被还原成光的颜色。也就是说,颜色是人大脑中的概念,如果你将红绿蓝三原色以正确的比例混合,就能够得到任何你想要的颜色,你眼前手机的、电脑的屏幕正是利用这个原理才能显示出各种各样的颜色的。
接下来是色温。从理论上说,任何有温度的物体都会主动向外界辐射电磁波,且温度越高,波长越短,看起来就是从黑变红,再到黄、白,最后发出蓝色光,打铁时铁受热会发红,煤气灶点然后发出的蓝光都是因为这个原理。普通白炽灯正常工作时,灯丝可达2500摄氏度,转换成绝对温标就是2773K,这就是白炽灯产生的光的色温,是一种黄白色的光。现代的很多灯泡像是荧光灯、LED灯采用了不同的发光原理,但是灯泡的包装上可能会写着“色温5000K”之类的字样,这并不是说灯泡在工作时,内部产生了那么高的温度,而是指光的颜色等效于同等温度下黑体产生的光的颜色,也就是相关色温的概念。
要理解计算色温非常复杂,但是这里我采用公式法拟合,将过程简化成了下面几个步骤:
1.为了计算光的色温,我们需要利用传感器获得这个光的光谱,以及人眼三种视锥细胞对不同波长的光的敏感程度——三刺激函数。三刺激函数可以通过查表获得。
- 光谱分别与三刺激函数相乘再进行积分就能得到三刺激值(X、Y、Z)。
- 通过公式转换为色坐标(x-y),任何一种颜色都能在色坐标中找到。
- 最后再通过色坐标计算色温。
代码实现
为了能更直观地看到光谱数据,我用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颜色表