Tanuki_Bayashin’s diary

電子工作を趣味としています。最近はラズベリーパイPicoというマイコンを使って楽しんでいます

自作の色を作り出す関数を検証してみた~HSV色空間

※なにか気になる点がありましたらコメント欄にご記入ください。また、工作や回路を製作する場合には、細かい作業などに対して、細心の注意を払われるようお願いいたします。


1.はじめに

 以前、筆者は以下の記事で独自のやり方で、虹色をNeopixelに描いてみました。

tanuki-bayashin.hatenablog.com

しかし、最近眼にしたYoutubeの動画を見ていたら、「DHAの電子工作教室」というチャンネルで、HSVという色の表現方法があることが紹介されていました。(以下参照)

youtu.be

このHSVという表現形式によると、色の別の見方が分かることを知りました。
今回は筆者が独自で作った色について、この動画の内容をもとに検証してみたので、書いていきたいと思います。

2.HSVについて

簡単にいうと、色相(Hue)、彩度(Saturation)と明度(Value)の3つの値で色を示す表現方法です。(頭文字を取ってHSVとなります)
色相は色の種類、彩度は色の鮮やかさ、明度は明るさの度合いを示しているようです。(詳しくは先ほどの動画をご覧ください)

ちなみに、色相は色の違いを0°~360°の値で示します。赤が0°(=360°)、緑が120°、青が240°の辺りで、その間が中間色となります。

また、彩度は0~255で示し、0のときはモノクロの状態で、255になると鮮やかとなる感じです。

さらに明度も0~255で示し、0なら真っ黒、255なら真っ白となります。(その間だといずれかの色を示します)

※彩度と明度に関しては、0~1で表したり、100%表示するなどいろいろあるようです。

そして動画の中では、RGB(赤、緑、青)で表現された色を、HSVに変換する関数が紹介されていました。(少し修正させていただきました)

それにより、以下のような検証を行いました。

  • それぞれの色の色相を調べる。(ちゃんと様々な色を表現できているか)
  • 彩度を調べる(どれくらいの鮮やかさなのか)
  • 明度を調べる。(明度は一定になっているか)

HSVに変換すれば、1度ですべての値が求まるので、実際の作業は楽でした。

3.検証結果

検証に用いたソースコードを以下に示します。
まず筆者独自の方法で色を作り、それをHSVの値に変換します。その後、その数値から、検証を行います。

※ライブラリcvt_col.pyは 1.はじめに の「DHAの電子工作教室」さんが制作されたものです。
下のURLの動画の説明欄(?)にソースコードへのリンク先があります。
#14-4 HSVでフルカラーLED制御 ~DHAの電子工作教室~【Raspberry Pi Pico】 - YouTube

リスト1 検証に用いたソーズコード

import cvt_col
import matplotlib.pyplot as plt

#各種配列を用意します
col = []
hsv = []
time = []
ss = []
hh = []
vv = []

# カラフルな色を作るのに必要な関数です
def triangle(x):
    if x < 20.0:
        y = int(1.5 * x)
    elif x < 40.0:
        y = 60 - int(1.5 * x)
    else:
        y = 0
    return y

#60色の色を生成しています
for i in range(60):
    p = float(i)
    x = (p + 20.0) % 60.0
    y = p
    z = (p + 40.0) % 60.0
    r = int(triangle(x))
    g = int(triangle(y))
    b = int(triangle(z))
    col.append([r,g,b])

#「DHAの電子工作教室」さんによるrgbからhsvを求める関数です
    (h, s, v) = cvt_col.rgb_to_hsv( r, g, b )
    hsv.append([h,s,v])
    time.append(i)
    hh.append(h)
    ss.append(s)
    vv.append(v)

#===========================================================
#Draw graph
#===========================================================
#色相のグラフ
plt.title('Graph of Heu')
plt.plot(time, hh, color="red", lw=2)
plt.show()

#彩度のグラフ
plt.title('Graph of Saturation')
plt.plot(time, ss, color="green", lw=2)
plt.show()

#明度のグラフ
plt.title('Graph of Value')
plt.plot(time, vv, color="blue", lw=2)
plt.show()

このソースコードにより、色相、彩度、明度のグラフを描き、どのような具合なのかを調べました。

グラフ1 色相(H)のようす
グラフ2 彩度(S)のようす
グラフ3 明度(V)のようす

※横軸はあえて言えば、色の番号みたいなものです。

グラフ1を見てみると、色相が0°から360°に向かって増加しているのが分かります。いくらか波打っていますが、おおよそ様々な色を彩っているのが分かります。

グラフ2を見ると、見事なまでに彩度が255と最高値を取っていることが分かります。(数値でも確認しました)
特に知識があった訳ではないのですが、偶然とは言え、鮮やかな色で光らせていたことになるので、嬉しいです。

