web-dev-qa-db-ja.com

大まかなグレースケールアルゴリズムの使用に関する問題

そこで、pythonを使用してPILの写真を編集するためのいくつかのプログラムを設計し、そのうちの1つは画像をグレースケールに変換していました(PILの関数の使用は避けています) 。

私が採用したアルゴリズムは単純です。各ピクセル(色深度は24)について、RGおよびBの値の平均を計算し、RGBを設定しましたこの平均値。

私のプログラムは正確に見えるグレースケール画像を生成していましたが、正しいアルゴリズムを採用したかどうか疑問に思っていました、そして、私は この答え 「正しい」アルゴリズムは0.299 R + 0.587 G + 0.114 Bを計算します。

私のプログラムをこのアルゴリズムと比較することにしました。 ウェブサイトオンライン'image to grayscale'の上位のGoogle結果)からプログラムと別の画像(同じ入力を使用)を使用してグレースケール画像を生成しました。

私の肉眼では、それらはまったく同じであるように見えたので、バリエーションがある場合、私はそれを見ることができませんでした。ただし、グレースケール画像を比較するために このWebサイト'compare two images online'のGoogleの上位の結果)を使用することにしました。ピクセルの奥深くにわずかな変化がありましたが、ひと目では人間の目には知覚できなかったことがわかりました(違いは見分けられますが、通常は画像が互いに重なり合っているか、ミリ秒以内に切り替えられている場合のみ) 。

私の質問(最初の質問は主な質問です)

  1. 「大まかな」グレースケールアルゴリズムを使用することに不利な点はありますか?
  2. グレースケールアルゴリズムが「正しい」ものとは視覚的に異なる画像を生成する入力画像はありますか?
  3. 私のアルゴリズムでも機能しない色/ RBGの組み合わせはありますか?

私の重要なコード(必要な場合):

def greyScale(pixelTuple):
    return Tuple([round(sum(pixelTuple) / 3)] * 3)

「正しい」アルゴリズム(緑の重みが大きいようです):

def greyScale(pixelTuple):
    return Tuple([round(0.299 * pixelTuple[0] + 0.587 * pixelTuple[1] + 0.114 * pixelTuple[2])] * 3)

私の入力画像: My input image

私のアルゴリズムが生成するグレースケール画像: The greyscale image my algorithm produces

「正しい」グレースケール画像: The greyscale image which is 'correct'

グレースケール画像をオンラインで比較する場合(強調表示された赤は、10%のファズを使用した違いです): When the greyscale images are compared online (highlighted red are the differences, using a fuzz of 10%)

上記のハイライトされたピクセルのバリエーションにもかかわらず、上記のグレースケール画像は(少なくとも、私には)まったく同じように見えます。

また、私の最初の質問に関して、誰かが興味を持っている場合、 このサイト はグレースケールへの変換のためのさまざまなアルゴリズムでいくつかの分析を行い、いくつかのカスタムアルゴリズムも持っています。

編集

@Szulatの答えに応じて、私のアルゴリズム実際にが代わりにこの画像を生成します(悪いトリミングを無視して、元の画像には3つの円がありましたが、最初の円だけが必要でした1):

This is what my algorithm **actually** produces

アルゴリズムが目的に依存しているように思われるため、人々がグレースケールに変換する理由が何か疑問に思っている場合、私はpythonでいくつかの簡単な写真編集ツールを作成しています。 Photoshop。フィルターやエフェクトを適用するためにインターネットに依存する必要はありません。

バウンティの理由:ここでのさまざまな答えは、すべて関連性があり有用なさまざまなことを扱っています。これにより、受け入れる回答を選択するのが非常に難しくなります。ここにリストされているいくつかの答えが好きであるだけでなく、この質問に必要なすべてをカバーする単一の答えがあるといいので、私は賞金を始めました。

60
Adi219

画像はかなり似ていますが、特に一方を他方の代わりに置いた場合、あなたの目は違いを知ることができます:

enter image description here

たとえば、平均化変換では、背景の花がより明るく見えることに注意してください。

