Edited at

グレースケール画像のうんちく

グレースケール変換の前提知識から具体的な変換アルゴリズムまで解説します。

カラー画像を元に白黒印刷したいときや、最近だと機械学習の物体認識で輝度だけ使いたい等、グレースケール化を実装する機会は意外とよくあります。

大抵はグラフィックエンジンのグレースケール化APIを使うだけですが、具体的にどういう処理をして、どのような選択肢があるのか、知っておいて損はないです。


グレースケールとは

画像を色味のない明るさの度合いだけで表現するのがグレースケールです。

似た言葉にモノクロ画像がありますが、こちらは下のカラー以外3つを含む広い概念です。白と黒のどちらかだけで中間のグレーがないバイナリ画像を指す事もありますし、セピア調のように色味をつけたモノトーンかもしれません。

カラー画像
グレースケール画像
バイナリ画像
セピア調モノクロ画像

image.png
image.png
image.png
image.png

image.png
image.png
image.png
image.png

カラー画像は1つのピクセルの色を表すのに複数の値が必要で、R,G,Bの3つの値を用いる事が多いのですが、グレースケール画像だと1つのピクセルに1つの値で十分です。

その1つの値に、モニタだと Y(輝度、CIE XYZ の Y軸)、L(Luminance)、印刷では K(Key 又は blacK)、より抽象的には V(Value、色価)、Intensity(強度) といった文字や用語がよく用いられます。

colourful pixel
grayscale pixel

4x4color.png
4x4grayscale.png

(OpenCV の imshow 関数を使った表示)

つまりグレースケール変換は、色成分RGB(又はCMYK等)が持つその複数の値から人の眼が感じる色の強さを推定して1つの値にまとめる作業と言えます。


前提知識


R,G,B は同じ値でも明るさが違う

R,G,B は3つ同じ値で加色混合すると無彩色(黒〜グレー〜白)になるよう輝度がバランス調整されています。

一番左は R=G=B=255、真ん中は R=G=B=187 で揃えてますが、大抵の人にはG(緑)が明るく、R(赤)は暗く、B(青)はとても暗く見えます。

一番右のように、(R,G,Bの値でなく)見た目の明るさを揃えて混ぜると青紫方向に色味がかります。

R=G=B=255
R=G=B=187(sRGBで輝度半分相当)
R:165,G:121,B:255(明るさを揃える)

image.png
image.png
image.png

色成分R,G,B毎の明るさ(輝度)の違いを考慮した良いグレースケール変換と、考慮しない悪いグレースケール変換を並べてみます。

カラー画像
良いグレースケール (CIE XYZ)
悪いグレースケール (RGB平均)

image.png
image.png
image.png

この悪い方でも我慢できる事はあるので、性能優先で悪いグレースケールを使う事も多々あります。


ガンマ補正で平均が歪む

一般的な画像ファイルから取り出せる RGB 値はピクセルの物理的な輝度と素直に比例しません。昔のブラウン管テレビが入力信号の電圧に対して出力する輝度が比例しなかったので、信号側で補正する事にしたそうです。あと、低輝度に敏感な視覚特性に都合がよい事も勘案したと思われます。

image.png

(c) https://ja.wikipedia.org/wiki/ガンマ値

このグラフで言うと、入力信号に対して CRT gamma 曲線のように輝度が下に沈み込む特性を想定して、RGB値の方を gamma correction の点線のように釣り上げる補正をします。すると、CRT では何も補正せずとも輝度リニアに表示するという理屈です。

このガンマ補正された R,G,B の値のまま平均を計算すると、本物の輝度の平均にはなりません。

以下の図は 1(最高輝度)と0(最低輝度)の真ん中の輝度を計算すると問題になる例です。

image.png

このように、ガンマ補正を考慮せずにR,G,Bの輝度値の平均をとると、期待する輝度よりも暗くなります。

カラー画像
嘘 CIE XYZ(ガンマ処理無し)
CIE XYZ(ガンマを考慮した版)

image.png
image.png
image.png