グラフ3を見ると、明度(V)の変化の様子が分かります。30と15の間を行ったり来たりしています。
色相と比較すると、赤、緑、青の色の辺りで高い値を取り、黄色やマゼンタなどの中間色で低い値を取っています。

明度は15から30と2倍の違いがあることが分かりました。調べてみないと分からないものです。

4.明度を一定にした場合

ライブラリcvt_col内の関数では明度を一定にしてrgbの値を求めることもできるので、それを用いてNeopixelを光らせることを行ってみます。(DHAさんの動画の中でも紹介されていました)

色を作る部分は以下のようになります。

スト2 明度を一定にして虹色を光らせるコード

#必用なライブラリをインポートします
import array, utime, math
from machine import Pin
import rp2
from rp2 import PIO, StateMachine, asm_pio
import cvt_col

#WS2812 のLEDの数を指定します
NUM_LEDS = 100
# Pico特有のstate machine を用いてWS2812の制御を行っています
#このあたりの処理はリスト1の下のリンク先の図書より引用しました
# PIO State Machine to display ws2812
@asm_pio(sideset_init=PIO.OUT_LOW, out_shiftdir=PIO.SHIFT_LEFT, autopull=True,\
         pull_thresh=24)
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    label("bitloop")
    out(x, 1)                .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")    .side(1)    [T1 - 1]
    jmp("bitloop")           .side(1)    [T2 - 1]
    label("do_zero")
    nop()                    .side(0)    [T2 - 1]

# Create the StateMachine with the ws2812 program, outputting on Pin(16)().
sm = StateMachine(0, ws2812, freq=8000000, sideset_base=Pin(16))

# start the StateMachine, it will wait for data on its FIFO.
sm.active(1)

# Display a pattern on the LEDs via an array of LED RGB values.
ar = array.array("I", [0 for _ in range(NUM_LEDS)])

#position の位置のLEDをred, green, blue の値にて指定します
def ar_color(position, red, green, blue):
    ar[position] = (green<<16) + (red<<8) + blue

#全てのLEDを消灯する関数です
def clear_all():
    for i in range(NUM_LEDS):
        ar[i] = 0
    sm.put(ar,8)

# 3個のリストのメモリーを確保しています
red   = [0.0 for i in range(NUM_LEDS)]
green = [0.0 for i in range(NUM_LEDS)]
blue  = [0.0 for i in range(NUM_LEDS)]

color = []
index = []
#ここから処理がスタートします
if __name__ == '__main__':
    # Process arguments
    print('Press Ctrl-C to quit.')
    
    # ここで色のテーブルを作っています。(colorというリストに値を代入しています)
    # 色の数は6°おきに60色、彩度は255、明度は検証の結果から23としました
    for i in range(60):
        (r, g, b) = cvt_col.hsv_to_rgb( i*6, 255, 23 )
        color.append([r, g, b])

    # Neopixelを同心円上に色を変化させて表示するための計算です
    r_max = math.sqrt(4.5 * 4.5 * 2)
    for i in range(NUM_LEDS):
        x = float(i % 10) - 4.5    # 左から何列目かを計算し、4.5を引いています
        y = float(i) / 10.0 - 4.5  # 下から何段目かを計算し、4.5を引いています

        # 中心(4.5, 4.5)からの距離を求め、最大値を1に規格化しています
        r = math.sqrt(x*x + y*y) / r_max

        # 同心円状に色を変えて表示するための処理です。
        # 色相を中心と端で240°(=6°×40)異なるようにしています。
        index.append(int(r*40))
    
    
    try:
        # 無限ループです
        while True:

            #それぞれのLEDの色を計算し、表示しています
            for i in range(NUM_LEDS):
            
                # 1ステップ前から色を1つずらします
                index[i]   = (index[i]   - 1) % 60

                # テーブルcolorにより色を取得しています
                red[i]   = color[index[i]][0]
                green[i] = color[index[i]][1]
                blue[i]  = color[index[i]][2]

                # 色を表示しています
                ar_color(i, red[i], green[i], blue[i])
            sm.put(ar,8)
            utime.sleep_ms(10)

    # ctl-C が押されたときの処理です
    except KeyboardInterrupt:
        #### clear ws2812b
        clear_all()

実際に色を光らせた様子を示します。

youtu.be

以前の独自の色を作る方法による場合

youtu.be

5.まとめ

HSVの各値を求める関数を用いて、独自の色を作る方法について検証してみました。
その結果、以下のことが分かりました。

  • 色相(H)はおおよそ、様々な色を表現できていた。
  • 彩度(S)は255と最高の値だった。
  • 明度(V)は最大と最低で2倍の値の差があった。

このことも踏まえて、さらに明度を一定にしてNeopixelを虹色に光らせてみました。
独自の手法による場合と比べてみたのですが、それほどの違いは(自分の肉眼では)感じられなかったです。

意外と色の違いにより明暗の差をつけたほうが、アクセントがついて面白みがある場合もあるかも知れないです(負け惜しみ)。

以上です。