3つのチャネルの平均化に本質的に「悪い」ものがあるわけではありません。この式の理由は、赤、緑、青を等しく知覚しないため、グレースケール画像の強度への寄与が同じではないためです。緑をより強く知覚するため、緑のピクセルはグレースケールでより明るく見えるはずです。ただし、 マークによるコメント グレースケールへのユニークな完全な変換はありません。色で見るため、また、いずれの場合も、すべての人の視界はわずかに異なるため、どの式もピクセルを近似しようとします強度はほとんどの人にとって「正しい」と感じます。

46
jdehesa

最も明白な例:

  1. 元の

  2. Gimpで非飽和(明度モード-これがアルゴリズムの機能です)

  3. Gimpで彩度を下げます(発光モード-これが私たちの目です)

gimp desaturate: lightness vs luminosity

したがって、RGBを平均しないでください。 RGBの平均化は単純に間違っています!

(わかりました、RGB値が色として扱われる場合、物理的または生理学的な意味はありませんが、平均化はいくつかの不明瞭なアプリケーションで有効かもしれません。 "ガンマのために、加重平均の方法もより微妙な方法で不正確です。sRGBを最初に線形化し、次に最終結果をsRGBに変換する必要があります(Lab色空間でLコンポーネントを取得するのと同じです))

42
szulat

任意の変換式、スケール、線形性を使用できます。あなたが見つけたもの:

I = 0.299 R + 0.587 G + 0.114 B

人間の平均的な「平均」原色(R、G、B)知覚感度に基づいています(少なくとも期間と人口/ HWについては、これらの標準がLED、TFTなどの前に作成されたことに留意してください)。画面)。

あなたが戦っているいくつかの問題があります:

  1. 私たちの目は同じではありません

    すべての人間が同じように色を知覚するわけではありません。性別の間には大きな差異があり、地域間でも小さい。世代と年齢も役割を果たします。したがって、平均であっても「平均」として扱う必要があります。

    可視スペクトル全体の光の強度に対する感度は異なります。最も敏感な色は緑です(したがって、最も高い重みが付きます)。しかし、 XYZカーブ ピークは異なる人々に対して異なる波長になります(私のように、私はそれらを少しシフトさせて、Aquaのシェードのように特定の波長の認識に違いを生じさせました-一部は緑と青のように見えます)それらのどれも色覚異常の障害または何でも持っていなくても)。

  2. モニターは同じ波長もスペクトル分散も使用しません

    したがって、2つの異なるモニターを使用する場合、R、G、Bにわずかに異なる波長を使用するか、スペクトルフィルターの異なる幅にさえ使用する可能性があります( 分光器を使用して参照 )。はい、それらはハードウェアによって「正規化」されるべきですが、それは正規化された波長を使用するのと同じではありません。これは、RGBとホワイトノイズスペクトル光源を使用した問題に似ています。

  3. 直線性の監視

    人間は線形スケールでは表示されません:私たちは通常対数/指数です(あなたの見方によって異なります)はい、HW(またはSW)でそれを正規化できますが、問題は1人の人間に対して線形化すると損傷することです別のもののために。

これらすべてを一緒に使用する場合は、平均...または特別な(そして高価な)機器を使用して、標準または較正された人(業界によって異なります)に対して測定/正規化することができます。

しかし、それは家庭の状況で扱うには多すぎるので、それを産業に残し、世界のほとんどのように「平均」に重みを使用します。並べて、またはアニメーションで:)。だから私は(するでしょう):

I = 0.299 R + 0.587 G + 0.114 B
R = I
G = I
B = I
19
Spektre

R、G、Bの原色に応じて、輝度には多くの公式があります。

Rec.601/NTSC: Y = 0.299*R + 0.587*G + 0.114*B , 

Rec.709/EBU:  Y = 0.213*R + 0.715*G + 0.072*B , 

Rec.2020/UHD: Y = 0.263*R + 0.678*G + 0.059*B . 

これは、私たちの目は緑よりも赤よりも青に敏感だからです。

とはいえ、おそらく輝度ではなく輝度を計算しているので、とにかく式はすべて間違っています。一定輝度の場合、線形光に変換する必要があります

