HSV色相系(HSB色相系)

サイト全体の目次

稿


稿

色の表現方法

ご存知のとおり、光は「光の三原色」すなわち「赤(Red)」「緑(Green)」「青(Blue)」の三色を用いることですべての色を表現することができます(以下このページでは、この表現方法をRGB表現と呼びます)。もう少し詳しく言うと、各色の明るさを変えることでさまざまな色を表現することができるのです。これはコンピュータでも同じことで、画像中の画素はその点でそれぞれの色がどれくらい光っているかを表す輝度値という情報で表現されます。

例えば24bitカラーの場合、各色の輝度値は8bit、すなわち256階調で、0(まったく光っていない)~255(最も明るい)の間の数値により表されます。ですので、256の3乗色(約1678万色)を表現することができます。以下に例を示します。

(R,G,B)=(255,000,000)->赤色
(R,G,B)=(255,255,000)->黄色
(R,G,B)=(255,128,000)->橙色
(R,G,B)=(128,255,255)->水色
			

しかし、この表現方法は人間にとってあまり直感的なものではありません。もし「赤が中程度、緑が中程度の半分、青がまったく光っていない色は?」と問われてとっさに「薄暗い橙色」と答えられるでしょうか?あるいは逆に「淡くて薄暗い橙色」という色は創造できても赤緑青がそれぞれどれくらい光っているのかはわからないのではないでしょうか。

つまり人間にわかりやすい表現とは「こういう色合いで、このくらい淡くて、これくらい明るい」といったように「色合い(赤とか橙とか緑といった色の表現)」を基準としたものなのです。ちなみに、この色合いのことを色相と呼びます。

HSV色相系

色相を基準とした色の表現方法にHSV色相系またはHSB色相系と呼ばれるものがあります。これは色を「色相(Hue)」「彩度(Saturation)」「明度(Value of Brightness)」によって表現するもので、人間にもわかりやすく、また、以下で説明するとおり比較的容易にRGBから変換またはRGBへ変換することもできます。ちなみに、彩度とは色の鮮やかさ、つまり濃淡のことだと考えてください。濃い色はより鮮やかで原色に近く、淡い色はより無色に近いということになります。ただし無色とは透明ということではありません。最も明るい無色は白で、最も暗い無色は黒です。

色相環

さて、彩度(色の濃淡)と明るさは単純に数値で表すことができます。それぞれ最小値を「0」、最大値を「255」とすれば、彩度は0が無色で255が原色、明度は0が最も暗くて255が最も明るいと定義することができます。

では、色相はどのように定義すればよいのでしょうか。実は色相環と呼ばれる図があります。色相環は円の真ん中を中心とし、赤を基準(0度)に何度の色かといった具合に、色を定義します。例えば、0度は赤、120度は緑、240度は青といった具合です。.

また、これに彩度を加えると.のようになります(以下このページでは、色相環に彩度を加えた円を色相円と呼びます)。

さらに上の図全体を明るくしたり暗くしたりすることで明度を表現します。上の図は最も明るい状態(明度が255)、.は半分程度に明るい状態(明度128)です。ちなみに、真っ暗な状態(明度が0)は円全体が真っ黒です。

RGB表現からHSV色相系への変換

説明をする前に、それぞれの表現方法を明確にしておきたいと思います。ここでは、RGB表現でとりうる値を

赤の輝度値 0~255
緑の輝度値 0~255
青の輝度値 0~255
			
とし、HSV色相系でとりうる値を
色相 0~360
彩度 0~255 ( 無色 <- -> 原色 )
明度 0~255 ( 暗い <- -> 明るい )
			
とします。

それではRGB表現からHSV表現へはどのように変換したらよいのでしょうか。もう一度色相円.を見てみましょう。よく見ると赤緑青がちょうど円を三等分した位置に配置されていることがわかります。

さらに輝度値と合わせてよく観察すると

000度 (R,G,B)=(255,000,000)
060度 (R,G,B)=(255,255,000)
120度 (R,G,B)=(000,255,000)
180度 (R,G,B)=(000,255,255)
240度 (R,G,B)=(000,000,255)
300度 (R,G,B)=(255,000,255)
中心 (R,B,B)=(255,255,255)
			
