2016年3月2日 星期三

{ Arduino } TCS3200 顏色辨識感測模組的校正、取色與顯色說明

網頁最後修改時間:2016/03/02

撰寫這篇網頁的目的是為了說明 TCS3200 顏色辨識感測模組如何進行白平衡校正,經由校正後的係數補償至物體取色過程中的誤差,得到接近物體色彩的目的,而取色的結果將使用 WS2812B RGB LED 顯示出來作為驗證。
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
有購買商品的使用者,網頁中所需相關資料已放置於雲端硬碟,請自行下載使用!
其餘的使用者,程式碼都在網頁中,請自行複製貼上使用。

編譯環境:https://www.arduino.cc/en/Main/Software
Arduino IDE V1.6.5-r5;Arduino IDE V1.6.7
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

*********************************************************************************
可使用於顯示全採的可程式的 RGB LED 可至分類賣場購買:
*********************************************************************************
簡介:

一般所看到的物品顏色,實際上是物體吸收了照射在其表面的白光而反射出一部分可讓人眼反應看到的有色成分,任何一種顏色都可以利用 RGB 三原色依照不同的比例混合而成。若手邊有 RGB LED 的話,可以仔細一點看進去晶片裡面,就不難發現裡面會有三顆很小的發光晶片,而這三顆就是 紅色 (Red)、綠色 (Green) 和藍色 (Blue) LED 被封裝在一個區域裡面;當要發出白光時,就是將其全比例輸出而得,而其他顏色就是依照比例來得到。

WiKi:RGB color model

在這裡我們不深入去探討色彩,我們著重的是如何得到待測物體表面顏色,以及怎麼呈現相同的顏色。

硬體參考接線圖:

線路使用 Arduino UNO R3 ( 其他 Arduino 開發板也可以 )作為主控,VCC GND 全部都是使用來自 UNO 的 +5V 電源 ( 板上有多個地方都有 +5V 可以接 )。

TCS3200 的 OUT 接 UNO D2S0 - S4 分別接到 UNO D3 - D6

WS2812B 使用 11 顆 ( 當使用顆數變多時,要考慮使用外接 +5V  電源;當然也可以使用其他的可定址的 RGB LED ... 等 ),DIN 控制輸入訊號接到 UNO D12

硬體參考接線圖
白平衡校正:

要取得與待測物體接近的表面顏色,首先就是要建立取色基準,而這個基準的建立稱為白平衡;也就是要知道什麼才是 TCS3200 認定的白色。

白色的 RGB 混色就是全輸出,對於使用 8-bit 顏色分級就是 R:255, G:255, B:255。白平衡取得的 RGB 三原色的數值要再個別除以 255,以得到 RGB 之後取物體表面顏色的補償係數。

TCS3200 對於擷取的顏色,是以不同頻率的 (  50% ) 方波輸出結果,而且分三次分別取得三原色 ( RGB )。三原色的取得不需要分順序,但是必須先設定要過濾取得的三原色 ( R、G 和 B ) 是哪一個 ? 顏色過濾的設定,使用模組上的 S2 S3 接腳,如下所示

S2
S3
L
L
Red
L
H
Blue
H
L
Clean ( 不過濾 )
H
H
Green

簡單取得頻率的方法:就是設定輸出截止時間為 1 秒,因為單位時間事件重複的次數就是頻率 (Hz)。

白平衡校正與取得物品表面顏色用的方法都是一樣的,只不過第一次物體取色是取白色!

取得的物體表面顏色頻率值乘上對應的白平衡 RGB 參數,就是可以用來輸出到 WS2812B RGB LED 上的 RGB 顏色值。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 白平衡校正說明:

進行白平衡校正之前,
  • 請先準備一張白紙 ( 最好很白,越接近白色效果越好 ! )
  • 安裝 Arduino 函式庫 TimerOne
    • TimerOne.zip ( 下載後解壓至 libraries 目錄下 )
    • Arduino IDE 選單:"Sketch / Include Library / Manager libraries...",輸入 TimerOner 選擇安裝
  • 複製下面程式碼到 Arduino IDE,並存檔為喜歡的檔名
#include <TimerOne.h>

#define S0     3
#define S1     4
#define S2     5
#define S3     6
#define OUT    2

int   g_count = 0;    // 頻率計算
int   g_array[3];     // 儲存 RGB 值
int   g_flag = 0;     // RGB 過濾順序
float g_SF[3];        // 儲存白平衡計算後之 RGB 補償係數
 
// TCS3200 初始化與輸出頻率設定
void TSC_Init()
{
  pinMode(S0, OUTPUT);
  pinMode(S1, OUTPUT);
  pinMode(S2, OUTPUT);
  pinMode(S3, OUTPUT);
  pinMode(OUT, INPUT);
 
  digitalWrite(S0, LOW);  // OUTPUT FREQUENCY SCALING 2%
  digitalWrite(S1, HIGH); 
}
 
