web-dev-qa-db-ja.com

リニアRGBスペースとノンリニアRGBスペースで色を操作する場合の実際的な違いは何ですか?

線形RGB空間の基本的な特性とは何か、非線形RGB空間の基本的な特性は何ですか?これらの8(またはそれ以上)ビットの各チャネル内の値について話すとき、何が変わりますか?

OpenGLでは、色は3 + 1の値であり、これで各チャンネルに8ビットが予約されているRGB + alphaを意味し、これは明らかに得られる部分です。

しかし、ガンマ補正に関しては、非線形RGB空間での作業の効果がわかりません。

私は写真編集用のグラフィックソフトウェアで曲線を使用する方法を知っているので、私の説明は、線形RGB空間では、値がそのままで、操作も数学関数も接続せずに、それぞれが非線形の場合チャネルは通常、古典的なべき関数の動作に従って進化します。

この説明を実際の説明としても、計算後、すべての非線形RGBスペースが線形になり、最も重要なのは、非線形の部分が得られないためです。 -最終的にすべてのRGB空間は私が理解するものに対して線形であるため、線形色空間は人間の目により適しています。

66
Ken

RGBカラーを使用しているとしましょう。各カラーは3つの強度または輝度で表されます。 「リニアRGB」と「sRGB」から選択する必要があります。ここでは、3つの異なる強度を無視することで物事を単純化し、1つの強度だけを持っていると仮定します。つまり、グレーの陰影だけを扱っているということです。

線形色空間では、保存する数値とそれらが表す強度の関係は線形です。実際には、これは、数値を2倍にすると、強度(グレーの明るさ)が2倍になることを意味します。 2つの光源の寄与に基づいて強度を計算するため、または不透明なオブジェクトの上に透明なオブジェクトを追加するために、2つの強度を一緒に追加する場合は、一緒に2つの数字。 あらゆる種類の2Dブレンドまたは3Dシェーディング、またはほぼすべての画像処理を行う場合は、線形色空間で強度が必要ですので、加算、減算、乗算、数値を除算して、強度に同じ効果を与えます。ほとんどのカラー処理およびレンダリングアルゴリズムは、すべてに余分な重みを追加しない限り、線形RGBでのみ正しい結果を出します。

それは本当に簡単に聞こえますが、問題があります。光に対する人間の目の感度は、高強度よりも低強度でより細かくなります。つまり、区別できるすべての強度のリストを作成すると、明るい強度よりも暗い強度の方が多くなります。別の言い方をすれば、明るいグレーの濃淡よりも濃いグレーの濃淡を区別することができます。特に、強度を表すために8ビットを使用しており、線形カラースペースでこれを行う場合、明るい色合いが多すぎて、暗い色合いが不十分になります。暗い部分ではバンディングが発生しますが、明るい部分では、ユーザーが区別できない白に近いさまざまな色合いのビットを無駄にしています。

この問題を回避し、これらの8ビットを最大限に活用するために、sRGBを使用する傾向があります。 sRGB標準は、色を非線形にするために使用する曲線を示します。曲線は底部でより浅いので、より濃い灰色を持ち、上部でより急勾配になるため、薄い灰色が少なくなります。数値を2倍にすると、強度は2倍以上になります。これは、sRGBカラーを一緒に追加すると、本来よりも明るい結果になることを意味します。最近では、ほとんどのモニターは入力色をsRGBとして解釈します。つまり、画面に色を配置するとき、またはチャンネルあたり8ビットのテクスチャに色を保存するときは、sRGBとして保存するなので、それらの8ビットを最大限に活用します。

問題があることに気づくでしょう。色を線形空間で処理したいが、sRGBで保存したいのです。つまり、読み取り時にsRGBから線形への変換を行い、書き込み時に線形からsRGBへの変換を行うことになります。すでに述べたように、線形の8ビット強度には十分な暗さがないと、これは問題を引き起こすので、もう1つの実用的なルールがあります:8ビット線形カラーを使用しないでくださいそれを避けてください。 8ビットカラーは常にsRGBであるという規則に従うのが一般的になりつつあるため、sRGBから線形への変換を行うと同時に、強度を8ビットから16ビットに、または整数から浮動小数点に広げます。同様に、浮動小数点処理を終了すると、sRGBへの変換と同時に8ビットにナローになります。これらの規則に従えば、ガンマ補正を心配する必要はありません。

