目次
章 | タイトル | 内容 |
---|---|---|
1 | はじめに | 今回やることの説明 |
2 | カラーセンサの設定 | 測定前のカラーセンサの設定 |
3 | 反射光・周辺光の測定 | 反射光・周辺光モードでの測定 |
4 | RGBで色の測定 | RGBでの色の測定 |
5 | HSVで色の測定 | HSVでの色の測定 |
6 | ライトの設定 | カラーセンサのライトの設定 |
7 | まとめ | 今回のまとめ |
1. はじめに
前回はモータを動かすためのAPIを紹介しました。
ここから二回に渡りセンサの使い方を紹介していきます。
今回は、センサの中でも最も重要といっても過言ではない、カラーセンサの使い方です。
カラーセンサのAPIの説明は以下のページに書かれています。本記事はそれをかみ砕いて、LEGO EducationのSPIKEソフトでの例も交えながら紹介していきます。
SPIKE-RT C API Reference [Japanese] / カラーセンサ
カラーセンサの仕様
以下に示すのが、「SPIKEプライム カラーセンサー」 です。
中央部にあるフォトダイオードにより、色・反射光・周辺光りの測定が可能です。
又、フォトダイオードの周りにはLEDが3つ搭載されており、これらもプログラムにより光り方を変えることが出来ます。
詳しい仕様については以下👇をご覧ください。
2. カラーセンサの設定
モータの時と同様に、センサを使う前には「ポート」と「カラーセンサ」の結びつけが必要です。
pup_device_t *pup_color_sensor_get_device(pbio_port_id_t port);
関数名はpup_color_sensor_get_device
で、「ポート」の情報により、「カラーセンサ」と「PUPデバイスポインタ」を結びつけます。
引数として、「ポート」を指定すると、戻り値として「PUPデバイスポインタ」が返ってきます。
使い方の例も示しておきます。
Ex2-1. カラーセンサのセットアップ
#include <stdlib.h>
#include <stdio.h>
#include <kernel.h>
#include <spike/hub/system.h>
#include <color_test.h>
#include "spike/pup/motor.h"
#include "spike/pup/colorsensor.h"
#include "spike/pup/forcesensor.h"
#include "spike/pup/ultrasonicsensor.h"
#include "spike/hub/battery.h"
#include "spike/hub/button.h"
#include "spike/hub/display.h"
#include "spike/hub/imu.h"
#include "spike/hub/light.h"
#include "spike/hub/speaker.h"
#include <pbio/color.h>
#include "kernel_cfg.h"
#include "syssvc/serial.h"
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device(PBIO_PORT_ID_C);
}
はじめにcolorC
という名前で「PUPデバイスポインタ」を宣言しています。
Main
関数に入ったら、pup_color_sensor_get_device
関数により「ポートC」が「カラーセンサ」であると結び付けて、colorC
に格納しています。
尚、前回もお伝えしましたが、ポートの定数PBIO_PORT_ID_C
などを使うためには、内部のファイルを編集する必要があります。
編集方法については前回の記事の2章をご覧ください。
ファイルの編集を避ける場合は、以下のようにポートを指定することも出来ます。
Ex2-2. カラーセンサのセットアップ (ポート指定方法)
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device('C');
}
3. 反射光・周辺光の測定
ではカラーセンサの設定が出来たので、実際に値を測定していきましょう。
まずは、反射光と周辺光の測定方法からです。
反射光
反射光モードでは、カラーセンサのLEDを点灯させて、対象物から反射された光の強度を測定します。
int32_t pup_color_sensor_reflection(pup_device_t *pdev);
関数名はpup_color_sensor_reflection
で、反射光を測定します。
引数に「PUPデバイスポインタ」を入力すると、戻り値として「反射光」が整数値で返ってきます。
このモードですが、白地に引かれた黒線上をライントレースする時などには、色のことを考えなくて良いので有効です。
では、値を測定してディスプレイに表示する例を示します。
Ex2-3. 反射光を測定する
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device(PBIO_PORT_ID_C);
while(1){
int ref = pup_color_sensor_reflection(colorC);
hub_display_number(ref);
}
}
カラーセンサのセットアップを行った後、無限ループに入ります。
ループ内では、pup_color_sensor_reflection
関数により反射光を測定し、変数ref
に格納しています。
その変数の値を、hub_display_number
関数を用いてハブディスプレイに表示しています。
周辺光
周辺光モードでは、カラーセンサのLEDを消灯させて、対象物の明るさを測定します。
int32_t pup_color_sensor_ambient(pup_device_t *pdev);
関数名はpup_color_sensor_ambient
で、周辺光を測定します。
引数に「PUPデバイスポインタ」を入力すると、戻り値として「周辺光」が整数値で返ってきます。
先ほど同様、値を測定してディスプレイに表示する例を示します。
Ex3-1. 周辺光を測定する
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device(PBIO_PORT_ID_C);
while(1){
int amb = pup_color_sensor_ambient(colorC);
hub_display_number(amb);
}
}
基本的には反射光の時と同様の構造で、カラーセンサのセットアップを行った後、無限ループに入り、ループ内でpup_color_sensor_ambient
関数により周辺光を測定し、変数amb
に格納しています。
その変数の値を、hub_display_number
関数を用いてハブディスプレイに表示しています。
4. RGBで色の測定
いよいよ色の測定です。
色の測定方法にはいくつかの方法があるのですが、まずは RGB での測定方法から解説していきます。
RGBとは
「色」には様々な表現があります。
「赤」や「青」という名前も色の表現の一つですが、その「赤」や「青」は人によって個人差がありますよね。
これを、数値によって定量的に表そうというのが「色空間」と呼ばれるものです。
この「色空間」にもさまざまな種類があり、それぞれに得意・不得意があるため、時と場合に応じて使い分けています。
それでは本題の RGB色空間 ですが、光の三原色である赤(Red)、緑(Green)、青(Blue)の混ざり具合で色を表現するものです。
(出典: Wikipedia - RGB https://ja.wikipedia.org/wiki/RGB)
それぞれにパラメータがあり、赤であれば(255, 0, 0)、緑であれば(0, 255, 0)、青であれば(0, 0, 255)のように表現されます。
(各パラメータごとに8bitでの表現を想定し、値の範囲は$0\sim255$)
測定方法
では、測定するための関数を紹介します。
pup_color_rgb_t pup_color_sensor_rgb(pup_device_t *pdev);
関数名はpup_color_sensor_rgb
で、対象物の色をRGB形式で測定します。
引数には「PUPデバイスポインタ」を入力します。
戻り値として、R・G・Bそれぞれの値が返されるのですが、この時これらの値はpup_color_rgb_t
という構造体で返されます。
ここで、構造体pup_color_rgb_t
について、その定義を記しておきます。
typedef struct {
uint16_t r, g, b;
} pup_color_rgb_t;
構造体pup_color_rgb_t
の中に、int型変数r
、g
、b
が用意されています。
尚、色の測定と構造体の説明については、EV3rtの記事で詳しく説明しておりますので、そちらも是非ご覧ください。
では、測定した値をハブディスプレイに表示してい見たいと思います。
Ex4-1. 色をRGBで測定する
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device(PBIO_PORT_ID_C);
while(1){
// 色の検出 (trueならLED点灯)
pup_color_rgb_t rgb = pup_color_sensor_rgb(colorC);
char str[32];
sprintf(str, "R:%d G:%d B:%d", rgb.r, rgb.g, rgb.b);
hub_display_text_scroll(str, 100);
}
}
先ほど同様セットアップを行った後、無限ループに入ります。
pup_color_sensor_rgb
関数により測定した値を、構造体変数rgb
に格納します。
ここで、ハブディスプレイに3桁以上の値を表示したいため、hub_display_text_scroll
関数を使用しています。
ハブディスプレイの表示については別の回で詳しく解説しますが、ここでも簡単にだけ説明しておくと、char型配列str
内に文字列を格納したいため、sprintf
関数を用いております。
これにより完成した文字列str
をhub_display_text_scroll
関数に渡して、値を表示しています。
尚、pup_color_sensor_rgb
により値が格納される際、値は正規化されておらず、私が確認する限り最大で900くらいの値が出ています。
よって、実際に使用する際にはパーセンテージ(混合の割合)に変換するなどして、正規化する必要があるかと思います。
例えばこんな感じ👇
Ex4-2. 色をRGBで測定する(正規化済み)
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device(PBIO_PORT_ID_C);
while(1){
// 色の検出 (trueならLED点灯)
pup_color_rgb_t rgb = pup_color_sensor_rgb(colorC);
double all = rgb.r + rgb.g + rgb.b;
double r_rate = rgb.r / all;
double g_rate = rgb.g / all;
double b_rate = rgb.b / all;
char str[32];
sprintf(str, "R:%lf G:%lf B:%lf", r_rate, g_rate, b_rate);
hub_display_text_scroll(str, 100);
}
}
上記の例では、赤・緑・青の合計値をall
に格納し、赤であれば元の値をall
で割ることにより赤の混合割合を算出し、r_rate
に保存しています。同様に、緑と青の混合割合も計算しています。
実際に上のサンプルなどで値を測定し、確かめてみて下さい。
5. HSVで色の測定
先ほどは色の表現方法として「RGB色空間」を用いましたが、ここからはまた別の色空間を使用していきます。
それが、「HSV色空間」 です。
HSV色空間
HSVとは Hue (色相) 、 Saturation (彩度) 、 Value (明度) の三要素で表す色空間です。
字面で見てもわかりにくいので、以下に図を示します。
(出典: Wikipedia - HSV色空間 https://ja.wikipedia.org/wiki/RGB)
まずHue(色相)ですが、これは色の種類を表しており、具体的には色相環における角度を表しています。
色は光の波長の違いにより変化しますが、これを連続的に繋げたものが色相環です。
測定においては、基準に対して何度差があるかを値として示します。
SPIKE-RTにおいては、赤の部分に基準($0$)が設定されており、以下のように値が変化していきます。
(あくまでイメージであるため、実際の測定値とは異なることがあります。)
続いてSaturation(彩度)は、色の鮮やかさを表す値です。
視覚的に説明すると、上図の円柱の外側に行けば行くほど色がビビット(鮮明)になり、値は大きくなります。
一方、円柱の内側に行けば行くほど色はくすみ、値は小さいくなります。
値の範囲は$0 \sim 100$です。
最後にValue(明度)は、色の明るさを表す値です。
こちらも上図で説明すると、円柱の高さ方向が明度に相当し、円柱の上の方へ行くほど明るくなり、値は大きくなります。
一方、底の方へ行くほど色は黒くなり、値は小さくなります。
こちらも値の範囲は$0 \sim 100$です。
測定方法
では、測定するための関数を紹介します。
pup_color_hsv_t pup_color_sensor_hsv(pup_device_t *pdev, bool surface);
関数名はpup_color_sensor_hsv
で、対象物の色をHSV形式で測定します。
第一引数には「PUPデバイスポインタ」を入力します。
又、第二引数はLEDの点灯状況を指定するもので、True
なら点灯、False
であれば消灯します。
戻り値として、H・S・Vそれぞれの値が返されるのですが、この時これらの値はpup_color_hsv_t
という構造体で返されます。
ここで、構造体pup_color_hsv_t
について、その定義を記しておきます。
/** HSV color. */
typedef struct {
/** The hue component. 0 to 359 degrees. */
uint16_t h;
/** The saturation component. 0 to 100 percent. */
uint8_t s;
/** The value component. 0 to 100 percent. */
uint8_t v;
} pbio_color_hsv_t;
typedef pbio_color_hsv_t pup_color_hsv_t;
構造体pup_color_hsv_t
は、もともと別で存在する構造体pbio_color_hsv_t
をリネームしたもので、そのpbio_color_hsv_t
はメンバとしてint型変数h
(範囲$0\sim359$)、s
(範囲$0\sim100$)、v
(範囲$0\sim100$)を持っています。
ではRGBの時と同様に、測定した値をハブディスプレイに表示してい見たいと思います。
Ex5-1. 色をHSVで測定する
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device(PBIO_PORT_ID_C);
while(1){
// 色の検出 (trueならLED点灯)
pup_color_hsv_t hsv = pup_color_sensor_hsv(colorC, true);
char str[32];
sprintf(str, "H:%d S:%d V:%d", hsv.h, hsv.s, hsv.v);
hub_display_text_scroll(str,100);
}
}
カラーセンサの設定を行った後無限ループに入り、HSVでの測定を行います。
測定した値は構造体変数hsv
に入るため、それらをsprintf
関数内で取り出し、char型配列str
に格納してハブディスプレイへ表示できるようにしています。
このpup_color_sensor_hsv
関数を使う方法が基本の測り方になるかと思います。
Hueの値を用いて、「ここからここまでの値の範囲であれば赤」のような条件分岐を作ることで色判定が可能になります。
ただし、白や黒といった、Hueの変化に現れない色を判定したい場合は、他のSaturationやValueの値も使う必要があるでしょう。
ちなみに、HSVの色判定については以下の記事で詳しく解説されています。
下記の記事はmicroPython用に書かれたもので言語が異なりますが、考え方自体は同じなのでぜひ参考にしてみてください。
測定可能な色を限定する
さて、先ほどはHSVの値をそのまま取得しましたが、SPIKE-RTには測定可能な色を限定してHSVを取得する関数があります。
まずは以下にその関数を示します。
pup_color_hsv_t pup_color_sensor_color(pup_device_t *pdev, bool surface);
関数名はpup_color_sensor_color
で、対象物の色をHSV形式で測定します。
ただし、HSVの値は、事前に設定した色に限定して返されます。
引数、および戻り値に関してはpup_color_sensor_hsv
関数と全く同じです。
ここで、「測定可能な色が限定される」の部分について、もう少し詳しく記したいと思います。
pup_color_sensor_color
関数を使うにあたり、内部で「特定の色のHSV値」が定義されています。
pup_color_hsv_t cb_color_map_default[] = {
{ PBIO_COLOR_HUE_RED, 100, 100 }, // Red
{ PBIO_COLOR_HUE_YELLOW, 100, 100 }, // Yellow
{ PBIO_COLOR_HUE_GREEN, 100, 100 }, // Green
{ PBIO_COLOR_HUE_BLUE, 100, 100 }, // Blue
{ 0, 0, 100 }, // White
{ 0, 0, 0 }, // None
};
HSVを格納するための構造体pup_color_hsv_t
を用いて、構造体配列cb_color_map_default
が定義されています。
上から順番に、赤・黄・緑・青・白・無しのHSV値が定義されています。
尚、各色の定数についても以下のように定義されています。
typedef enum {
/** Red. */
PBIO_COLOR_HUE_RED = 0,
/** Orange. */
PBIO_COLOR_HUE_ORANGE = 30,
/** Yellow. */
PBIO_COLOR_HUE_YELLOW = 60,
/** Green. */
PBIO_COLOR_HUE_GREEN = 120,
/** Spring green. */
PBIO_COLOR_HUE_SPRING_GREEN = 150,
/** Cyan. */
PBIO_COLOR_HUE_CYAN = 180,
/** Blue. */
PBIO_COLOR_HUE_BLUE = 240,
/** Violet. */
PBIO_COLOR_HUE_VIOLET = 270,
/** Magenta. */
PBIO_COLOR_HUE_MAGENTA = 300,
} pbio_color_hue_t;
その上でpup_color_sensor_color
関数ですが、対象物のHSV値を測定した後、cb_color_map_default
の各値と比較して、一番近い組み合わせのHSV値に変換して返します。
例えば、得られたHSV値が$(10, 80, 80)$のとき、一番近いHSV値の組み合わせは赤の$(0, 100, 100)$となります。従って、戻り値は$(0, 100, 100)$となります。
この関数を使うことで、得られる値が限定されるので、条件分岐がやりやすくなるかと思います。
例えば👇こんな感じ
pup_color_hsv_t hsv = pup_color_sensor_color(colorC, true);
if(hsv.v == 0){
// 無し
}
else if(hsv.s == 0){
// 白
}
else if(hsv.h == PBIO_COLOR_HUE_RED){
// 赤
}
else if(hsv.h == PBIO_COLOR_HUE_YELLOW){
// 黄
}
else if(hsv.h == PBIO_COLOR_HUE_GREEN){
// 緑
}
else if(hsv.h == PBIO_COLOR_HUE_BLUE){
// 青
}
else{
// エラー状態
}
HSVの生値の場合、条件式は不等号を用いて範囲で指定する必要がありますが、pup_color_sensor_color
関数では得られる値の組み合わせが分かっているため、条件式も簡略化出来ます。
測定可能色の再定義
pup_color_sensor_color
関数を用いると、測定可能な色を限定できることをお話しましたが、事前に定義された色の組み合わせcb_color_map_default
が必ずしもそれぞれの環境で適合するとは限りません。
そこで、測定可能な色を自分で再定義することが出来るので、その方法を紹介します。
まずcb_color_map_default
と同じ要領で、構造体pup_color_hsv_t
で構造体配列を宣言します。
pup_color_hsv_t color_map_edit[] = {
{ 352, 91, 95 }, //Red
{ 56, 53, 100 }, //Yellow
{ 155, 62, 46 }, //Green
{ 212, 93, 72 }, //Blue
{ 195, 80, 97 }, // Skyblue
{ 345, 76, 50 }, // Purple
{ 0, 5, 80 }, //White
{ 0, 0, 0 },//None
};
今回は、color_map_edit
という配列名で、上記の8色分HSV値の組み合わせを定義しました。
ここでのHSV値は、pup_color_sensor_hsv
関数を用いてHSVの生値を測定して決めました。
尚、色の組み合わせは30色まで定義可能です。
次に、定義した構造体配列を元に、測定可能色を再定義していきます。
そこで使うのが、以下の関数です。
pup_color_hsv_t *pup_color_sensor_detectable_colors(int32_t size, pup_color_hsv_t *colors);
関数名はpup_color_sensor_detectable_colors
で、pup_color_sensor_color
関数で測定可能な色を再定義します。
第一引数には、再定義に用いる構造体配列の色の組み合わせの数を入力します。
第二引数には、再定義に用いる構造体配列を指定します。
それでは、構造体配列の定義から測定可能色の再定義、そこからHSVの測定までの流れを示したいと思います。
Ex5-2. 色をHSVで測定する(測定色限定)
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device(PBIO_PORT_ID_C);
pup_color_hsv_t color_map_edit[] = {
{ 352, 91, 95 }, //Red
{ 56, 53, 100 }, //Yellow
{ 155, 62, 46 }, //Green
{ 212, 93, 72 }, //Blue
{ 195, 80, 97 }, // Skyblue
{ 345, 76, 50 }, // Purple
{ 0, 5, 80 }, //White
{ 0, 0, 0 },//None
};
pup_color_sensor_detectable_colors(8, color_map_edit);
while(1){
// 色の検出 (trueならLED点灯)
pup_color_hsv_t hsv = pup_color_sensor_color(colorC, true);
char str[32];
sprintf(str, "H:%d S:%d V:%d", hsv.h, hsv.s, hsv.v);
hub_display_text_scroll(str,100);
}
}
構造体配列color_map_edit
として、測定色を8色分定義した後、pup_color_sensor_detectable_colors
関数を実行して測定可能色を再定義しています。
その後は無限ループに入り、pup_color_sensor_color
関数を実行して色を測定し、ハブディスプレイに表示しています。
この測定可能色再定義→限定色測定の流れを使うと、かなりの精度で色判定を正確に行えるのではと思います。
ただし、測定可能色を増やせば増やすほど、意図しない色に判定されたりもするので、これらは自分で良い塩梅を見極める必要があります。
6. ライトの設定
最後に小ネタとして、カラーセンサのライトの制御について解説しておきます。
SPIKE Primeのカラーセンサには、3つのLEDが入っています。
これらのLEDの点灯・消灯・および点灯時の明るさ(パーセント)を個別に指定することが出来ます。
ライト全消灯
まずは、3つのLEDを全て消す関数です。
pbio_error_t pup_color_sensor_light_off(pup_device_t *pdev);
関数名はpup_color_sensor_light_off
で、3つのLEDを全て消灯します。
引数には「PUPデバイスポインタ」を入力します。
この関数を実行することで、「PUPデバイスポインタ」により指定したポートのカラーセンサのLEDを全消灯します。
ライト全点灯
続いて、3つのLEDを全て点ける関数です。
pbio_error_t pup_color_sensor_light_on(pup_device_t *pdev);
関数名はpup_color_sensor_light_on
で、3つのLEDを全て点灯します。
引数には「PUPデバイスポインタ」を入力します。
この関数を実行することで、「PUPデバイスポインタ」により指定したポートのカラーセンサのLEDを全点灯します。
また、この時の明るさは指定できる最大の値(100%)で、デフォルトの状態よりもかなり明るいです。
ライトの明るさを個別に指定
最後に、LEDを1個ずつ個別に明るさを変更する関数です。
pbio_error_t pup_color_sensor_light_set(pup_device_t *pdev, int32_t bv1, int32_t bv2, int32_t bv3);
関数名はpup_color_sensor_light_set
で、LEDを1個ずつ個別に明るさを変更します。
第一引数には「PUPデバイスポインタ」を入力します。
第二・三・四引数には、それぞれのLEDでの明るさを$0\sim100$の範囲で指定します。
これらの関数の使い方についても簡単に例をしめしておきます。
Ex6-1. カラーセンサのLEDを設定
pup_device_t *colorC;
void Main(intptr_t exinf)
{
colorC = pup_color_sensor_get_device(PBIO_PORT_ID_C);
pup_color_sensor_light_off(colorC); // 全部消す
dly_tsk(3*1000*1000);
pup_color_sensor_light_on(colorC); // 全部MAXで点ける
dly_tsk(3*1000*1000);
pup_color_sensor_light_set(colorC, 10, 10, 10); // 任意の値で点ける
dly_tsk(3*1000*1000);
pup_color_sensor_light_set(colorC, 100, 0, 0); // LED一つだけを点灯することも出来る
dly_tsk(3*1000*1000);
}
これらの関数について、実用性があるのかは微妙ですが、APIとして用意されているので紹介しました。
7. まとめ
今回は、センサの中でもかなり重要なカラーセンサの使い方を解説しました。
関数の種類も沢山あり、何から使えばいいか迷う(困る)ところですが、状況に応じて使い分けられるようになりましょう。
次回はカラーセンサ以外の他のセンサを紹介します。
フォースセンサ(力センサ)と、超音波センサの2つですね。
前回: #4 モータの使い方
次回: #6 フォースセンサと超音波センサの使い方