// 選擇過濾顏色
void TSC_FilterColor(int Level01, int Level02)
{
  if(Level01 != 0)
    Level01 = HIGH;
 
  if(Level02 != 0)
    Level02 = HIGH;
 
  digitalWrite(S2, Level01); 
  digitalWrite(S3, Level02); 
}
 
void TSC_Count()
{
  g_count ++ ;
}
 
void TSC_Callback()
{
  switch(g_flag)
  {
    case 0: 
         Serial.println("->WB Start");
         TSC_WB(LOW, LOW);              // Red
         break;
    case 1:
         Serial.print("->Frequency R=");
         Serial.println(g_count);
         g_array[0] = g_count;
         TSC_WB(HIGH, HIGH);            // Green
         break;
    case 2:
         Serial.print("->Frequency G=");
         Serial.println(g_count);
         g_array[1] = g_count;
         TSC_WB(LOW, HIGH);             // Blue
         break;
 
    case 3:
         Serial.print("->Frequency B=");
         Serial.println(g_count);
         Serial.println("->WB End");
         g_array[2] = g_count;
         TSC_WB(HIGH, LOW);             // Clear(no filter)   
         break;
   default:
         g_count = 0;
         break;
  }
}

// 白平衡
void TSC_WB(int Level0, int Level1) 
{
  g_count = 0;
  g_flag ++;
  TSC_FilterColor(Level0, Level1);
  Timer1.setPeriod(1000000);      // us; 每秒觸發 
}
 
void setup()
{
  TSC_Init();
  Serial.begin(9600);
  Timer1.initialize();             // defaulte is 1s
  Timer1.attachInterrupt(TSC_Callback);  
  attachInterrupt(0, TSC_Count, RISING);  
 
  delay(4000);
 
  for(int i=0; i<3; i++)
    Serial.println(g_array[i]);
 
  g_SF[0] = 255.0/ g_array[0];     // R 補償係數
  g_SF[1] = 255.0/ g_array[1] ;    // G 補償係數
  g_SF[2] = 255.0/ g_array[2] ;    // B 補償係數
 
  Serial.println(g_SF[0]);
  Serial.println(g_SF[1]);
  Serial.println(g_SF[2]);
 
}
 
void loop()
{
   g_flag = 0;
   for(int i=0; i<3; i++)
    Serial.println(int(g_array[i] * g_SF[i]));
   delay(4000);
 
}

將上述程式碼編譯後上傳到 Arduino 開發板。打開 Serial Monitor 並將白紙放置在四顆白光 LED 集中為一點時的位置。距離調整要看手上的模組而定,先將紙靠近 LED,然後再慢慢遠離,這時候就會看到原本在角落的四顆 LED 的燈光向中間集中,直到四點光集中至像下面照片一樣就可以;像下面這片就差不多是 6 公分。
白平衡校正方法
TCS3200 的設計者筆記 "All_AC_Light_Flicker_DN22.pdf" 中說到,外界 AC 燈光對於 TCS3200 的影響,因此在校正白平衡時,我是關掉檯燈與日光燈的 ( 照片是閃光燈 ),降低校正的誤差。
白平衡實際校正- 關掉其他燈光
相關的參數與係數都會輸出到 Serial Monitor 視窗下。其中,

259、219 和 265 是白紙擷取之後的 RGB 頻率值,將其除以 255 可得到 RGB 補償係數值 0.98、1.16 和 0.96,之後輸出的 255、255 和 255 就是反算的結果。之後只要拿個其他物體在前方就會出現該物品計算之後的 RGB 值。
白平衡校正輸出結果
打開 "小畫家",在選單最左方點選 "編輯色彩",輸入上面取得的 RGB 數值,濟會得到相對應的顏色,可以用來驗證取得的物體表面顏色的正確性如何
小畫家編輯色彩畫面

使用 RGB LED 顯示取得的物體表現顏色

上面的方法是一種方法,但是很麻煩!

下面介紹的方法,直接將白平衡校正之後取得的物體表面顏色輸出到 RGB LED 上,比較快也比較直覺!例如:WS2811、WS2812B 和 APA102 都可以直接拿來用,在這裡我們使用 WS2812B。使用之前請先安裝 Adafruit_Neopixel 函式庫,安裝方法請看上面 "白平衡校正說明" 小節,或是手動下載安裝。

下面的由上面的程式碼修改,主要是加入了對於 WS2812B RGB LED 的支援;兩者比較一下就不難發現之間的程式碼差異部分。