この真中の図のように特に彩度が高い色に影響が現れます。以下のようにガンマ値解除の手続きをとる事で、右図のような結果に出来ます。


  • R,G,B値にかかっているガンマ補正を外して、物理的な輝度と比例の関係にする (linear-RGB 化)

  • やりたい輝度計算をする。(グレースケールだとR,G,Bを混ぜる計算)

  • その後またガンマ補正をかけ直して元に戻す

ちなみに、グレースケール以外でも回転やリサイズ等の座標変換で補間処理がある時にも同じ事が言えます。


RGB からグレースケールへ変換

R,G,B 3つの値をどうやって 1つの値にまとめるのか。幾つか代表的な方法を紹介します。


RGB平均

V = (R + G + B) / 3.0

処理は単純ですが、前提知識で述べた2つの問題があって、実感と合わない結果になりがちです。

元画像
平均

image.png
image.png

右下の緑とか論外ですね。^^;


RGB平均 (ガンマ補正)

ガンマ補正を考慮した変換をしてみます。

(R',G',B') = 逆ガンマ補正(R,G,B)

V' = (R' + G' + B') / 3.0
V = ガンマ補正(V')

元画像
逆ガンマ補正
グレースケール変換
ガンマ補正

image.png
image.png
image.png
image.png

これで大分よくなりますが、それでも色々と食い違っています。特に左下が明るすぎます。


CIE XYZ の Y

CIE XYZ は人の眼の視覚特性を等色実験で調べて CIE(国際照明委員会)がまとめた規格で、RGB とは別の軸を持つ色空間です。

XYZ 軸の Y 軸が輝度(明るさ)で、XとZを色味に対応します。

詳しくはこちらを参考にどうぞ

この CIE XYZ を使った方法だと、ガンマ補正を考慮した上、更に R,G,B を足す際に重み付けをする(加重平均)事で、前提知識にある問題を2つとも解決します。

(R',G',B') = 逆ガンマ補正(R,G,B)

V' = 0.2126*R' + 0.7152*G' + 0.0722*B'
V = ガンマ補正(V')

元画像
逆ガンマ補正
グレースケール変換
ガンマ補正

image.png
image.png
image.png
image.png

人の眼の色の捉え方には個人差がありますが、おそらく多くの人はこのグレースケールに納得できるでしょう。

また、カラーマネジメントの観点でも sRGB 以外の P3 や AdobeRGB 、ProPhoto といった色域やガンマ値が異なる色空間でも XYZ への変換は ICC プロファイルで定義されているので、適切な CMM(カラーマネジメントモジュール) を使えば輝度は楽に取得できます。

デジタル放送の BT.709 も同じ加重平均ですし、sRGB (Webで流通している画像の殆どがこれ)も同じなので、これが決定版だ! …と言いたいところですが、そうとも言い切れないので記事の最後で少し解説しておきます。


ITU-R Rec BT.601

V = 0.299*R + 0.587*G + 0.114*B

CIE XYZ と並んでメジャーな計算式を持つ BT.601 は SDTV の規格です。

ガンマ補正がかかったまま R,G,B を計算して大丈夫なのか不安になりますが、現実的な都合でそうなっているようです。

あと JPEG の YCbCr の Y に用いられる式としても有名です。

ガンマ補正をかけた場合と両方の結果を並べます。

元画像
BT.601(ガンマ補正のまま)
BT.601(ガンマ補正解除して計算)

image.png
image.png
image.png


標準テレビジョン放送(デジタル放送を除く。)に関する送信の標準方式

V = 0.30*R + 0.59G + 0.11*B

日本のアナログ放送の規格では、BT.601 より少し精度を落として定義されています。

元画像
日本のアナログ放送

image.png
image.png


BT.601 係数を更に粗くする

更に係数の精度を落とす処理もたまに見かけます。

V = 0.3*R + 0.6*G + 0.1*B

元画像
BT.601(ガンマ補正のまま)
粗い方

image.png
image.png
image.png

精度についてはこんな感じで一見して違いが分からなかったりするので良いのですが、

ガンマ補正を解かず処理すると、彩度の高い色ほど暗くなり、やはり良くない結果が出ます。


ITU-R Rec BT.709

V = 0.2126*R + 0.7152*G + 0.0722*B

BT.709 は HDTVの 規格です。これは CIE XYZ と同じ処理です。また、sRGB も BT.709 相当です。

元画像
BT.709(ガンマ補正のまま)
BT.709

image.png
image.png
image.png


標準テレビジョン放送等のうちデジタル放送に関する送信の標準方式

日本のデジタル放送の規格は BT.709 と同じ係数の計算式で精度も同じなので説明を省きます。


YCgCo の Y

V = R/4 + G/2 + B/4 

YCgCo 色空間で使われる輝度(Y)の計算式です。

単純な計算ながらも G を強めに反映する事で、RGB平均よりは良い結果が期待できます。

ビット演算が使えるのも強みです。

V = (R>>2) + (G>>1) + (B>>2) 

元画像
YCgCo

image.png
image.png

YCgCo は動画圧縮の都合が優先で、人の視覚特性にあまり合っていないので、グレースケール化に使うことはあまりないですが、ビット演算で多少速度が稼げるのと計算誤差が把握しやすいメリットに対して一考の余地があります。


HSV の V (RGB の最大)

V = max(R,G,B)

R,G,B の一番明るい輝度を使います。列挙した中では一番外れの方式かもしれません。

ただ、例えばイラスト画像等で、色の使い方が独特な場合に、これが都合の良い時もあります。


  • 実例)


元画像
HSV

image.png
image.png


HSL の L (RGB の最大と最小の中間)

V = (max(R,G,B) + min(R,G,B)) / 2

こちらも HSV 同様、特殊な状況でたまに使う事があります。

元画像
HSL

image.png
image.png


Gチャンネルonly

V = G

どうせ R,G,B のうち G の輝度が圧倒的(6割)なので、G だけで良いのでは?という方法です。

元画像
G

image.png
image.png

image.png
image.png

The・雑という感じですが。画像によってはあまり違和感がなかったりします。

あと、人の目は暗がりだと緑に近い色がよく見えるので、夜間をイメージする画像としても良いかもしれません。(プルキンエ現象)

参考) https://qiita.com/yoya/items/96c36b069e74398796f3#comment-55b8dd0c13f313b8d708


Bチャンネルonly (特殊用途)

V = B

先ほどは G チャネルで。今度はB チャネルです。

見ての通り、一般的なグレースケール変換としては全然駄目です。

元画像
B

image.png
image.png

image.png
image.png

ただモニタのテストといった使い道もあるようです。

情報感謝! > @toru_ver15


中央値 (実験)

R,G,B の中央値を使う方法です。これはただの思いつきの実験です。実際に使う事はないでしょう。

V = [R,G,B].sort()[1] // Median

元画像
Median

image.png
image.png

image.png
image.png

駄目だこりゃ。。(;’ε‘)


高度なグレースケール変換


Contrast Preserving Decolorization

こちらの論文で提案された、コントラストをなるべく維持する手法です。

今までの方法は全て、R G B を決まった係数で混ぜますが、それだと都合の悪い事があります。

元画像
BT.601 係数で足すだけ
BT.601 係数+ガンマ処理

mandrill.jpg
gray.png
gray_gamma.png

BT.601係数処理+ガンマ処理の方が色の明るさ的には正しいのに、結果としてコントラスト(明暗の差)による境界がわかりにくく、BT.601係数で足すだけの方が画像の印象が良い。といったケースもありえます。

Contrast Preserving Decolorization の手法は、各色の正確な明るさよりもコントラスト維持を優先します。

OpenCV は以下のコードで、この Decolor アルゴリズムを利用出来ます。

import cv2

_, infile, outfile = sys.argv
img = cv2.imread(infile)
img_gray, _ = cv2.decolor(img);
cv2.imwrite(outfile, img_gray)

元画像
decolor

mandrill.jpg
decolor.png

それらしい都合の良いグレイスケール画像が生成されます。


Salience-Preserving Color Removal

特徴が残るように色を消す手法だそうです。

ある意味 Contrast Preserving Decolorization と似ていて、色の本来の明るさをある程度犠牲にして、色による差をなるべく情報として残す手法です。

SIGGRAPH2005 での発表スライドから図を抜粋すると、

既存の方法だと、3次元の色空間を決まった軸にマッピングするに過ぎません。

(page 8) Simple Linear Mapping

image.png

PCA で軸を探してそこにマッピングすると一見良さそうですが、

(page 9) Principal Component Analysis (PCA)

image.png

同じ輝度で色相環上に配置するケースが苦手なので、

(page 10)
Problem with PCA

Worst case:
Isoluminant Colorwheel
image.png

image.png

(pace 11) Non-linear mapping

image.png

一本の直線の軸にまとめるのこだわるのを辞めた。といった具合です。

Illuminance distance (輝度差) と Chrominance distance (色差)の両方がなるべく反映されるよう処理します。

興味のある方は論文をどうぞ。(57ページあります)


その他


ガンマ変換の注意

ポイントだけ並べます


NTSC 係数 ?

BT.601 係数についてネットで検索すると NTSC coefficient(係数)の呼び方で、

Y = 0.298912*R + 0.586611*G + 0.114478*B

この式がよく出てくるのですが、日本語の記事ばかりな上にいずれも根拠が無いので紹介しませんでした。

そもそも合計が 1.000001 で輝度100% をオーバーシュートしてる。。

尚、ImageMagick ではまた違った係数を使っています。海外の記事だとこちらの係数をよく見かけます。

* MagickCore/enhance.c (GrayscaleImage)

intensity=0.298839*red+0.586811*green+0.114350*blue;

標準光源 C での NTSC RGB Primary xyY の Y の値のようです。

参考までに2エントリ抜粋。

Name
Gamma
Reference White
Red Primary (x)
(y)
(Y)
Green Primary (x)
(y)
(Y)
Blue Primary (x)
(y)
(Y)
Volume (ΔE3)
Lab Gamut Efficiency %
Coding Efficiency %

NTSC RGB
2.2
C
0.6700
0.3300
0.298839
0.2100
0.7100
0.586811
0.1400
0.0800
0.114350
1,300,252
54.2
99.9

sRGB
≈2.2
D65
0.6400
0.3300
0.212656
0.3000
0.6000
0.715158
0.1500
0.0600
0.072186
832,870
35.0
100.0

これはこれで出典元を確認しておきたいです。恐らく NTSC の仕様から算出できる値だとは思いますが。


蛇足


GSDF (Grayscale Standard Display Function)

グレースケール化でなく、グレースケールをどうモニタに表示するかの話で、この記事のスコープ外ですが、折角なのでうんちく。

レントゲン写真等、医療用途のグレースケールは多用されますが、通常のモニタ表示ではモニタごとに表示が違う、そもそもアナログ写真と見た目が違う問題があり、補正して特定のトーンカーブに合わせる規格があります。

詳しくはこちらを参照して下さい。


最後に

はじめに思いつく RGB 単純平均は悪手で、CIE XYZ が良い方法。という結論で大体合ってますが、実際にその辺のモニタで試すと BT.601 のガンマ補正解除版も良かったりするので、自分は BT.601 を使う事もあります。

幾つか心当たりがあります。


  • 一般にRGB規格は標準光源 C 、D65 が基本だけど、一般の人のモニタは色温度を高めに設定するので、赤が暗くなりがちで BT.601 で赤の比重が高いので相殺分があるかも

  • XYZ といえば CIE1932 だけど被験者は17人でかつ人種の偏り(瞳の色が違うし住環境による文化の違いとか)もあるかもしれず、絶対視して良いのか疑問もある

赤を頑張れば、緑が犠牲になるといったトレードオフもあるので、結局、サンプルを作って実際に評価するのが良いでしょう。

あと、こちらに画像ファイルを放り込むと、列挙した方式を全部まとめて変換します。参考にして下さい。(注: 高度な方法は含まれません)

自分の眼で確認すると色々と分かる事もあるかもしれません。なお、linear チェックボックスを on にすると、ガンマ補正を考慮して変換します。

スクリーンショット 2019-06-20 1.43.05.png

スクリーンショット 2019-06-20 1.41.50.png

以上です。