Dans ce tutoriel, vous apprendrez à utiliser le capteur de couleur TCS230 avec un Arduino pour construire un détecteur de couleur entraînable basé sur un classificateur des plus proches voisins.
Le capteur de couleur TCS230 ne vous indique pas directement la couleur qu’il voit, mais renvoie des mesures des canaux rouge, vert, bleu (et clair). Pour la plupart des applications pratiques, il faut d’une manière ou d’une autre convertir ces mesures en une couleur détectée, par exemple « violet ». Il peut être assez difficile de détecter une couleur de manière fiable en utilisant des seuils sur les mesures des canaux. Nous allons donc appliquer un peu d’apprentissage automatique et utiliser un classificateur des plus proches voisins entraînable.
Pièces requises
Vous aurez besoin d’un capteur de couleur TCS230 et d’un microcontrôleur. J’ai utilisé un Arduino Uno pour ce projet, mais tout autre Arduino ou ESP32 fonctionnera également. Nous utiliserons aussi un écran OLED pour afficher la couleur détectée et d’autres informations sur un petit écran.

Capteur de distance TFmini Plus

Arduino Uno

Câble USB pour Arduino UNO

Jeu de fils Dupont

Plaque d’essai (breadboard)

Écran OLED
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Principes de base du capteur de couleur TCS230
Le TCS230 est un convertisseur lumière-couleur en fréquence. Il est composé d’une matrice 8 x 8 de photodiodes qui détectent la lumière. Il y a 16 photodiodes avec un filtre rouge sensibles à la lumière rouge, 16 avec un filtre vert pour la lumière verte, 16 avec un filtre bleu et 16 sans filtre pour la lumière « blanche ».
Si vous regardez de près la photo de la puce TCS230 ci-dessous, vous pouvez en fait voir la matrice 8 x 8 de photodiodes avec leurs filtres colorés (et clairs).

Fonction du capteur de couleur TCS230
Outre la matrice de photodiodes, le TCS230 contient un convertisseur courant-fréquence, qui transforme le courant traversant un ensemble de photodiodes en une onde carrée. Un ensemble est composé de 16 photodiodes pour une couleur spécifique (rouge, vert, bleu) ou clair, et la fréquence de l’onde carrée est proportionnelle au courant traversant cet ensemble de photodiodes.

Pour mesurer l’intensité d’une des trois couleurs (ou clair), vous sélectionnez l’ensemble de photodiodes correspondant (rouge, vert, bleu, blanc) en réglant deux entrées (S2, S3), puis vous lisez la fréquence en sortie. La plage de fréquence (échelle) peut être contrôlée par deux autres entrées (S0, S1).
Brochage du capteur de couleur TCS230
L’image suivante montre le brochage de la puce TCS230. Vous pouvez voir les broches S0 et S1 pour sélectionner la plage de fréquence de sortie, ainsi que les broches S2, S3 pour sélectionner l’ensemble de photodiodes (couleur).

La sortie OUT peut être activée ou désactivée via la broche OE. VDD et GND sont pour l’alimentation avec une tension de 2,7 V à 5,5 V. Le tableau ci-dessous résume le brochage :
| Broche | Nom | Description |
| 1,2 | S0, S1 | Échelle de fréquence de sortie |
| 3 | OE | Activation de la sortie fréquence |
| 4 | GND | Masse |
| 5 | VDD | Alimentation |
| 6 | OUT | Fréquence de sortie |
| 7,8 | S2, S3 | Sélection du type de photodiode |
Sélection photodiode/couleur
Comme mentionné plus haut, avant de pouvoir mesurer l’intensité d’un canal couleur spécifique, vous devez d’abord le sélectionner via les broches S2 et S3. Le tableau ci-dessous montre quelle configuration de S2 et S3 correspond à quel canal couleur :
| Canal couleur | S2 | S3 |
| Rouge | LOW | LOW |
| Bleu | LOW | HIGH |
| Clair | HIGH | LOW |
| Vert | HIGH | HIGH |
Échelle de fréquence
La plage de fréquence de sortie peut varier entre 100 %, 20 %, 2 % et arrêt en réglant les valeurs suivantes pour les broches de contrôle S0 et S1 :
| Échelle de sortie | S0 | S1 | min f | max f |
| Mise hors tension | LOW | LOW | – | – |
| 2 % | LOW | HIGH | 10 kHz | 12 kHz |
| 20 % | HIGH | LOW | 100 kHz | 120 kHz |
| 100 % | HIGH | HIGH | 500 kHz | 600 kHz |
Module capteur de couleur TCS230
En général, vous n’utilisez pas le TCS230 directement, mais un module capteur de couleur TCS230 qui intègre quelques composants supplémentaires et est plus facile à connecter. La photo ci-dessous montre le devant et le dos d’un module typique :

Sur le devant du module, vous trouvez quatre LED blanches pour l’éclairage et un boîtier noir protecteur contenant la puce TCS230. En regardant de près, vous pouvez voir la puce TCS230 au centre :

