Human perceive brightness change non-linearly

When you want to change the brightness of LED or any light source, one thing you need to consider is how human perceive the brightness. As you see in the following chart, human perceive the brightness change non-linearly. We have better sensitivity at low luminance than high luminance. For example, when we control LED brightness using Arduino PWM, we see big brightness change between analogWrite(9,1) and analogWrite(9,2). We don’t see brightness change between analogWrite(9,244) and analogWrite(9,255). If you didn’t know, just quickly test yourself with Arduino. If you want to control LED brightness linearly to your eye, it require to have some adjustment.

 

image

 

Mis-understanding of Gamma Correction

In Arduino or any microcontroller, a common way to achieve linear brightness change is a lookup table that compensate or correct the value according to the curve. There are a common misunderstand or confusion regarding what curve to use. Many people use so called gamma correction table or equation which is not related with human perception of brightness. The Maxim App note describe “Gamma correction is used to correct for the nonlinear relationship between luminance and brightness” which is simply wrong. The gamma correction is used to correct nonlinear relationship between applied voltage to CRT and luminance of CRT. It is nothing to do with human perception. It is not just Maxim, I could find many implementation of gamma correction to correct luminance and brightness. The cyz_RGB also use the gamma correction.

Why people so easily confuse about it? The gamma correction is necessary for the display application. When movie or image is displayed on LED matrix like a stadium display, you want to have gamma correction since movie and image data itself is already gamma corrected data. It is also useful for LED based LCD backlight. When LED is used for lighting, however gamma correction is irrelevant. A funny thing is co-incidentally gamma correction and human perception of luminance is very similar. http://www.poynton.com/PDFs/SMPTE93_Gamma.pdf Take a look at following chart. Again it is just a co-incidence. So somehow the gamma correction is close approximation of human perception the luminance.

Correction calculation of luminance and brightness describe in CIE 1931 report then used for CIELAB color space.

L* = 116(Y/Yn)^1/3 – 16 , Y/Yn > 0.008856
L* = 903.3(Y/Yn), Y/Yn <= 0.008856

Where L* is lightness, Y/Yn is Luminance ratio.

For the correction curve, you need to inverse the equation.

image

 

Due to the similarity between gamma correction and brightness vs luminance, there is only minor differences. If you still want to have more accurate correction, you can use following table for cyz_RGB firmware. Don’t get confused about 16bit valves, your PWM is still 8 bit. It is internally use 16 bit value for conversion.

 1:

 

 2: /*

 

 3: 16 bits to 8 bit CIE Lightness conversion

 

 4: L* = 116(Y/Yn)^1/3 - 16 , Y/Yn > 0.008856

 

 5: L* = 903.3(Y/Yn), Y/Yn <= 0.008856

 

 6: */

 

 7:

 

 8: prog_uint16_t pwm_table[] PROGMEM = {

 

 9:     65535,    65508,    65479,    65451,    65422,    65394,    65365,    65337,

 

 10:     65308,    65280,    65251,    65223,    65195,    65166,    65138,    65109,

 

 11:     65081,    65052,    65024,    64995,    64967,    64938,    64909,    64878,

 

 12:     64847,    64815,    64781,    64747,    64711,    64675,    64637,    64599,

 

 13:     64559,    64518,    64476,    64433,    64389,    64344,    64297,    64249,

 

 14:     64200,    64150,    64099,    64046,    63992,    63937,    63880,    63822,

 

 15:     63763,    63702,    63640,    63577,    63512,    63446,    63379,    63310,

 

 16:     63239,    63167,    63094,    63019,    62943,    62865,    62785,    62704,

 

 17:     62621,    62537,    62451,    62364,    62275,    62184,    62092,    61998,

 

 18:     61902,    61804,    61705,    61604,    61501,    61397,    61290,    61182,

 

 19:     61072,    60961,    60847,    60732,    60614,    60495,    60374,    60251,

 

 20:     60126,    59999,    59870,    59739,    59606,    59471,    59334,    59195,

 

 21:     59053,    58910,    58765,    58618,    58468,    58316,    58163,    58007,

 

 22:     57848,    57688,    57525,    57361,    57194,    57024,    56853,    56679,

 

 23:     56503,    56324,    56143,    55960,    55774,    55586,    55396,    55203,

 

 24:     55008,    54810,    54610,    54408,    54203,    53995,    53785,    53572,

 

 25:     53357,    53140,    52919,    52696,    52471,    52243,    52012,    51778,

 

 26:     51542,    51304,    51062,    50818,    50571,    50321,    50069,    49813,

 

 27:     49555,    49295,    49031,    48764,    48495,    48223,    47948,    47670,

 

 28:     47389,    47105,    46818,    46529,    46236,    45940,    45641,    45340,

 

 29:     45035,    44727,    44416,    44102,    43785,    43465,    43142,    42815,

 

 30:     42486,    42153,    41817,    41478,    41135,    40790,    40441,    40089,

 

 31:     39733,    39375,    39013,    38647,    38279,    37907,    37531,    37153,

 

 32:     36770,    36385,    35996,    35603,    35207,    34808,    34405,    33999,

 

 33:     33589,    33175,    32758,    32338,    31913,    31486,    31054,    30619,

 

 34:     30181,    29738,    29292,    28843,    28389,    27932,    27471,    27007,

 

 35:     26539,    26066,    25590,    25111,    24627,    24140,    23649,    23153,

 

 36:     22654,    22152,    21645,    21134,    20619,    20101,    19578,    19051,

 

 37:     18521,    17986,    17447,    16905,    16358,    15807,    15252,    14693,

 

 38:     14129,    13562,    12990,    12415,    11835,    11251,    10662,    10070,

 

 39:     9473,    8872,    8266,    7657,    7043,    6424,    5802,    5175,

 

 40:     4543,    3908,    3267,    2623,    1974,    1320,    662,    0

 

 41: };

 

 

