adafruit/Adafruit_TCS34725

The detected colours are way off, and I think I know why

Closed this issue · 7 comments

Hi,

I am not sure if the function calculateColorTemperature() works correctly. Specifically, I have problems with the lines below. Could someone who wrote this explain what happens here? How these numbers the ADC values are multiplied with were calculated?

/* 1. Map RGB values to their XYZ counterparts.    */

  /* Based on 6500K fluorescent, 3000K fluorescent   */

  /* and 60W incandescent values for a wide range.   */

  /* Note: Y = Illuminance or lux                    */

  X = (-0.14282F * r) + (1.54924F * g) + (-0.95641F * b);

  Y = (-0.32466F * r) + (1.57837F * g) + (-0.73191F * b);

  Z = (-0.68202F * r) + (0.77073F * g) + ( 0.56332F * b);

I was trying to make a colorimeter, which would use this RGB sensor to get the x and y chromaticity values, and the values I am getting from this, are way off. If this method is used to process data from other colour sensors in any application where colorimetry is required, this can be big problem. Here is how I found out that something is wrong:

I measured the chromaticities of my RGB light with a calibrated Minolta CS-150, and with the included code in the library. Here is what I got:

Colour Minolta chromaticity (x, y) TCS chromaticity (x, y)
Red 0.71012, 0.2988 -0.15, 0.51
Green 0.1389, 0.6929 0.88, 0.05
Blue 0.1467, 0.0303 1.06, 0.02
'White' (with a built-in white LED) 0.4580, 0.4130 0.27, 0.33

There are a few problems with this:

  • The chromaticity coordinates shoud never go below 0 or above 1
  • The chromaticity should not depend on light intensity, provided you don't saturate the sensor
  • The sensor should work in absolute mode so the illuminant won't play a role, especially when measuring light sources and not reflections

I wrote a Matlab script that calculates the primary chromaticity values from the spectral curves given in the datasheet. I found out that they are completely weird values, and do not correspond with common RGB colour spaces.
So in my case, for my sensor the primaries are:

Primary CIE 1931 x-coordinate CIE 1931 y-coordinate
Red 0.58 0.38
Green 0.28 0.53
Blue 0.14 0.13

From this, if we hard-code the primary chromaticity coordinates, we can calculate the measured chromaticity with the weighted average of the sensor ADC values:

 sum_adc_values = r_adc_value + g_adc_value + b_adc_value;
  x = ( r_adc_value / sum_adc_values ) * primary_r_x + ( g_adc_value / sum_adc_values ) * primary_g_x + ( b_adc_value / sum_adc_values ) * primary_b_x;
  y = ( r_adc_value / sum_adc_values ) * primary_r_y + ( g_adc_value / sum_adc_values ) * primary_g_y + ( b_adc_value / sum_adc_values ) * primary_b_y;

So this means that the TCS sensors can not actually measure the colours my RGB light, because they fall outside its colour gamut. Guess that would explain the negative chromaticity values. I measured the chromaticity of my white LED in my RGB light, which gives more reasonable (0.41, 0.39) coordinates, which do not depend on the intensity any more. I think the difference is accounted to manufacturing tolerances. And of course, I never expected an $5 device to be as accurate as a multi-thousand dollar instrument. :)

So, assuming I didn't miss something obvious, can we agree that compared to what's in the library code, pre-calculating the primaries and then using the ADC values is a more reliable method of detecting colour? If so, I can make a pull request.

...and this also means, that if you use other RGB sensors (I saw the same code with the ADPS9960 sensor, where you have also added automatic gain control, neat!), the primaries have to be re-calculated for it to provice a meaningful result.

Since I think this is a bug in the library itself and not specific to hardware. But, anyway, I have included these:

  • Arduino board: Any. Replicated on Adafruit Metro, ESP-01, and NodeMCU

  • Arduino IDE version (found in Arduino -> About Arduino menu): 1.8.5, and 1.8.7 No difference

  • List the steps to reproduce the problem below (if possible attach a sketch or
    copy the sketch code in too):
    Go to a dark room, read the chromaticity values with this library, and compare it with an instrument of the same purpose.
    I wrote a brief explanation on how the colour primaries are calculated, but there is so much more to it: https://github.com/ha5dzs/colour_sensor_primary_calculator

