Compensation de la sensibilité de l’œil

Tout un programme. Et oui j’ai encore fait un développement avec des leds qui doivent s’éclairer progressivement. Mais comme vous la savez sans doute, l’œil (humain) n’a pas une sensibilité linéaire à l’intensité lumineuse. C’est pour cela que la CIE a produit une courbe normalisée de la sensibilité.

Evidemment, il ne s’agit pas de demander à mon micro-controleur de faire des calculs en permanence, mais juste d’établir une table de correspondance (LUT – LookUp Table) stockée en dur, qui sera consultée régulièrement. Claculons donc les valeurs de cette table…

Pour référence, l’article Wikipédia : Luminosité (colorimétrie) — Wikipédia (wikipedia.org)

Voici donc la définition « CIE 1976 lightness » :

L* = 116 f (Y/Yn) – 16
where
f (Y/Yn) = (Y/Yn)1/3 if (Y/Yn) > (6/29)3
f (Y/Yn) = (841/108) (Y/Yn) + 4/29 if (Y/Yn) ≤ (6/29)3

17-23-074 CIE – Note 1

Et voilà le code C qui correspond :

float ciel(const float& y) {
  return y <= 0.008856 ? 903.3*y : 116*pow(y, 1/3.0) - 16;
}
Et le tracé de la fonction pour 256 échantillons entre 0 et 1.

Mais tout ceci ne nous avance pas beaucoup puisqu’en fait il s’agit là de la sensibilité de l’œil et donc ce que l’on recherche c’est une fonction de la courbe de puissance de la led ; donc la fonction inverse…

La solution naïve serait de parcourir tout l’intervalle à très petit pas pour trouver chaque valeur la plus proche d’un entier, mais cela risque d’être très long, même pour mettre ces résultats dans une table qui ne sera plus jamais calculée.

Nous allons profité de la monotonie de cette fonction (pas de changement de signe de sa dérivée : « elle fait que monter quoi » !) pour utiliser une dichotomie et rechercher pour chaque valeur de sortie attendue, la meilleure valeur d’entrée et cela pas essais successifs (et convergents).

Le code ci-dessous est adapté pour les valeurs entrantes de 0 à 255 (au lieu de 0 à 100 comme au-dessus).

Serial.begin(115200);
  for (int lum = 0; lum < 256; ++lum) {
    float yMin = 0;
    float yMax = 1;
    while (true) {
      const float y = (yMin + yMax) / 2.0;
      if ((yMax - yMin) < 0.00001) {
        Serial.print(round(y*255));
        Serial.print(",");
        break;
      }
      const float r = 255 * ciel(y) / 100;
      if (r < lum) {
        yMin = y;  
      } else {
        yMax = y;
      }
    }
  }
  Serial.println();
  Serial.flush();

Si vous lancer ce code, vous obtiendrez une chaine de constante permettant de compenser la sensibilité humaine et ainsi donner l’impression d’un éclairement linéaire de votre led.

Un petit bonus ? Si vous faire varier rapidement les valeurs, vous vous rendrez compte des paliers associés aux valeurs entières surtout sur les petites luminosités de 0 à 10 environ. On peut réduire cet effet en mélangeant les bits de point faible ce qui va intervertir les valeurs de sortie deux à 2. On obtient cet effet en modifiant la ligne de condition de la dichotomie :

      if (r < (lum ^0x01)) {

Bon développements !

Références :