Schéma du module capteur de couleur TCS230
Le module TCS230 a essentiellement le même brochage que la puce TCS230 elle-même, à deux exceptions près. Il n’y a pas de broche OE mais il y a une broche supplémentaire LED.
Le schéma du module révèle que OE est connecté à VSS, ce qui signifie que la sortie est toujours activée. La broche LED est connectée à la grille d’un MOSFET (Q1) qui commande les quatre LED d’éclairage (D1…D4). Comme la broche LED a une résistance de tirage interne, les LED sont actives par défaut et vous pouvez les éteindre en reliant la broche LED à la masse.

Notez que les broches S0 et S1 ont aussi des résistances de tirage internes et que l’échelle de fréquence par défaut est donc à 100 %. En revanche, les broches S2 et S3 n’ont pas de résistances de tirage et vous devez les régler pour sélectionner un canal couleur.
Dans la section suivante, nous connectons le module TCS230 à un Arduino et écrivons un code pour tester le capteur.
Connexion du TCS230 à l’Arduino
Le schéma suivant montre comment connecter le module TCS230 à un Arduino. J’utilise 5 V comme alimentation, mais 3,3 V fonctionnerait aussi. Les broches de contrôle S0, S1, S2 et S3 du module TCS230 sont connectées aux broches 8, 9, 10, 11 de l’Arduino.

Le tableau ci-dessous rappelle les connexions à effectuer
| TCS230 | Arduino |
| VCC | 5V |
| GND | GND |
| S0 | 8 |
| S1 | 9 |
| S2 | 10 |
| S3 | 11 |
| OUT | 12 |
Notez qu’il y a deux broches VCC et deux broches GND sur le TCS230. Vous pouvez utiliser l’une ou l’autre. La broche LED n’est pas connectée, mais vous pourriez la relier à une sortie numérique pour commander les LED d’éclairage.
Code pour lire les couleurs avec le TCS230
Dans le code suivant, nous utilisons un capteur de couleur TCS230 pour lire les valeurs des différents canaux couleur (rouge, vert, bleu et blanc) et afficher les résultats sur le moniteur série.
#include "aprintf.h"
const int s0 = 8;
const int s1 = 9;
const int s2 = 10;
const int s3 = 11;
const int out = 12;
void initPins() {
pinMode(s0, OUTPUT);
pinMode(s1, OUTPUT);
pinMode(s2, OUTPUT);
pinMode(s3, OUTPUT);
pinMode(out, INPUT);
}
void setScaling(int s0state, int s1state) {
digitalWrite(s0, s0state);
digitalWrite(s1, s1state);
}
uint16_t readPulse(int s2state, int s3state) {
delay(20);
digitalWrite(s2, s2state);
digitalWrite(s3, s3state);
return pulseIn(out, LOW);
}
void setup() {
Serial.begin(9600);
initPins();
setScaling(HIGH, LOW); // 20 %
}
void loop() {
uint16_t red = readPulse(LOW, LOW);
uint16_t blue = readPulse(LOW, HIGH);
uint16_t green = readPulse(HIGH, HIGH);
uint16_t white = readPulse(HIGH, LOW);
aprintf("R:%3d G:%3d B:%3d W:%3d\n",
red, green, blue, white);
delay(1000);
}
Décomposons le code en ses composants pour mieux comprendre.
Includes
Le code utilise la bibliothèque aprintf library, qui simplifie la mise en forme du texte à afficher. C’est essentiellement une version de la fonction printf(). Pour plus de détails, lisez le tutoriel How To Print To Serial Monitor On Arduino.
Pour l’installer, allez dans github repo, cliquez sur le bouton vert « Code » puis sélectionnez « Download ZIP » :

Ensuite, allez dans « Sketch » -> « Include Library » -> « Add .ZIP Library » pour l’installer.