由於程式碼中以加入了註解,就不再細部說明。

在 Arduino IDE 開啟一新檔,複製 & 貼上上面程式碼,編譯後上傳。
#include <TimerOne.h>
#include <Adafruit_NeoPixel.h>

struct CRGB {
  union {
    struct {
            union {
                uint8_t r;
                uint8_t red;
            };
            union {
                uint8_t g;
                uint8_t green;
            };
            union {
                uint8_t b;
                uint8_t blue;
            };
        };
    uint8_t raw[3];
  };
};

#define S0     3
#define S1     4
#define S2     5
#define S3     6
#define OUT    2

int   g_count = 0;    // 頻率計算
int   g_array[3];     // 儲存 RGB 值
int   g_flag = 0;     // RGB 過濾順序
float g_SF[3];        // 儲存白平衡計算後之 RGB 補償係數

// 使用多少顆 WS2812B
#define NUM_LEDS 11 

// WS2812B DIN 接腳街道 UNO 的哪根接腳
#define DATA_PIN 13

// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel ws2812 = Adafruit_NeoPixel(NUM_LEDS, DATA_PIN, NEO_GRB + NEO_KHZ800);

CRGB leds[NUM_LEDS];
 
// TCS3200 初始化與輸出頻率設定
void TSC_Init()
{
  pinMode(S0, OUTPUT);
  pinMode(S1, OUTPUT);
  pinMode(S2, OUTPUT);
  pinMode(S3, OUTPUT);
  pinMode(OUT, INPUT);
 
  digitalWrite(S0, LOW);  // OUTPUT FREQUENCY SCALING 2%
  digitalWrite(S1, HIGH); 
}
 
// 選擇過濾顏色
void TSC_FilterColor(int Level01, int Level02)
{
  if(Level01 != 0)
    Level01 = HIGH;
 
  if(Level02 != 0)
    Level02 = HIGH;
 
  digitalWrite(S2, Level01); 
  digitalWrite(S3, Level02); 
}
 
void TSC_Count()
{
  g_count ++ ;
}
 
void TSC_Callback()
{
  switch(g_flag)
  {
    case 0: 
         Serial.println("->WB Start");
         TSC_WB(LOW, LOW);              // Red
         break;
    case 1:
         Serial.print("->Frequency R=");
         Serial.println(g_count);
         g_array[0] = g_count;
         TSC_WB(HIGH, HIGH);            // Green
         break;
    case 2:
         Serial.print("->Frequency G=");
         Serial.println(g_count);
         g_array[1] = g_count;
         TSC_WB(LOW, HIGH);             // Blue
         break;
 
    case 3:
         Serial.print("->Frequency B=");
         Serial.println(g_count);
         Serial.println("->WB End");
         g_array[2] = g_count;
         TSC_WB(HIGH, LOW);             //Clear(no filter)   
         break;
   default:
         g_count = 0;
         break;
  }
}
 
void TSC_WB(int Level0, int Level1)      //White Balance
{
  g_count = 0;
  g_flag ++;
  TSC_FilterColor(Level0, Level1);
  Timer1.setPeriod(1000000);             // us; 每秒觸發 
}
 
void setup()
{
  TSC_Init();
  Serial.begin(9600);

  ws2812.begin();
  ws2812.show();  // Initialize all pixels to 'off'  

  Timer1.initialize();             // defaulte is 1s
  Timer1.attachInterrupt(TSC_Callback);  
  attachInterrupt(0, TSC_Count, RISING);  
 
  delay(4000);
 
  for(int i=0; i<3; i++)
    Serial.println(g_array[i]);
 
  g_SF[0] = 255.0/ g_array[0];     // R 補償係數
  g_SF[1] = 255.0/ g_array[1] ;    // G 補償係數
  g_SF[2] = 255.0/ g_array[2] ;    // B 補償係數
 
  Serial.println(g_SF[0]);
  Serial.println(g_SF[1]);
  Serial.println(g_SF[2]);

  for(int i=0; i<3; i++)
    Serial.println(int(g_array[i] * g_SF[i]));
  Serial.println("Finish Calibration.");
  delay(4000);
 
}
 
void loop()
{
  g_flag = 0;
  // R
  for( int i = 0; i < NUM_LEDS; i++ )
  {
    leds[i].r = int(g_array[0] * g_SF[0]);
    leds[i].g = int(g_array[1] * g_SF[1]);
    leds[i].b = int(g_array[2] * g_SF[2]);
    ws2812.setPixelColor( i, leds[i].r, leds[i].g, leds[i].b );
  }
  Timer1.stop();

  Serial.println(leds[0].r);
  Serial.println(leds[0].g);
  Serial.println(leds[0].b);

  ws2812.show();  // Sends the value to the LED

  Timer1.resume();

  delay(4000);
}

