1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

目次

タイトル 内容
1 はじめに 今回やることの説明
2 カラーセンサの設定 測定前のカラーセンサの設定
3 反射光・周辺光の測定 反射光・周辺光モードでの測定
4 RGBで色の測定 RGBでの色の測定
5 HSVで色の測定 HSVでの色の測定
6 ライトの設定 カラーセンサのライトの設定
7 まとめ 今回のまとめ

1. はじめに

前回はモータを動かすためのAPIを紹介しました。

ここから二回に渡りセンサの使い方を紹介していきます。
今回は、センサの中でも最も重要といっても過言ではない、カラーセンサの使い方です。

カラーセンサのAPIの説明は以下のページに書かれています。本記事はそれをかみ砕いて、LEGO EducationのSPIKEソフトでの例も交えながら紹介していきます。

SPIKE-RT C API Reference [Japanese] / カラーセンサ

カラーセンサの仕様

以下に示すのが、「SPIKEプライム カラーセンサー」 です。

04_01-300x200.jpg

中央部にあるフォトダイオードにより、色・反射光・周辺光りの測定が可能です。
又、フォトダイオードの周りには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. カラーセンサのセットアップ

spikert_5
#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)の混ざり具合で色を表現するものです。

AdditiveColorMixing.svg.png

(出典: 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型変数rgbが用意されています。

尚、色の測定と構造体の説明については、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関数を用いております。
これにより完成した文字列strhub_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 (明度) の三要素で表す色空間です。
字面で見てもわかりにくいので、以下に図を示します。

300px-HSV_cylinder.jpg

(出典: Wikipedia - HSV色空間 https://ja.wikipedia.org/wiki/RGB)

まずHue(色相)ですが、これは色の種類を表しており、具体的には色相環における角度を表しています。
色は光の波長の違いにより変化しますが、これを連続的に繋げたものが色相環です。

測定においては、基準に対して何度差があるかを値として示します。
SPIKE-RTにおいては、赤の部分に基準($0$)が設定されており、以下のように値が変化していきます。

Hue.png

(あくまでイメージであるため、実際の測定値とは異なることがあります。)

続いて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の点灯・消灯・および点灯時の明るさ(パーセント)を個別に指定することが出来ます。

color_led.jpg

ライト全消灯

まずは、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 フォースセンサと超音波センサの使い方

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?