となっています。この関係、実はベクトルを使うととてもうまく表現することができるのです。

では、ベクトルによってRGB表現とHSV色相系の関係を明らかにしてみましょう。色相環の中心を原点とするX-Y平面を考えます。また、円の半径は255とします。ここに赤緑青各色の輝度値を表す、原点から伸びる、輝度値を長さとしたベクトルを配置します。また話を単純にするため、まずはいずれかの輝度値が最大(つまり255)である場合を考えます。.

あとは三つのベクトルを足し合わせれば、六角形の色相円(円ではありませんが)上の色相、彩度を算出することができ、色相は足しあわされたベクトルの偏角、彩度は足しあわされたベクトルの大きさとなります。上の図は、各色の輝度値がそれぞれ

(R,G,B)=(255,YYY,ZZZ)
(R,G,B)=(XXX,255,ZZZ)
(R,G,B)=(XXX,YYY,255)
			
である場合の色相円を描画したものです。

六角形のままでは格好が悪いので、円になるよう引き伸ばせば、色相円の出来上がりです。

最後に明度ですが、これは各色の輝度値のうち最大のものであると定義します。例えば、.の大きい円は明度が255のもので、小さい円は明度が128のもの、すなわち

(R,G,B)=(128,YYY,ZZZ)
(R,G,B)=(XXX,128,ZZZ)
(R,G,B)=(XXX,YYY,128)
			
のものです。

ここで、円が小さいのは縮小したわけではなく、ベクトルの大きさが小さいからであることに注意してください。この場合、彩度の最大値が128となってしまうので、最大値が255になるよう補正します。

別の見方

私は美術的な分野(美術やCGなど)についてあまり詳しいわけではないのですが、この分野では色を考える際に色相環を基準とする傾向があるようですので、今回、私も色相環からRGB表現とHSV色相系の関係を説明しました。ですが、数学的に考えるともう少し合理的な説明ができます。

RGB表現で与えられる色の情報はRGBの三色、つまり三次元です。ですから、すべての色は立体、特にRGBの最大値は255なので、各辺の大きさが255である立方体の中の点として表すことができます。

このとき、RGBがすべて255である点から立方体を見る.と、.のようになります。結局、先ほどやった、平面上でベクトルによりあらわした関係は、立体上でベクトルによりあらわされた点を平面上に投影したものだったのです。

実際の計算

はじめに

実際の計算はベクトルと相性のよい複素平面で行うことにします。そうする理由は、複素数はベクトルのように扱うことができ、さらに C++ では STL クラスである complex とそれに関連する関数で容易に複素数の計算ができるためです(ただし、説明では C++ に関するものは使用していません)。

以下の説明では、複素数を「ベクトル」と言います。また、以下のように接頭辞「r_」はそれが実数であることを、接頭辞「i_」はそれが虚数であることを示し、接頭辞がないものはベクトル(複素数)であるものとします。

r_X 実数
i_X 虚数
			
さらに、表現を分かりやすくするため、いくつかの関数を定義しておきます。これらの関数はいずれも実数を返します。以下に関数の定義を示します。
real(C): C の実部
imag(C): C の虚部
cos(r_X): r_X のコサイン
sin(r_X): r_X のサイン
arg(C): C の偏角
abs(C): C の大きさ(長さ)
// 以上の関数は実際に C++ でも用意されています。
// ただし、引数にとりうる値や返される値の範囲に注意してください。
// ここでは角度は度数表示、偏角の範囲は 0度~360度 とします。

max(r_X, r_Y, r_Z): r_X, r_Y, r_Z のうち最も大きいもの
min(r_X, r_Y, r_Z): r_X, r_Y, r_Z のうち最も小さいもの
また、計算するに当たり、各色の単位ベクトルを定義しておきます。 
赤 RE = r_cos(000) + i_sin(000)
緑 GE = r_cos(120) + i_sin(120)
青 BE = r_cos(240) + i_sin(240)
			