Patched cyz_RGB firmware can be found at google code download.

http://code.google.com/p/neuroelec/downloads/list

 

You can test linearity of LED brightness to your eye using following Arduino example code.

Just put any LED on pin 9 with current limiting resistor.

 1: /*

 

 2:  Change brightness of LED linearly to Human eye

 

 3:  32 step brightness using 8 bit PWM of Arduino

 

 4:  brightness step 24 should be twice bright than step 12 to your eye.

 

 5: */

 

 6:

 

 7: #include <avr/pgmspace.h>

 

 8: #define CIELPWM(a) (pgm_read_word_near(CIEL8 + a)) // CIE Lightness loopup table function

 

 9:

 

 10: /*

 

 11: 5 bit CIE Lightness to 8 bit PWM conversion

 

 12: L* = 116(Y/Yn)^1/3 - 16 , Y/Yn > 0.008856

 

 13: L* = 903.3(Y/Yn), Y/Yn <= 0.008856

 

 14: */

 

 15:

 

 16: prog_uint8_t CIEL8[] PROGMEM = {

 

 17:     0,    1,    2,    3,    4,    5,    7,    9,    12,

 

 18:     15,    18,    22,    27,    32,    38,    44,    51,    58,

 

 19:     67,    76,    86,    96,    108,    120,    134,    148,    163,

 

 20:     180,    197,    216,    235,    255

 

 21: };

 

 22:

 

 23: int brightness = 0;    // initial brightness of LED

 

 24: int fadeAmount = 1;

 

 25:

 

 26: void setup()  {

 

 27:   // declare pin 9 to be an output:

 

 28:   pinMode(9, OUTPUT);

 

 29: }

 

 30:

 

 31: void loop()  {

 

 32:   // set the brightness of pin 9:, 0-31, 5 bit steps of brightness

 

 33:   analogWrite(9, CIELPWM(brightness));

 

 34:

 

 35:   // change the brightness for next time through the loop:

 

 36:   brightness = brightness + fadeAmount;

 

 37:

 

 38:   // reverse the direction of the fading at the ends of the fade: 

 

 39:   if (brightness == 0 || brightness == 31) {

 

 40:     fadeAmount = -fadeAmount ;

 

 41:   }

 

 42:   // wait for 500 milliseconds to see the bightness change 

 

 43:   delay(500);

 

 44: }