Pour utiliser aprintf library, nous avons besoin de l’inclusion suivante :
#include "aprintf.h"
Constantes
Ensuite, nous définissons les broches utilisées pour connecter le capteur TCS230 à l’Arduino.
const int s0 = 8; const int s1 = 9; const int s2 = 10; const int s3 = 11; const int out = 12;
Ici, s0 et s1 servent à régler l’échelle de fréquence, tandis que s2 et s3 servent à sélectionner le canal couleur. La broche out est celle où le capteur sort le signal de fréquence correspondant à l’intensité de la couleur détectée.
Initialisation des broches
La fonction initPins() configure les modes des broches pour chacune des broches définies. Les broches du capteur s0, s1, s2 et s3 sont configurées en OUTPUT, tandis que la broche out est configurée en INPUT.
void initPins() {
pinMode(s0, OUTPUT);
pinMode(s1, OUTPUT);
pinMode(s2, OUTPUT);
pinMode(s3, OUTPUT);
pinMode(out, INPUT);
}
Échelle de fréquence
La fonction setScaling() sert à configurer l’échelle de fréquence du signal de sortie du capteur. En réglant les états de s0 et s1, on peut ajuster la résolution de la sortie du capteur. Nous y reviendrons plus tard.
void setScaling(int s0state, int s1state) {
digitalWrite(s0, s0state);
digitalWrite(s1, s1state);
}
Lecture des impulsions
La fonction readPulse() est responsable de la lecture de la durée en microsecondes de l’impulsion émise par le capteur. Elle prend en paramètres les états de s2 et s3 pour sélectionner le canal couleur à lire. La fonction attend 20 millisecondes pour stabiliser le capteur entre les lectures.
uint16_t readPulse(int s2state, int s3state) {
delay(20);
digitalWrite(s2, s2state);
digitalWrite(s3, s3state);
return pulseIn(out, LOW);
}
Notez que cette fonction ne retourne pas la fréquence de l’onde carrée mais la durée des impulsions (beaucoup de tutoriels sur le TCS230 se trompent à ce sujet). Plus la couleur est intense, plus la fréquence est élevée mais plus l’impulsion est courte. Il y a donc une relation inverse : plus la couleur est intense, plus la valeur retournée est faible.
Si vous voulez convertir la valeur en fréquence, il faudrait compter le nombre d’impulsions n en une seconde et prendre l’inverse :
f = 1 / n
mais nous travaillerons directement avec les durées d’impulsions.
Fonction Setup
Dans la fonction setup(), nous initialisons la communication série à 9600 bauds et appelons la fonction initPins() pour configurer les broches. Nous réglons aussi l’échelle à 20 % en appelant setScaling(HIGH, LOW), ce qui nous donne une bonne résolution.
void setup() {
Serial.begin(9600);
initPins();
setScaling(HIGH, LOW); // 20 %
}
Si vous comparez les facteurs d’échelle 2 %, 20 % et 100 %, vous verrez que les lectures des canaux couleur sont les plus grandes pour 2 % et les plus petites pour 100 %. Voici quelques exemples de lectures pour les différentes échelles lorsque le capteur a vu un objet vert et un objet blanc avec la même illumination et distance :
100% R: 23 G: 16 B: 25 W: 8 // green R: 7 G: 6 B: 6 W: 3 // white 20% R:116 G: 82 B:124 W: 35 // green R: 32 G: 31 B: 27 W: 10 // white 2% R:1149 G:807 B:1220 W:339 // green R:308 G:305 B:260 W: 96 // white
Le facteur d’échelle 20 % est un bon compromis entre résolution et robustesse. De plus, les lectures couleur pour 20 % tiennent facilement dans un entier, tandis qu’avec 2 % vous pouvez rencontrer des dépassements lorsque le capteur est dans l’obscurité totale. J’ai donc choisi le facteur 20 %.
Fonction Loop
Dans la fonction loop(), nous lisons les durées d’impulsions pour les couleurs rouge, bleu, vert et blanc en appelant readPulse() avec les paramètres appropriés. Les résultats sont stockés dans les variables red, green, blue et white.
uint16_t red = readPulse(LOW, LOW); uint16_t blue = readPulse(LOW, HIGH); uint16_t green = readPulse(HIGH, HIGH); uint16_t white = readPulse(HIGH, LOW);
Comme mentionné, les valeurs couleur sont inverses à l’intensité des couleurs détectées. Nous affichons les valeurs couleur sur le moniteur série en utilisant la fonction aprintf(), qui formate la sortie pour une meilleure lisibilité. Enfin, nous ajoutons un délai de 1000 millisecondes (1 seconde) avant de répéter la boucle.
aprintf("R:%3d G:%3d B:%3d W:%3d\n",
red, green, blue, white);
delay(1000);
Exemple de sortie
Si vous téléversez le code sur votre Arduino et ouvrez le moniteur série, vous devriez voir des lectures des canaux couleur similaires à celles-ci :
R: 56 G:123 B:112 W: 32 R: 47 G:117 B: 93 W: 28 R: 76 G: 71 B: 86 W: 26 R: 77 G: 74 B: 72 W: 26 R:104 G: 76 B: 47 W: 24 R:105 G: 75 B: 48 W: 24 R:165 G:101 B: 85 W: 28 R: 69 G: 71 B: 68 W: 24 R: 70 G: 71 B: 68 W: 24
Si vous placez un objet rouge devant le capteur, la valeur rouge devrait diminuer, de même si le capteur voit un objet bleu ou vert, la valeur du canal correspondant devrait diminuer.
Il y a cependant un problème : les valeurs dépendent fortement de la luminosité ambiante, ce qui rend difficile la détection fiable des couleurs. Dans la section suivante, nous normaliserons donc les lectures.
Normalisation de la luminosité pour le TCS230
La plage globale des lectures couleur dépend de l’échelle choisie. Avec 20 %, vous pouvez obtenir des valeurs entre 0 et environ 15000. Vous pouvez facilement tester cela en couvrant le capteur avec votre doigt (obscurité totale) ou en y dirigeant une lumière forte. Voici un exemple des lectures que j’ai obtenues :
R: 4 G: 3 B: 4 W: 4 R: 4 G: 4 B: 4 W: 4 R:2243 G:13215 B:10289 W:2058 R:2330 G:13986 B:10896 W:2119
Cette forte dépendance à la luminosité ambiante rend presque impossible la définition de seuils robustes pour détecter une couleur. Par exemple, un code comme celui-ci ne fonctionnera pas très bien :
if (red > 10 && red < 100) {
Serial.println("red detected");
}
Nous devons donc normaliser les lectures couleur. Cela peut être facilement fait en divisant les lectures couleur par la valeur du canal blanc (luminosité). Voici la fonction correspondante :
void normalize(uint16_t &red, uint16_t &green, uint16_t &blue, uint16_t white) {
red = 100 * red / (white + 1);
green = 100 * green / (white + 1);
blue = 100 * blue / (white + 1);
}
Elle multiplie la lecture couleur (rouge, vert, bleu) par 100 puis la divise par la valeur blanche plus 1. Nous ajoutons ce plus un pour éviter une division par zéro potentielle, et multiplions par 100 pour garder les valeurs couleur dans une plage similaire (même résolution).
Si vous comparez les lectures couleur avec et sans normalisation, vous verrez que celles avec normalisation sont plus stables. Par exemple, j’ai placé le TCS230 à différentes distances (1 cm … 4 cm) d’un objet bleu.