If you'd like to make a PR, I'd be happy to review and test it. The current code is problematic to capture xy chromaticities, but as you mentionned calculating this with three+one photodiodes is problematic anyway simply because you only have one value to cover a wide range of wavelengths, etc. But I agree the method you have proposed trying to determine the three primaries for this sensor seems valid.

In terms of where the values in the 3x3 matrix come from, you may find this interesting which provides an alternative means of calculating XYZ (and thus xyY and eventually CCT and Duv) from RGB sensor data:

AN5410.pdf

The setup involved to calculate a custom correlation constant matrix to go from RGB to XYZ (and onward) involves taking a measurement with a spectrometer and with the TCS3472x in the same lighting conditions, and you can then create a 3x3 matrix to correlate new RGB sample using that ccm.

This isn't perfect since different lights have different spectral densities, but if you only care about one specific lighting setup the results can be accurate for that light source.

I found the work you did interesting as an new approach to the same problem, though! Good job on this, and I'd be happy to test a PR based on your work. You may also want to look at DN40 from AMS: https://ams.com/documents/20143/80162/ColorSensors_AN000166_1-00.pdf/c0b4a4b4-9948-f2a7-f8a1-36a8208bd0a9

This library is unfortunately quite old at this point and I know much more about colorimetry than I did when I first wrote it (somewhat blindly as you can surely tell). It definately deserves a good update.

This is only a short term solution, but I added a new function called calculateColorTemperature_dn40 that will perform more accurate color temperarture calculations than the previous code. More complex code can and should be implemented, but this at least is an improved solution over the existing code (which is still in the lib for compatibility sake).

Some test results under a 5K LED light (LEDs being the worst case scenario for these measurements) gives:

  • Previous code: 6234 K
  • New DN40 code: 4874 K
  • Sekonic C-700 spectrometer: 5135 K

So that's well under a +/-10% margin of error, which is acceptable for a three photodiode solution like this.

Unfortunately it seems that I didn't get a notification of these comments, and I just got round to read them today (27/11/2018). I'll have a go and see how this new function performs, and will get back to this.
I am only using the clear channel for automatic gain control only, because the amount of actual light hitting the sensor will depend on the end user implementation. Either way, once the x-y chromaticity values are realistic, the colour temperature values will be OK too, irrespective on how you calculate them. I just would like to see what happens when you give it a colour the sensor can't cope with.

I will put a pull request on, and see how things work out from there.

There is some further discussion on CCT and these sensors in the CircuitPython driver for this chip, but the current Arduino driver with the DN40 implementation is a better reference at the moment.

Just linking these issues up as a FYI and to leave breadcrumbs.

See: adafruit/Adafruit_CircuitPython_TCS34725#15

Again, sorry for getting back with this so late, I had difficulty testing this.

I used McCamy's formula to calculate the colour temperature from my chromaticity values, and I compared against the dn40 method. Here are the results on my 'white' LED:

Results from the Minolta: (x=0.458, y=0.4130) 2745K
Results from my code: (x=0.41 y=0.38) 3300K
Results from the dn40 method: 3600K

The only thing that I noticed being different is that the colour temperature changes slightly with changing gain and ADC values. I don't see this phenomenon with my code. But the results are close enough, and I couldn't generate silly negative colour temperature values like with the previous implementation.

So I guess you can close this issue, and I will upload my code as a separate project, for a workshop I am planning to organise.

Can you tell me the raw RGBC values you used for the test above, along with your GAIN and ATIME values? I've been working on a custom algorithm that I'm curious to check, just to see how far they are from what you got. DN40 is an improvement, but it obviously isn't ideal and the two values are +/-20% and +/-30% off which is still pretty large. +/-10% seems like an ideal range for these kinds of measurements, if that's possible with a simple four diode device like this.