-
TCS3200 color sensor with k-Nearest Neighbor classification algorithm
03/03/2019 at 14:14 • 0 commentsThe k-NN (k-nearest neighbors) algorithm is among the simplest of all machine learning algorithms. We will use this algorithm to classify the color of a new object. As our training set of learned colors will only contain one sample per color, k = 1, and the object is simply assigned to the class of that single nearest neighbor. We will work in a 3-dimensional Euclidean space ℝ3. The x-axis represents the ratio of the clear channel frequency to the red channel frequency, the y-axis the ratio of the clear channel frequency to the green channel frequency and the z-axis the ratio of the clear channel frequency to the blue channel frequency (see TCS3200 data sheet for more details).
The image below illustrates the 3-dimensional Euclidean space, the trained colors (white, orange, yellow, red, green and blue point) and a new sample (black point), which color needs to be classified. The black lines are the so called Euclidean distances from the new sample point to every trained color point. All we need to do is to find the smallest distance.
Fig. 1 Euclidean distances of RGB values
In general, for an n-dimensional Euclidean space ℝn, the Euclidean distance between two points
and
is given by
Trouble shooting:
- Colored objects shall be non glossy
- Best work colored paper post-it notes (or sticky notes) with a large surface area of at least 8100 square millimeters
- Always keep the same distance from the color sensor resp. collimator lens to the object
- Fix the distance of the collimator lens to the TCS3200 color sensor itself by using Loctite or other glue or a small screw. The distance itself is unproblematic
Machine learning code:
#include <SPI.h> #include <Wire.h> #include <Servo.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include "musical_notes.h" #define SCREEN_WIDTH 128 /* OLED display width */ #define SCREEN_HEIGHT 64 /* OLED display height */ /* Declaration for an SSD1306 display connected to I2C (SDA, SCL, reset pin) */ #define OLED_RESET 9 /* Reset pin */ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); byte LED = 8, S0 = 7, S1 = 6, S2 = 5, S3 = 4, OUT = 3; char* color[] = {"white", "orange", "yellow", "red", "green", "blue", "out of range"}; char point[] = "Point color sensor to"; char object[] = " object"; char submit[] = "Press the push buttonif it's done"; int learned_colors[3][6]; int PercentageRed, PercentageGreen, PercentageBlue; float OutOfRange; int switchPin = 23; int debounce = 200; /* Speaker connected to digital pin 26 */ int speakerPin = 26; void setup() { Serial.begin(9600); /* Init OLED */ display.begin(SSD1306_SWITCHCAPVCC, 0x3D); /* Clear the buffer */ display.clearDisplay(); TCS3200_Setup(); pinMode(switchPin, INPUT); pinMode(speakerPin, OUTPUT); } void loop() { learning_mode(); while(1) { new_sample(); classify(); } } void TCS3200_Setup() { pinMode(S0,OUTPUT); pinMode(S1,OUTPUT); pinMode(S2,OUTPUT); pinMode(S3,OUTPUT); pinMode(LED,OUTPUT); pinMode(OUT,INPUT); } void TCS3200_On() { digitalWrite(LED,HIGH); // switch LED on digitalWrite(S0,HIGH); // output frequency scaling (100%) digitalWrite(S1,HIGH); delay(5); } void TCS3200_Off() { digitalWrite(LED,LOW); // switch LED off digitalWrite(S0,LOW); // power off sensor digitalWrite(S1,LOW); } void NoFilter() { digitalWrite(S2,HIGH); // select no filter digitalWrite(S3,LOW); delay(5); } void RedFilter() { digitalWrite(S2,LOW); // select red filter digitalWrite(S3,LOW); delay(5); } void GreenFilter() { digitalWrite(S2,HIGH); // select green filter digitalWrite(S3,HIGH); delay(5); } void BlueFilter() { digitalWrite(S2,LOW); // select blue filter digitalWrite(S3,HIGH); delay(5); } void input() { while (1) { if(digitalRead(switchPin) == 1) { squeak(); break; } } } void GetColor() { float FrequencyClear, FrequencyRed, FrequencyGreen, FrequencyBlue; TCS3200_On(); NoFilter(); FrequencyClear = float(pulseIn(OUT,LOW,10000)); RedFilter(); FrequencyRed = float(pulseIn(OUT,LOW,10000)); GreenFilter(); FrequencyGreen = float(pulseIn(OUT,LOW,10000)); BlueFilter(); FrequencyBlue = float(pulseIn(OUT,LOW,10000)); TCS3200_Off(); PercentageRed = int((FrequencyClear / FrequencyRed) * 100.0); PercentageGreen = int((FrequencyClear / FrequencyGreen) * 100.0); PercentageBlue = int((FrequencyClear / FrequencyBlue) * 100.0); OutOfRange = 500.0 / FrequencyClear; } void learning_mode() { for(byte i = 0; i < 6; i ++) { display.clearDisplay(); display.setTextSize(1); display.setTextColor(1, 0); display.setCursor(0, 0); display.print(point); if(i == 1) display.print("an "); else display.print("a "); display.print(color[i]); display.println(object); display.println(""); display.println(submit); display.println(""); display.display(); input(); GetColor(); learned_colors[0][i] = PercentageRed; learned_colors[1][i] = PercentageGreen; learned_colors[2][i] = PercentageBlue; } } void new_sample() { GetColor(); delay(500); } void classify() { display.clearDisplay(); display.setTextSize(1); display.setTextColor(1, 0); display.setCursor(0, 0); int i_color; int ClosestColor; float MaxDiff; float MinDiff = 100.0; if(OutOfRange < 1.1) ClosestColor = 6; /* object out of range */ else { /* Find nearest neighbor, k = 1 */ for (i_color = 0; i_color < 6; i_color ++) { /* compute Euclidean distances */ float ED = sqrt(pow((learned_colors[0][i_color] - PercentageRed),2.0) + pow((learned_colors[1][i_color] - PercentageGreen),2.0) + pow((learned_colors[2][i_color] - PercentageBlue),2.0)); MaxDiff = ED; /* Find minimum distance */ if (MaxDiff < MinDiff) { MinDiff = MaxDiff; ClosestColor = i_color; } } } if(ClosestColor != 6) display.print("Object is "); /* Print result */ else display.print("Object "); display.println(color[ClosestColor]); display.display(); } void beep (int speakerPin, float noteFrequency, long noteDuration) { int x; // Convert the frequency to microseconds float microsecondsPerWave = 1000000/noteFrequency; // Calculate how many HIGH/LOW cycles there are per millisecond float millisecondsPerCycle = 1000/(microsecondsPerWave * 2); // Multiply noteDuration * number or cycles per millisecond float loopTime = noteDuration * millisecondsPerCycle; // Play the note for the calculated loopTime. for (x=0;x<loopTime;x++) { digitalWrite(speakerPin,HIGH); delayMicroseconds(microsecondsPerWave); digitalWrite(speakerPin,LOW); delayMicroseconds(microsecondsPerWave); } } void squeak() { for (int i=100; i<5000; i=i*1.45) { beep(speakerPin,i,60); } delay(10); for (int i=100; i<6000; i=i*1.5) { beep(speakerPin,i,20); } }
The musical_notes.h file is available in the Files section of this project. It must be placed in the same folder as the machine learning file.