Sans normalisation, j’ai obtenu des valeurs bleues entre 43 et 169, soit une variation de 126 unités. Avec normalisation, j’ai mesuré des valeurs entre 188 et 237, soit une différence de seulement 49 unités. Les lectures couleur sont donc plus stables (moins sensibles à la lumière ambiante) avec la normalisation.
Dans la fonction loop, vous pouvez facilement ajouter la fonction de normalisation comme suit :
void loop() {
uint16_t red = readPulse(LOW, LOW);
uint16_t blue = readPulse(LOW, HIGH);
uint16_t green = readPulse(HIGH, HIGH);
uint16_t white = readPulse(HIGH, LOW);
normalize(red, green, blue, white);
aprintf("R:%3d G:%3d B:%3d W:%3d\n",
red, green, blue, white);
delay(1000);
}
Cependant, la normalisation n’est pas parfaite et la lumière ambiante affecte toujours les lectures du capteur. De plus, si vous voulez utiliser des seuils pour détecter des couleurs autres que rouge, vert ou bleu, le code devient complexe.
Par exemple, le violet est une combinaison des valeurs rouge, vert et bleu. Pour le détecter, vous devriez implémenter quelque chose comme ceci :
if(red > 250 && red < 300 && green > 330 && green < 400 && blue > 190 && blue < 230) {
Serial.println("purple detected");
}
Ce code est complexe, les seuils sont difficiles à choisir et cela ne fonctionnera pas très bien. À la place, nous entraînons un classificateur simple pour apprendre quelles lectures des canaux couleur correspondent à quelles couleurs. Ce sera beaucoup plus robuste, plus facile à implémenter et aussi plus amusant ; )
Classification des lectures couleur du TCS230
Nous allons utiliser un nearest-neighbor-classifier pour classifier les lectures couleur du TCS230. Pour mieux comprendre comment ce classificateur fonctionne, j’ai collecté des données. J’ai placé le TCS230 devant un carré rouge, vert, violet et jaune et imprimé les lectures des canaux couleur. Voici la grille de couleurs que j’ai utilisée :

Représentation graphique des lectures des canaux couleur
J’ai répété ce processus plusieurs fois et enregistré les lectures rouge et vert. (Je laisse de côté les lectures bleues pour l’instant, mais nous les utiliserons plus tard). Voici à quoi ressemblaient les échantillons de données :
RED, GREEN, COLOR 175,471,red 160,400,red ... 296,221,green 303,226,green ... 193,233,yellow 207,257,yellow ... 269,358,purple 282,374,purple ...
Ensuite, j’ai tracé chaque échantillon dans un nuage de points 2D. Chaque point dans le graphique ci-dessous représente un échantillon (une lecture des valeurs rouge et vert), et la couleur de chaque point indique quel carré coloré était placé devant le capteur.

Comme vous pouvez le voir, les échantillons pour les objets rouge, violet, jaune et vert se regroupent très bien, à l’exception d’un possible point aberrant pour un objet violet dans le coin supérieur droit.
Classification de la couleur d’un objet
Avec ces données, il est maintenant facile de déterminer la couleur d’un objet. On prend simplement une lecture des canaux rouge et vert et on trouve l’échantillon le plus proche de cette lecture. Dans le graphique ci-dessous, la croix marque le point d’une nouvelle lecture et vous pouvez voir que l’échantillon le plus proche est violet. Nous classerions donc notre objet comme étant de couleur violette.

Cette méthode pour classifier les objets basée sur l’échantillon le plus proche s’appelle une Nearest-Neighbor Classifier ou recherche du plus proche voisin. Il existe de nombreuses variantes de cet algorithme.
Surtout, cet algorithme fonctionne aussi avec des données de plus haute dimension. Ce qui signifie que nous pouvons utiliser les trois lectures des canaux couleur pour déterminer une couleur. Le graphique suivant montre quelques échantillons pour cinq couleurs (rouge, violet, jaune, vert, bleu) dans un espace 3D. Chaque échantillon a trois dimensions, les lectures des canaux rouge, vert et bleu, et est un point dans ce nuage 3D :

