RGB←→HSB相互変換

誰かRGB←→HSBの変換式を教えてくれー!
と探してみたものの、なかなかどうして
ちゃんと解説されているサイトは見つからない……。

しかし、バラバラの理論を基に何とか私にも出来ましたよ、ええ(T_T)。
「何故だー(泣)!?」と何度叫んだか分かりませんっ!

これからお話しする内容には、
かなり私の独断と偏見が含まれている可能性がありますが、
まあ、とりあえずちゃんと動きます(笑)!

今回の理論創造にあたり、以下のサイト様を参考にしました。
勝手にリンクですが、感謝の意を込めてここに表記させて頂きました。

もしかして私の理論が間違ってても、こちら様に迷惑をかけないよーに(笑)。

osakana.factory (http://ofo.jp/)
HSB 値と輝度の求め方:http://ofo.jp/osakana/cgtips/hsb.phtml

TryNext Project(http://trynext.co.jp/)
HSBカラーモデルについて:http://trynext.co.jp/tips/hsb/index.html

HSBって何?

H は色相
S は彩度
B は明度


を表します。詳しくは各項目で説明します。
ウィンドウズの場合(マックは知らん)、RGBが全ての基本ですが、
色調変換したい場合、RGBは直感的ではありません。
HSBの方が人間に優しく、プログラマーに厳しい(泣)色再現方法なのです。

似たものに、明度( H )ではなく輝度( Y )を使った、
HSY(こんな名前はないかも)がありますが、これらは違う物です。
良く同じだと言われますが、少なくとも私は別々の計算式を使っています。
また、HSBの原型となったHSL( L は明度)と言うのもあります。
もちろんHSBとHSLは別物です。

ちなみに、私が公開しているプログラム『画像処理シリーズ《HSY》』はHSY変換を行っています。
ただし、RGBから直接変換しているわけじゃなく、YCrCbを経由して相互変換しています。
数式で表せばこちらの方が断然簡単です。
しかも、人間の眼には明度( B )より輝度( Y )の方が自然に見えるらしいとか。

ところで、実際にHSB、HSYをいじってみた感想は、どちらも一長一短だなと。
かなり似たような結果が得られるので、後は個人の好みの問題かも知れませんね。
また、補正と言うよりも変換と言った方が正しいかも?
というくらい、HSB、HSYという言葉のイメージとはかけ離れた結果が得られることもあります。
まあ、それはそれ。
ユーザーにちゃんと説明すれば、大丈夫です(たぶん分かってくれないだろうなあ……)。

以降はHSYのことは忘れて(^^;)、HSBのことについて解説します。

RGB→HSB変換

色相(Hue)

色相は簡単に言ってしまえば、色の種類を示す値です。
0〜360度の円になっていて、0度は赤です。
一周した360度も赤です。
言葉で説明するよりも下の図を見た方が分かり易いでしょう。

 ■000度(255,000,000)

 ■060度(255,255,000)

 ■120度(000,255,000)

 ■180度(000,255,255)

 ■240度(000,000,255)

 ■300度(255,000,255)


 
360度(255,000,000)


計算式

右上に列挙した値を見ると、どうやらRGB各成分の増減には一定の法則がありそうです。
この法則に注目して、計算式を導出しましょう。

まず、RGB各成分の中で最大値がGとなっている時を考えます。
それは、60〜180度です。
その中で、最小値がBとなっているのは、60〜120度です。
また、最小値がRとなっているのは、120〜180度です。
残りの値が1度毎に増減する値はいくらでしょうか?
255/60度?違います。
最大値でも最小値でもない値が増減するんですから、
最初と最後の値は最大値か最小値かのいずれかです。
従って、1度毎に増減する値は
(最大値-最小値)/60です。
60〜120度の場合はR成分だけが減っていきます。
したがって、計算式は

最大値−(最大値−最小値)/60×H’=R

を計算すれば60度からの増分角度を求められます。
つまり、最終的にはこの式を変形して60度を足します。

H=(最大値−R)/(最大値−最小値)×60+60

それでは、120〜180度の時はどうでしょう?
B成分だけがだんだん増えてますね。
まず、120度からの増分を求める式は

最小値+(最大値−最小値)/60×H’=B

この式を変形して、120度を足せば

H=(B-最小値)/(最大値-最小値)×60+120

他も同じ考えで計算式を導出できます。

さて、120度の時はRもBも最小値となり、二つの範囲の条件を満たしてしまっています。
どちらで計算すべきでしょうか?
実はどちらでも同じになります。
実際(0,255,0)を上の二式に代入するとどちらも120度となります。
この場合、最大値は255で、最小値は0ですね。
したがって、最大値、最小値を求めるプログラムで同じ値が出た場合、
どっちを最大値と見なすか、といったことを考える必要はありません。

ただし、一つだけ注意しなければいけないのは、
360度(0度)は300〜360度の範囲で計算するのではなく、
0〜60 度の範囲で計算しましょう。
つまり、360度は0度と見なしたいわけです。
360度を0度に修正するのは面倒ですしね。
プログラムでは条件式の順番を工夫すれば良いだけです。

おっと、プログラム的な注意がまだありましたね。
それは、最大値−最小値が 0 になった場合です。
0 で割るとプログラムは死んでしまいます。
最大値−最小値が 0 ということは、全てのRGB成分が同じ、
つまりグレースケールであることになります。
この場合、色相と言われても困るんですが、とりあえず色相 0 にしておけば問題ありません。
別に他の値でも良いですが。
ちなみに、色相 0 は赤の色相でもある事を忘れないで下さい。

補足ですが、範囲は0〜360度にこだわる必要はありません。
0〜1でも、0〜100でもプログラム的には問題ありません。

彩度(Saturation)

値が大きくなるほど鮮やかになります。
って言ってもよく分からないでしょうが、0(最小値)ならグレースケールです。
色相の円は一次元ですが(二次元っぽく描いてますが)、
これに円の中心からの距離(彩度)を加えると二次元の本当の円になります。
円の中心(最小値)はグレースケール、円周部(最大値)は純色になります。

計算式

この計算式は、もうこういう物だって事で(^^;)。

S=(最大値-最小値)/最大値×255

範囲を 0〜255 にしたいので255を掛けました。
別に 0〜1 でも、0〜100 でも構いません。

ここでもプログラム的な例外処理が必要です。
最大値が 0 になった場合ですね。
つまりRGB成分全てが 0 と言うことになり、
要するに黒の彩度は?と言う問題になります。
黒もグレースケールの一種ですから、この場合は彩度 0 にしておきましょう。
これまた別に他の値でも良いですが。

また、この計算式よりグレースケールの時の彩度は 0 であることも確認できます。

明度(Brightness)

彩度との違いが分かりにくいですが、
値が大きくなればだんだん明るくなって、
最大値なら非常に明るくなり(白にはなりません!)、最小値(0)なら黒になります。
これは色相の円の中心に垂直に交わる直線に相当します。
従って、HSB三つの成分を合わせると三次元の円柱になりましたね。

計算式

これは超簡単で、RGB各成分中の最大値です。

B=最大値

今、範囲は0〜255としましたが、
範囲を替えたい場合は適当な値を掛けるなり割るなりして下さい。
やっぱり0〜255以外だと、0〜1、0〜100が一般的ですが。

明度Bを最大にしても白にはならないというのはイメージと違うでしょうが、
HSBの原型になったHSLの明度Lを最大にした場合は白になるみたいです。

HSB→RGB変換

問題は逆変換です。

まず、直ぐに分かる情報は何でしょうか?
それは、(HSBの)Bの値がRGB各成分中の最大値になっていると言うことです。
また最大値が分かれば、Sの計算式より最小値も求められます。
問題はこれらをどう使うかです。
あと残っている情報はHだけですが、
Hの値より60度毎のどの範囲に属するのかが分かります。
例えば、100度ならば、60〜120度の範囲に属し、
最大値はG成分で、最小値はB成分であることが分かります。

060度(255,255,000)
120度(000,255,000)


あと必要なのはR成分の値です。
ところで、60〜120度の範囲のHを求める際に以下の計算式を使いました。

H=(最大値−R)/(最大値−最小値)×60+60

この式の中で分からないのはRの値だけです。
したがって、この式を変形すればRの値も求められます。

R=最大値−(H−60)×(最大値-最小値)/60
G=最大値
B=最小値


同様に150度の場合は、120〜180度の範囲に属し、
最大値はG、最小値はRで、計算しなければならないのはBであることが分かります。

120度(000,255,000)
180度(000,255,255)

120〜180度の時、Hを求める式は以下のようになっていました。

H=(B-最小値)/(最大値-最小値)×60+120

この式を変形すれば

R=最小値
G=最大値
B=最小値+(H−120)×(最大値-最小値)/60


他も考え方は同じです。

さて、またまた120度の場合はどっちで計算するんじゃー!という問題がありますが、
やっぱりどっちでも同じになります。
試しに(0,255,0)を上の二式に代入してみて下さい。
最大値は255で、最小値は0なので、求めたい値(R or B)は0になります。

ここを解説されているサイトがなく、何度挫折しそうになったか分かりません……(T_T)。

HSB各成分の値をいじる

まず、この処理を実装する前にRGB→HSB→RGBがちゃんと動作するか確かめて下さい。
見た目が変わらなければ成功です。
失敗した場合、一番考えられるのは小数の誤差です。
とりあえず、H、S、Bの値を格納する変数は double 型にしましょう。
int 型では誤差が大きすぎます。

それでは、HSBの値をそれぞれ独立に増減してみましょう。
他の値に影響を与えないところがシンプルで扱いやすく、良いところなのですが、
逆にイメージとは違う結果が得られたりして困ったところでもあります。

まあそれは後で説明するとして、
Photoshopなどではどのようになっているでしょうか?
色相は±180度、彩度は±100%、明度は±100%の範囲で調節できますね。
色相は良いとして、彩度と明度の%って何よ?ということになります。
私も知りたいです(泣)!
解析できなかった理由は単純明快。
HSBじゃないからです(爆)!
Photoshopは HAI ですし、PaintShopProは HSL です。
例えば、彩度しかいじってないのに明度も勝手に変わってたりするのは
その色再現方式の仕様なんでしょう。
こっちの方がイメージと合致した結果が得られますけどね。
もちろん HSB は彩度をいじったからといって、明度が勝手に変わったりすることはありません。
理論的には自然ですが、処理結果はイメージと合致しなかったりするわけで……うーむ。

えーかなり脱線しましたが、謎のパラメータ%は
現在の値と最大値、または最小値の値との差を表していると、私は考えることにしました(爆)!
例えば現在の値が100(%じゃない)ならば、+50%ならば
100+(255−100)×0.5=177.5
−50%ならば
100−(100-0)×0.5=50
ということになります。
+か−かで1%毎の増減値が違う点に注意して下さい。
もちろん+100%ならば255、
−100%ならば0になります。
ここで、最大値は255、最長値は0と仮定しています。
色相にも言えることですが、画像全体に対して処理を施すので
どれか特定のピクセルに注目するわけにはいかないんですね。
例えば、+200(%じゃない)として、ある値が255を越えてしまっては困るのです。
画像全体の見た目のバランスが崩れる気がしませんか?
例えば、明度を最小値に限りなく近づけた場合、処理前に黒以外だった色は、
処理後は黒ではなく、黒に限りなく近い色になっているはずです。
したがって、各ピクセルに対して相対値で指定する方がより自然な結果が得られるのです。

プログラム的には
Hは360度を超した場合、0未満になった場合は0〜360度の範囲に修正します。
SとBは前述の方法でプラスに変化する場合とマイナスに変化する場合を分けて書きます。

HSB各成分と人間の感性との相違

とりあえず、今までの内容をプログラムすれば純粋なRGB←→HSB相互変換の完成ですが、
純粋なHSBをいじると、人間の感性とは違う結果が得られることがあります。

まず、グレースケールの彩度を上げた場合、
カラーになるのかという問題がありますが、感覚的な答えはノーです。
しかし、純粋なHSBではイエスです。

以下は実際のプログラムですが、
例えばグレースケール画像に対してS=255としてみましょう。
BとHは何でも良いんですが、
とりあえず、B=128、H=0としておきます。
すると max=128 より min=128−255*128/255=0 となり、
R=128、G=0、B=0が得られます。
これはグレースケールではありません。

max=B[x+y*iWidth];

min=max-S[x+y*iWidth]*max/255;

hue=H[x+y*iWidth];

if(hue<60){
 r=max;
 g=min+hue*(max-min)/60;
 b=min;
}


RGB→HSB変換の際にグレースケールの時は H=0 としましたから、
グレースケールの彩度を上げると必ず赤っぽい画像が得られることになります。
また、彩度と色相を同時に変更すれば、赤でない疑似カラーが得られます。
しかし、これはこれでHSBの性質としては正しい反応です。
だがしかし、感覚的に納得できる結果ではありません。
そこで、グレースケールの彩度を上げてもグレースケールが保てるように
例外処理を追加してみましょう。
解決策は意外と簡単です。
ちょっと反則ですが、もともとグレースケール画像(彩度0)ならば、
処理結果の彩度も必ず 0 になるようにしてしまえば良いだけです。
上の値を計算し直せば、RGB成分全てが 128 になるのが分かります。
ところで、黒の場合は彩度0にしましたが、これについても少し考えてみましょう。
黒の場合の明度Bは0、つまり、max=0 です。
この値を基に計算すると min=0 となり、
RGB成分全てが 0 となります。
したがって、黒の場合の彩度は何でも良かったんですね。

次に、もう一つ人間の感性と異なる処理結果が得られる場合があります。
明度を上げた場合です。
明度を下げた場合は大丈夫なんです。
取り敢えず見づらくなってきたのでプログラムを再掲します。

max=B[x+y*iWidth];

min=max-S[x+y*iWidth]*max/255;

hue=H[x+y*iWidth];

if(hue<60){
 r=max;
 g=min+hue*(max-min)/60;
 b=min;
}


ここで、例えば明度B=0(最小値)にした場合、
上の式よりmax=0となり、min=0が導かれます。
したがって、RGB成分全てが0、つまり黒となり人間の感性に合います。
また、明度Bを下げることにより、maxの値は小さくなり、
それにつられてminの値も小さくなり、
結果的にRGB各成分の値も小さくなる、つまり色は暗くなることが分かります。
問題は明度を上げた場合です。
例えば、明度B=255(最大値)とした場合を考えてみましょう。
もちろん結果はRGB成分全て255、つまり白となって欲しいわけでが、計算結果はどうでしょう?
max=255 が取り敢えず得られます。
次に min の値ですが、Sは 0 とは限りません。
ここで、Sは0じゃない場合 min=255−S となり、min<maxとなります。
したがって、RGB成分全てが 255 、つまり白になることは不可能となってしまいました。
上のプログラムの場合、Rは255になりますが、GとBが255になりませんね(Hの値によってはG=0)。
しかし、これもまたHSBの性質的には正しい反応です。
と言っても、やっぱりこれじゃー使いづらいだろうと思いませんか?
出来ればこれも直したいところですが、良いアルゴリズムが思いつきませんでした(汗)。
素直に別の色再現方式を使った方が良さそうですね。

最後に

人間の感性とは違う結果が得られることをユーザーになんて説明しましょう?
そもそも、グレースケールの彩度を上げるなんて事をするでしょうか?
まあ、そんな理不尽な要求をしてくるユーザーにはありのままを受け入れて貰いましょう(笑)。
これはこれで使い道がありますしね。
ただし、画像はカラーなのに、たまたまグレースケールになっているピクセルまでもが
疑似カラー化してしまうのは本当に困った問題なのですが……。
って、そんなことより、ユーザー的に納得がいかないのは
明度を最高まで上げても白にならないことの方でしょう!
これは、明度を最高にするのと同時に彩度を最低にすれば真っ白な処理結果が得られます。
あーつまり結論を言ってしまえば、自分で色々実験して確かめてくれと(^^;)。

さて、ここで述べたプログラムはこちらです。
グレースケールの彩度を上げた場合の例外処理は実装していますが、
コメントアウトして無効化しています。
また、このプログラムを用いたアプリケーションも公開しています。
そちらに実行ファイルも含めた全てのプログラムがありますので、
併せてダウンロードして下さい。


戻る / ホーム