程式碼上傳之後,就會先執行白平衡校正,所以

  1. 請先擺好白紙 (如上一節的校正方式)
  2. 打開 Serial Monitor
  3. 按下 UNO 的 RESET 按鈕,開始執行白平衡校正,直到 Serial Monitor 出現 "Finish Calibration" 的文字,表示完成白平衡校正。

白平衡校正輸出
如下照片所示,抽掉白紙,拿個顏色較鮮明的物體擺放在差不多的位置上試試效果 !

剛好找到一個藍色的香水盒子擺了上去
source: www.lancome.com
TCS3200 物體取色測試 - 開閃光燈
上面的照片拍攝時使用了閃光燈,可以看清楚實際的設置。關掉閃光燈拍攝,就如下照片所示,藍吧  ^_^
TCS3200 物體取色測試 - 關閃光燈

進一步修改建議:

這個程式很方便地進行白平衡校正以及取得物體表面顏色,但每次重置或是重新通電都要做一次白平衡校正,很煩!不過當白平衡校正環境不變之下,可以將計算後的 RGB 補償係數寫死到程式碼中,這樣就不必每次都要進行白平衡校正。

再者,物體表面取色一次花費 4 秒鐘,很長!其中只要物體有一些變動情況產生,很容易造成 RGB 取值錯誤;因為現在為控制器處理的速度很快,因此使用者可以試著縮短 TimerOne 每次觸發的時間,來增加辨識的速度。

辨識之後的顯色,除了可用可定址控制的 RGB LED ( 如:WS2811, WS2812B, APA102 ) 等,也能改用其他可產生全彩的 RGB LED 晶片 !

結論:

白平衡的校正對於之後的物體表面顏色的取色過程非常重要,使用者在這過程中必須考慮到所有外在環境的影響,盡量讓影響降至最低;之後的取色過程也應該維持與校正環境相同的條件來處理,這樣才能讓取色後的 RGB 值儘量與物品表面顏色相近。


<<部落格相關網頁>>

3 則留言:

  1. 你好,我是媒體設計系的學生,想要利用arduino + processing + tcs3200顏色感測器 做一個互動裝置
    這個裝置是希望讓使用者可以戴上不同顏色的帽子
    並用tcs3200去感測
    當感測到rgb某範圍的數值時
    processing會出現一個特定的圖片
    例如:戴著黃色帽子,會出現小小兵的圖片
    戴著紅色帽子,則會出現米奇的圖片

    然而,在製作過程,我們遇到了一些問題
    1.我們希望能讓顏色感測器不用碰到黃色帽子就能感測到,但rgb的數值跳動的幅度會很大,以至於圖片會有時出現有時沒出現ps:我們希望可在沒關燈的環境下,因此沒有關燈
    2.我們希望能在感應到時,出現圖片,並且讓圖片可以停留大概三秒的時間,但是程式寫不出來,目前是寫這樣
    if( (rgb[0] >-100 && rgb[0] <13) && (rgb[1] >-500 && rgb[1] <-450) && (rgb[2] >150 && rgb[2] <200))
    {
    image(img1, 0, 0);
    }
    if( (rgb[0] >80 && rgb[0] <100) && (rgb[1] >50 && rgb[1] <80) && (rgb[2] >260 && rgb[2] <280))
    {
    image(img2, 0, 0);
    }
    //delay(300);
    }

    希望您能告訴我遇到上述問題要如何解決
    非常感謝~~

    回覆刪除
    回覆
    1. 要在暗的環境之下,(四顆LED)才能產生單一的燈光,照樣才能正常的偵測到顏色。所以要先解決如何讓 color sensor 在偵測的時候是在一個暗的環境中,然後盡量維持依定的距離範圍,如此就能得到一個穩定的顏色值 !

      下面的網址對於增加 color sensor 的偵測距離有一些討論;其中,對於在不同距離中偵測相同顏色,就需要先對這幾個距離先進行校正動作,以得到這些距離對於 RGB 值的變化... 等有進行討論與有資料可以參考,相信會有所幫助
      https://forum.arduino.cc/index.php?topic=447517.0

      先能夠在戴上帽子之後,偵測到正確的值之後再來考慮其他的動作,這是最先要做到的事!

      刪除
    2. 非常感謝您的回覆~
      我們後來有在color sensor外做一個遮罩的動作,盡量讓光線不要干擾到偵測,此外在看了您給的網址後,我們決定把裝置改成讓使用者戴好黃色帽子後,再拿桌上的黃色卡片靠緊遮罩偵測,以此讓環境光不要干擾
      再次感謝您的幫助~

      刪除