Comme avant, la couleur du point indique à quelle couleur appartient l’échantillon. Déterminer la couleur d’un nouvel objet dans cet espace 3D de plus haute dimension fonctionne de la même manière qu’avant. Il suffit de trouver l’échantillon le plus proche de vos lectures de canaux. C’est juste plus difficile à visualiser, c’est pourquoi j’ai commencé avec un espace 2D.
Dans la section suivante, nous allons implémenter un classificateur des plus proches voisins simple qui fonctionne dans cet espace 3D.
Code pour le classificateur des plus proches voisins avec TCS230
Le code de cette section est une extension du code précédent. Il ajoute des données d’échantillons et le classificateur des plus proches voisins. Il affiche les lectures des canaux couleur (rouge, vert, bleu) et la couleur détectée. La sortie ressemblera à ceci :
{292, 376, 221} => purple
{393, 293, 193} => blue
{274, 261, 306} => green
{206, 253, 486} => yellow
{165, 428, 375} => red
{166, 246, 466} => ???
Notez qu’il peut détecter des couleurs comme « violet » ou « jaune », qui sont des combinaisons des valeurs rouge, vert et bleu. De plus, s’il ne peut pas détecter une couleur avec confiance, il affiche « ??? ».
Jetez un coup d’œil rapide au code puis nous expliquerons son fonctionnement.
#include "aprintf.h"
const int s0 = 8;
const int s1 = 9;
const int s2 = 10;
const int s3 = 11;
const int out = 12;
const uint16_t reject = 1000;
const char *colors[] = { "???", "red", "green", "blue", "purple", "yellow" };
const int n_samples = 33;
const uint16_t samples[n_samples][4] = {
// red
{ 158, 422, 358, 1 },
{ 165, 354, 311, 1 },
{ 145, 347, 307, 1 },
{ 160, 376, 321, 1 },
{ 177, 343, 309, 1 },
{ 156, 403, 343, 1 },
// green
{ 296, 225, 303, 2 },
{ 279, 232, 311, 2 },
{ 300, 223, 313, 2 },
{ 271, 231, 295, 2 },
{ 293, 230, 309, 2 },
{ 313, 231, 320, 2 },
// blue
{ 413, 300, 186, 3 },
{ 337, 286, 191, 3 },
{ 376, 293, 200, 3 },
{ 354, 290, 206, 3 },
{ 395, 287, 187, 3 },
{ 351, 285, 197, 3 },
{ 427, 304, 190, 3 },
// purple
{ 271, 361, 215, 4 },
{ 274, 346, 217, 4 },
{ 268, 355, 217, 4 },
{ 268, 333, 214, 4 },
{ 297, 387, 225, 4 },
{ 284, 365, 218, 4 },
{ 270, 356, 221, 4 },
// yellow
{ 193, 243, 450, 5 },
{ 194, 252, 464, 5 },
{ 200, 244, 433, 5 },
{ 205, 252, 442, 5 },
{ 210, 245, 357, 5 },
{ 236, 284, 436, 5 },
{ 206, 253, 473, 5 },
};
float sqr(float a) {
return a*a;
}
float eucDist(uint16_t r, uint16_t g, uint16_t b, uint16_t *s) {
return sqrt(sqr(r - s[0]) + sqr(g - s[1]) + sqr(b - s[2]));
}
char *classify(uint16_t r, uint16_t g, uint16_t b) {
char *color = colors[0];
float mindist = reject;
for (int i = 0; i < n_samples ; i++) {
float dist = eucDist(r, g, b, samples[i]);
if (dist < mindist) {
mindist = dist;
color = colors[samples[i][3]];
}
}
return color;
}
void initPins() {
pinMode(s0, OUTPUT);
pinMode(s1, OUTPUT);
pinMode(s2, OUTPUT);
pinMode(s3, OUTPUT);
pinMode(out, INPUT);
}
void setScaling(int s0state, int s1state) {
digitalWrite(s0, s0state);
digitalWrite(s1, s1state);
}
uint16_t readPulse(int s2state, int s3state) {
delay(20);
digitalWrite(s2, s2state);
digitalWrite(s3, s3state);
return pulseIn(out, LOW);
}
void normalize(uint16_t &red, uint16_t &green, uint16_t &blue, uint16_t white) {
red = 100 * red / (white + 1);
green = 100 * green / (white + 1);
blue = 100 * blue / (white + 1);
}
void setup() {
Serial.begin(9600);
initPins();
setScaling(HIGH, LOW); // 20 %
}
void loop() {
uint16_t red = readPulse(LOW, LOW);
uint16_t blue = readPulse(LOW, HIGH);
uint16_t green = readPulse(HIGH, HIGH);
uint16_t white = readPulse(HIGH, LOW);
normalize(red, green, blue, white);
char *color = classify(red, green, blue);
aprintf("{%3d, %3d, %3d} => %s\n",
red, green, blue, color);
delay(1000);
}
Constantes pour les échantillons, les couleurs et le rejet
Au début du code, nous ajoutons trois constantes. La constante reject détermine quand le classificateur décide qu’il ne peut pas détecter une couleur. Nous y reviendrons plus tard.
Ensuite, nous avons la liste des colors que nous allons détecter. La première « couleur » est « ??? », qui est retournée lorsque le classificateur n’a pas pu détecter une couleur. Vous pouvez modifier et étendre cette liste pour détecter autant de couleurs que vous voulez.
Enfin, nous avons une liste d’échantillons n_samples, où chaque échantillon contient les lectures des canaux rouge, vert et bleu plus un index de couleur. L’index de couleur est simplement la position dans la liste colors.
const uint16_t reject = 1000;
const char *colors[] = { "???", "red", "green", "blue", "purple", "yellow" };
const int n_samples = 33;
const uint16_t samples[n_samples][4] = {
// red
{ 158, 422, 358, 1 },
...
// green
{ 296, 225, 303, 2 },
...
// blue
{ 413, 300, 186, 3 },
...
// purple
{ 271, 361, 215, 4 },
...
// yellow
{ 193, 243, 450, 5 },
...
};
Par exemple, le dernier échantillon { 193, 243, 450, 5 } signifie que nous avons les lectures pour les trois canaux couleur red==193, green==243, blue==450 et que cet échantillon correspond à la couleur colors[5] == "yellow".
Je vous montrerai plus tard comment collecter ces échantillons.
Fonction de distance
Un classificateur des plus proches voisins fonctionne en trouvant l’échantillon le plus proche d’une lecture donnée des canaux couleur. Nous avons donc besoin d’une fonction pour calculer la distance entre une lecture des canaux couleur et un échantillon. Une fonction de distance courante pour cela est la Euclidean distance.
La fonction eucDist calcule la Euclidean distance entre les lectures des canaux couleur r, g, b et un échantillon s. Plus les lectures sont proches de l’échantillon, plus la Euclidean distance sera petite.
float sqr(float a) {
return a * a;
}
float eucDist(uint16_t r, uint16_t g, uint16_t b, uint16_t *s) {
return sqrt(sqr(r - s[0]) + sqr(g - s[1]) + sqr(b - s[2]));
}
La fonction carré sqr est une fonction auxiliaire qui calcule le carré de son argument a.
Fonction de classification
La fonction classify parcourt tous les samples, trouve l’échantillon avec la plus petite distance euclidienne mindist aux lectures des canaux couleur r, g, b et retourne la couleur détectée color sous forme de chaîne.
char *classify(uint16_t r, uint16_t g, uint16_t b) {
char *color = colors[0];
float mindist = reject;
for (int i = 0; i < n_samples; i++) {
float dist = eucDist(r, g, b, samples[i]);
if (dist < mindist) {
mindist = dist;
color = colors[samples[i][3]];
}
}
return color;
}
Si la plus petite distance mindist n’est pas inférieure au seuil reject, la première couleur dans le tableau colors est retournée. C’est la chaîne « ??? », qui indique que les lectures des canaux couleur n’étaient pas assez proches d’un des échantillons pour détecter la couleur de manière fiable.
Vous pouvez rendre le seuil de rejet très grand (1e8) et le classificateur répondra toujours avec une couleur, mais en général il est préférable de laisser le classificateur vous dire s’il ne peut pas classifier la couleur d’un objet avec confiance.
Exemple de sortie
Si vous compilez et téléversez le code, vous devriez voir les lectures des canaux couleur et la couleur détectée affichées ainsi :
{263, 363, 221} => ???
{292, 376, 221} => purple
{284, 371, 215} => purple
{392, 275, 200} => ???
{392, 296, 200} => blue
{393, 293, 193} => blue
{303, 269, 236} => ???
{280, 266, 316} => green
{274, 261, 306} => green
{274, 261, 306} => green
{222, 302, 320} => ???
{163, 420, 360} => red
{163, 420, 356} => red
{160, 416, 333} => red
{160, 416, 353} => red
{268, 237, 314} => ???
{287, 237, 292} => ???
Dans la section suivante, je vous montrerai comment adapter le code pour choisir les couleurs que vous voulez détecter.
Collecte des données d’entraînement pour le détecteur de couleur
Le code ci-dessus montre comment implémenter un détecteur de couleur utilisant un classificateur des plus proches voisins. Il fonctionne pour mon capteur, avec mes conditions d’éclairage ambiant, et peut détecter les couleurs « rouge », « vert », « bleu », « violet », « jaune ». Sa précision et le nombre de couleurs dépendent directement des échantillons de données que j’ai collectés.
En général, vous devez collecter vos propres échantillons pour construire un détecteur de couleur qui fonctionne dans votre environnement et pour les couleurs qui vous intéressent. Heureusement, c’est facile. Vous pouvez utiliser le code existant. Il suffit de commenter la ligne où le classificateur est appelé et de modifier l’affichage comme montré ci-dessous :
void loop() {
uint16_t red = readPulse(LOW, LOW);
uint16_t blue = readPulse(LOW, HIGH);
uint16_t green = readPulse(HIGH, HIGH);
uint16_t white = readPulse(HIGH, LOW);
normalize(red, green, blue, white);
//char *color = classify(red, green, blue);
int index = 1; // first color
aprintf("{%3d, %3d, %3d, %d}",
red, green, blue, index);
delay(1000);
}
Commencez maintenant à collecter des échantillons en choisissant votre première couleur (index=1) et placez un objet de cette couleur devant le capteur. Variez la distance et la lumière ambiante autant que possible. Après quelques secondes, vous aurez assez d’échantillons imprimés pour votre première couleur, par exemple « vert »
// green
{296,225,303,1},
{279,232,311,1},
...
{293,230,309,1},
{313,231,320,1},
Éliminez les « mauvais » échantillons, par exemple quand l’objet a été retiré du détecteur, et sauvegardez les « bons » échantillons dans un éditeur de texte. Ensuite, incrémentez la variable index à 2 et répétez le processus pour votre couleur suivante, par exemple « violet »
// purple
{271,361,215,2},
{274,346,217,2},
...
{284,365,218,2},
{270,356,221,2},
Continuez ainsi jusqu’à avoir des données d’échantillons pour toutes les couleurs que vous voulez détecter. Puis copiez toutes les données d’échantillons dans un grand tableau :
const uint16_t samples[n_samples][4] = {
// green
{296,225,303,1},
{279,232,311,1},
...
{293,230,309,1},
{313,231,320,1},
// purple
{271,361,215,2},
{274,346,217,2},
...
{284,365,218,2},
{270,356,221,2},
// other colors
...
};
Enfin, ajustez la constante n_samples pour qu’elle corresponde au nombre d’échantillons dans la liste et vous aurez terminé la collecte des données et l’entraînement de votre classificateur.
Remettez le code à appeler le classificateur et afficher sa réponse pour tester votre classificateur nouvellement entraîné :
void loop() {
...
char *color = classify(red, green, blue);
aprintf("{%3d, %3d, %3d} => %s\n",
red, green, blue, color);
...
}
Si vous trouvez des cas où le détecteur ne détecte pas correctement une couleur, ajoutez ces cas comme échantillons dans le jeu de données. Au bout d’un moment, le jeu de données peut devenir assez volumineux. Vous pouvez le réduire en supprimant les doublons ou quasi-doublons, par exemple :
{271,361,215,3},
{274,346,217,3}, <= duplicate
{268,355,217,3},
{268,333,214,3},
{274,346,217,3}, <= duplicate
{297,387,225,3},
{273,346,216,3}, <= near duplicate
{270,356,221,3},
Vous pouvez faire cela manuellement ou écrire un code pour supprimer les doublons automatiquement.
Dans la dernière section, nous allons ajouter un écran OLED pour afficher la couleur détectée sur un petit écran, au lieu d’imprimer sur le moniteur série.
Ajout d’un OLED pour afficher les couleurs détectées
Ajouter l’OLED est simple. Il suffit de connecter SDA et SCL de l’OLED à A4 et A5 de l’Arduino (SDA->A4, SCL->A5). Comme l’OLED peut fonctionner en 5 V, nous pouvons partager les lignes d’alimentation. Connectez VCC à 5 V et GND à GND. La photo ci-dessous montre le câblage complet :