SRGBイメージを読み込んでいて、線形強度が必要な場合は、各強度に次の式を適用します。

float s = read_channel();
float linear;
if (s <= 0.04045) linear = s / 12.92;
else linear = pow((s + 0.055) / 1.055, 2.4);

逆に、画像をsRGBとして書き込む場合は、各線形強度に次の式を適用します。

float linear = do_processing();
float s;
if (linear <= 0.0031308) s = linear * 12.92;
else s = 1.055 * pow(linear, 1.0/2.4) - 0.055; ( Edited: The previous version is -0.55 )

どちらの場合も、浮動小数点の値の範囲は0から1です。したがって、8ビット整数を読み取る場合は最初に255で除算し、8ビット整数を書き込む場合は255で乗算します最後に、通常と同じ方法で。 sRGBを使用するために知っておく必要があるのはそれだけです。

これまで、私は1つの強度だけを扱ってきましたが、色に関してはもっと賢いことがあります。人間の目は、異なる色合いよりも異なる明るさを区別することができます(技術的には、クロミナンスよりも優れた輝度解像度を持っています)。色合いとは別に明るさを保存することで、24ビットをさらに活用できます。これが、YUV、YCrCbなどの表現が行うことです。 Yチャンネルは色の全体的な明るさであり、他の2つのチャンネルよりも多くのビットを使用します(または空間解像度が高くなります)。この方法では、RGB強度の場合のように(常に)曲線を適用する必要はありません。 YUVは線形の色空間であるため、Yチャンネルの数値を2倍にすると、色の明るさが2倍になりますが、RGB色のようにYUV色を加算または乗算することはできないため、画像処理、保存および送信専用。

これであなたの質問の答えになると思うので、簡単な歴史的メモで終わります。 sRGBの前は、古いCRTに非線形性が組み込まれていました。ピクセルの電圧を2倍にした場合、強度は2倍以上になります。モニターごとにどれだけ異なるか、このパラメーターはgammaと呼ばれていました。この動作は、ライトよりも暗い部分を取得できることを意味していましたが、最初に調整しない限り、ユーザーのCRTの色がどれだけ明るいかを判断できなかったため、役に立ちました。 ガンマ補正は、最初の色を変換し(おそらく線形)、ユーザーのCRTのガンマに変換することを意味します。 OpenGLはこの時代に由来するものであり、そのためsRGBの動作が少し混乱することがあります。しかし、GPUベンダーは、上記で説明した規則を使用する傾向があります。テクスチャまたはフレームバッファーに8ビットの強度を格納する場合はsRGBであり、色を処理する場合は線形です。たとえば、OpenGL ES 3.0では、各フレームバッファーとテクスチャに「sRGBフラグ」があり、読み取りおよび書き込み時の自動変換を有効にできます。 sRGB変換やガンマ補正を明示的に行う必要はまったくありません。

178
Dan Hulme

私は「人間の色検出の専門家」ではありませんが、YUV-> RGB変換についても同様のことに出会いました。 R/G/Bチャンネルには異なる重みがあるため、ソースカラーをxだけ変更すると、RGB値は異なる量に変化します。

とにかく、私は専門家ではありません、とにかく、色を正しく変換したい場合は、YUV空間でそれを行ってからRGBに変換する必要があります(またはRGBで数学的に同等の操作を行うことに注意してくださいデータ損失の)。また、YUVが色の最高のネイティブ表現であるかどうかはわかりませんが、ビデオカメラがその形式を提供しているので、問題に遭遇しました。

秘密の番号が含まれた魔法のYUV-> RGB式を次に示します。 http://www.fourcc.org/fccyvrgb.php

1
ern0