RGB表現からHSV色相系への計算

与えられた各色の輝度値と求めるべき色相、彩度、明度をそれぞれ

赤 -> r_R
緑 -> r_G
青 -> r_B
色相 -> r_H
彩度 -> r_S
明度 -> r_V
			
とします。

最初に明度(r_V)を求めます。これは r_R,r_G,r_B のうちもっとも大きい値をとれば、それが明度となります。即ち

r_V = r_max(r_R,r_G,r_B)
			

次に色相を求めます。各色の単位ベクトル RE,GE,BE に輝度値をかけて大きさを持たせ、それらを足し合わせたベクトル C を求めると、その偏角が色相になります。即ち

C = r_R * RE + r_G * GE + r_B * BE
r_H = r_arg(C)
			

最後に彩度を求めます。彩度はベクトル C の大きさとしたいところですが、前述のとおり

1. 最大値が255になるように修正
2. 六角形が円になるように修正
			
とする必要があります。

1. については単純に比を考えればよいので、説明は省略します。

2. についても、.のように中心から円周方向に向かって比を考えて引き伸ばせばよいでしょう。

具体的には、0度~60度までを考えます。残りの角度については、0度~60度のところに当てはめて考えてください。

0度~60度の部分ですが、.のように30度回転して考えるとよいでしょう。

こうすれば、直角三角形を利用して実線部分の長さを求めることができます。ここで直角三角形の長さを r_S0 とすれば、r_S0 は

r_S0 = r_cos(h) / r_X
※ r_X は底辺の長さ(= ルート3 / 2)
			
となります。そして、対する円周までの長さは即ち半径ですから、
r_S0 * r_A = 255
			
となるような r_A をもとめて、ベクトル C の大きさにかけてやればそれが彩度(r_S)となります。即ち
r_S = r_A * abs(C)
			

HSV色相系からRGB表現への計算

与えられた色相、彩度、明度と求めるべき各色の輝度値をそれぞれ

色相 -> r_H
彩度 -> r_S
明度 -> r_V
赤 -> r_R
緑 -> r_G
青 -> r_B
			
とします。 まず、彩度(r_S)について
1. 最大値が255になるように修正
2. 六角形が円になるように修正
			
の逆の演算をやっておきます。ただし、以下の説明で r_S は逆演算が行われた値とします。

さて、色相 r_H、彩度 r_S であらわされるベクトル C は

C = r_S * r_cos(r_H) + r_S * i_sin(r_H)
			
とあらわされます。また、r_V を求めた時の逆を考えれば、最も輝度値が大きい色の輝度値は r_V になります。どの輝度値が最も大きいかは C が RE、GE、BE のうちどれに最も近いかで判断します。これは
r_HR = |r_H - r_arg(RE)|
r_HG = |r_H - r_arg(GE)|
r_HB = |r_H - r_arg(BE)|
			
とし、r_HR、r_HG、r_HB を比較すれば良いでしょう。以下、話を単純にするため最も輝度値が大きい色は赤であったものとします。つまり、
r_min(r_HR, r_HG, r_HB) = r_HR
			
であり、
r_R = r_V
			
です。さらに次のステップを考えるために C から赤色成分を取り除きます。
C' = C - r_R * RE
			
そしてこの C' は
C' = r_G * GE + r_B * BE
			
をあらわしています。さて、r_G と r_B 以外は既知の値なので、これにより連立方程式を組むことができます。
r_real(C') = r_G * r_real(GE) + r_B * r_real(BE)
r_imag(C') = r_G * r_imag(GE) + r_B * r_imag(BE)
			
この連立方程式を解けば、r_G と r_B を求めることができます。

参考

ここで紹介した方法以外にもRGB表現とHSV色相系を関連付ける式はあるようです。


ですが、こちらに紹介されている方式またはそれに準ずる方式については数学的な根拠が不明瞭でした(というか私の力不足で理解できませんでした)。

もっとも、私の書いたこのページもHSV色相系の標準的な定義 (*1) を調べたわけではないのでこれが絶対正しいとも言えません。所詮、人が定義したものですので。

テストコメントです。