Code pour afficher les couleurs détectées
Voici le code qui affiche la couleur détectée par le TCS230 et notre classificateur des plus proches voisins sur l’OLED :
#include "Adafruit_SSD1306.h"
const int s0 = 8;
const int s1 = 9;
const int s2 = 10;
const int s3 = 11;
const int out = 12;
const uint16_t reject = 1000;
const char *colors[] = { "???", "red", "green", "blue", "purple", "yellow" };
const int n_samples = 33;
const uint16_t samples[n_samples][4] = {
// red
{ 158, 422, 358, 1 },
{ 165, 354, 311, 1 },
{ 145, 347, 307, 1 },
{ 160, 376, 321, 1 },
{ 177, 343, 309, 1 },
{ 156, 403, 343, 1 },
// green
{ 296, 225, 303, 2 },
{ 279, 232, 311, 2 },
{ 300, 223, 313, 2 },
{ 271, 231, 295, 2 },
{ 293, 230, 309, 2 },
{ 313, 231, 320, 2 },
// blue
{ 413, 300, 186, 3 },
{ 337, 286, 191, 3 },
{ 376, 293, 200, 3 },
{ 354, 290, 206, 3 },
{ 395, 287, 187, 3 },
{ 351, 285, 197, 3 },
{ 427, 304, 190, 3 },
// purple
{ 271, 361, 215, 4 },
{ 274, 346, 217, 4 },
{ 268, 355, 217, 4 },
{ 268, 333, 214, 4 },
{ 297, 387, 225, 4 },
{ 284, 365, 218, 4 },
{ 270, 356, 221, 4 },
// yellow
{ 193, 243, 450, 5 },
{ 194, 252, 464, 5 },
{ 200, 244, 433, 5 },
{ 205, 252, 442, 5 },
{ 210, 245, 357, 5 },
{ 236, 284, 436, 5 },
{ 206, 253, 473, 5 },
};
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
void initOLED() {
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
oled.setTextColor(WHITE);
}
void display(const char *color) {
oled.clearDisplay();
oled.setTextSize(3);
oled.setCursor(15,10);
oled.print(color);
oled.display();
}
float sqr(float a) {
return a * a;
}
float eucDist(uint16_t r, uint16_t g, uint16_t b, uint16_t *s) {
return sqrt(sqr(r - s[0]) + sqr(g - s[1]) + sqr(b - s[2]));
}
char *classify(uint16_t r, uint16_t g, uint16_t b) {
char *color = colors[0];
float mindist = reject;
for (int i = 0; i < n_samples; i++) {
float dist = eucDist(r, g, b, samples[i]);
if (dist < mindist) {
mindist = dist;
color = colors[samples[i][3]];
}
}
return color;
}
void initPins() {
pinMode(s0, OUTPUT);
pinMode(s1, OUTPUT);
pinMode(s2, OUTPUT);
pinMode(s3, OUTPUT);
pinMode(out, INPUT);
}
void setScaling(int s0state, int s1state) {
digitalWrite(s0, s0state);
digitalWrite(s1, s1state);
}
uint16_t readPulse(int s2state, int s3state) {
delay(20);
digitalWrite(s2, s2state);
digitalWrite(s3, s3state);
return pulseIn(out, LOW);
}
void normalize(uint16_t &red, uint16_t &green, uint16_t &blue, uint16_t white) {
red = 100 * red / (white + 1);
green = 100 * green / (white + 1);
blue = 100 * blue / (white + 1);
}
void setup() {
initPins();
initOLED();
setScaling(HIGH, LOW); // 20 %
}
void loop() {
uint16_t red = readPulse(LOW, LOW);
uint16_t blue = readPulse(LOW, HIGH);
uint16_t green = readPulse(HIGH, HIGH);
uint16_t white = readPulse(HIGH, LOW);
normalize(red, green, blue, white);
char *color = classify(red, green, blue);
display(color);
delay(100);
}
Les principaux changements sont l’include de la bibliothèque Adafruit_SSD1306 Library nécessaire pour communiquer avec l’OLED, la création de l’objet OLED, une fonction initOLED pour initialiser l’OLED, et une fonction display pour afficher la couleur détectée sur l’OLED :
#include "Adafruit_SSD1306.h"
...
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
void initOLED() {
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
oled.setTextColor(WHITE);
}
void display(const char *color) {
oled.clearDisplay();
oled.setTextSize(3);
oled.setCursor(15,10);
oled.print(color);
oled.display();
}
...
Si vous n’avez pas encore installé la bibliothèque Adafruit_SSD1306 Library, vous devrez le faire. Installez-la via le gestionnaire de bibliothèques comme d’habitude :