R = R' ^ 2.4 , G = G' ^ 2.4 , B = B' ^ 2.4 , 

輝度式を適用し、ガンマ領域に変換します

Y' = Y ^ (1/2.4) . 

また、3Dカラースペースを1D量に変換すると、情報の2/3が失われ、次の処理ステップで噛み付く可能性があることを考慮してください。問題に応じて、V = MAX(R、G、B)(HSV色空間から)のような異なる式の方が良い場合があります。

どうやって知るの?私はポイントン博士の信奉者であり友人です。

8
StessenJ

グレースケールへの変換にはさまざまな方法があり、異なる結果が得られますが、異なる入力カラー画像では違いが見やすくなります。

グレースケールでは実際には表示されないため、 "best"メソッドはアプリケーションに多少依存しており、見る人の目には多少依存しています。

あなたが参照する代替式は、緑の色調の変化に人間の目が敏感であり、したがって、より大きな重みを与えることに基づいています-赤と青のそれぞれに2つの緑のピクセルがあるカメラのベイヤー配列と同様です。 Wiki-バイエル配列

8
Mark Setchell

提供される答えは十分ですが、このトピックについては別の方法でもう少し議論したいと思います。

興味のあるデジタルペインティングを学んだので、より頻繁にHSVを使用します。

ペイント中にHSVを使用する方がはるかに制御しやすいですが、短くしてください。主なポイントは、光から色の概念を分離するS:彩度です。 Sを0に変えると、すでに「コンピューター」の画像のグレースケールになります。

from PIL import Image
import colorsys

def togrey(img):
    if isinstance(img,Image.Image):
        r,g,b = img.split()
        R = []
        G = []
        B = [] 
        for rd,gn,bl in Zip(r.getdata(),g.getdata(),b.getdata()) :
            h,s,v = colorsys.rgb_to_hsv(rd/255.,gn/255.,bl/255.)
            s = 0
            _r,_g,_b = colorsys.hsv_to_rgb(h,s,v)
            R.append(int(_r*255.))
            G.append(int(_g*255.))
            B.append(int(_b*255.))
        r.putdata(R)
        g.putdata(G)
        b.putdata(B)
        return Image.merge('RGB',(r,g,b))
    else:
        return None

a = Image.open('../a.jpg')
b = togrey(a)
b.save('../b.jpg')

この方法は、元の色の「明るい」ものを本当に確保しました。ただし、人間の目がデータを処理する方法を考慮せずに

5
MatrixTai

あなたの主な質問に答えて、グレーの単一の尺度を使用することには欠点があります。それはあなたがあなたのイメージから何を望むかに依存します。たとえば、白い背景に色付きのテキストがある場合、テキストを目立たせたい場合は、最小のr、g、b値を測定値として使用できます。ただし、色付きの背景に黒いテキストがある場合は、同じ結果に最大値を使用できます。私のソフトウェアでは、ユーザーが選択できる最大値、最小値、または中央値のオプションを提供しています。連続階調画像の結果も輝いています。詳細を求めるコメントに応えて、ピクセルのコードを以下に示します(防御策はありません)。

int Ind0[3] = {0, 1, 2};                 //all equal
int Ind1[3] = {2, 1, 0};                 // top, mid ,bot from mask...
int Ind2[3] = {1, 0, 2};
int Ind3[3] = {1, 2, 0};
int Ind4[3] = {0, 2, 1};
int Ind5[3] = {2, 0, 1};
int Ind6[3] = {0, 1, 2};
int Ind7[3] = {-1, -1, -1};              // not possible
int *Inds[8] = {Ind0, Ind1, Ind2, Ind3, Ind4, Ind5, Ind6, Ind7};
void grecolor(unsigned char *rgb, int bri, unsigned char *grey)
{                         //pick out bot, mid or top according to bri flag
    int r = rgb[0];
    int g = rgb[1];
    int b = rgb[2];
    int mask = 0;
    mask |= (r > g);
    mask <<= 1;
    mask |= (g > b);
    mask <<= 1;
    mask |= (b > r);
    grey[0] = rgb[Inds[mask][2 - bri]];  // 2, 1, 0 give bot, mid, top
}
1
Steve J