Notez que l’adresse I2C pour l’écran OLED est réglée sur 0x3C dans oled.begin(). La plupart de ces petits OLED utilisent cette adresse (or 0x27) mais la vôtre peut être différente. Si vous ne voyez rien sur l’OLED, c’est probablement qu’il a une adresse I2C différente et vous devrez la modifier.
Si vous ne connaissez pas l’adresse I2C, consultez le tutoriel How to Interface the SSD1306 I2C OLED Graphic Display With Arduino pour la trouver. Le tutoriel Use SSD1306 I2C OLED Display With Arduino vous expliquera aussi comment utiliser un OLED.
Fonction Loop
Le seul changement dans la fonction loop est que nous remplaçons l’affichage sur le moniteur série par un appel à la fonction display avec la couleur détectée :
void loop() {
uint16_t red = readPulse(LOW, LOW);
uint16_t blue = readPulse(LOW, HIGH);
uint16_t green = readPulse(HIGH, HIGH);
uint16_t white = readPulse(HIGH, LOW);
normalize(red, green, blue, white);
char *color = classify(red, green, blue);
display(color);
delay(100);
}
Et voilà ! Vous devriez maintenant avoir un détecteur de couleur que vous pouvez ajuster (entraîner) pour détecter n’importe quelle couleur sous différentes conditions d’éclairage. La courte vidéo ci-dessous montre mon détecteur de couleur en action :

Vous pouvez voir qu’il détecte bien les trois couleurs violet, jaune et bleu et affiche « ??? » quand je passe d’une couleur à l’autre ou quand le détecteur est trop loin.
Conclusions
Dans ce tutoriel, vous avez appris à utiliser le capteur de couleur TCS230 avec un Arduino pour construire un détecteur de couleur entraînable utilisant un classificateur des plus proches voisins. Ce détecteur fonctionne assez bien, mais il y a beaucoup d’améliorations possibles.
Si vous voulez détecter beaucoup de couleurs et que les lectures du capteur sont plus bruitées, un k-nearest neighbor classifier rendra la détection plus robuste.
Au lieu de normaliser les lectures des canaux couleur par le canal blanc, vous pourriez collecter des échantillons en 4 dimensions (r, g, b, w) et créer un classificateur des plus proches voisins en 4 dimensions. Un tel classificateur sera plus robuste aux changements de lumière ambiante mais nécessitera aussi plus d’échantillons d’entraînement.
Cependant, vous pouvez compresser la liste d’échantillons, réduisant ainsi les besoins en mémoire et augmentant la vitesse de classification, en ne stockant que les centres des clusters de couleurs. Alternativement, vous pouvez supprimer les échantillons du centre et ne garder que ceux proches de la frontière des classes.
Enfin, nous avons « entraîné » le classificateur manuellement en collectant des échantillons et en les codant en dur. Mais vous pourriez aussi ajouter un bouton « entraînement » au circuit et un code qui stocke un échantillon dans l’EEPROM lorsqu’il est pressé. Cela vous permettrait d’entraîner dynamiquement votre détecteur de couleur.
Si vous avez des questions, n’hésitez pas à les poser dans la section commentaires.
Bon bricolage ; )

