【Python・OpenCV】カラー画像をグレースケールに変換する

※当サイトではアフィリエイト広告を利用しています

Python プログラミング 画像処理

【Python・OpenCV】カラー画像をグレースケールに変換する

2023-05-16

はじめに

カラー画像をグレースケールに変換する方法について説明します。
グレースケールは明度という 1 つのコンポーネントのみを使用して色を定義する最も単純なモデルとなります。明るさの値は、0 (黒) から 255 (白) までの値を使用して記述されます。
グレースケール画像は カラー画像より情報が少ないため、使用するメモリが少なくて済み、特に複雑な計算の場合に高速に処理することができるため、画像処理ではよく使われます。

計算によりグレースケールに変換

計算式によりカラー画像をグレースケールに変換する3つの方法を紹介します。

平均法(Average Method)

赤(R)、緑(G)、青(B)の成分値の平均値を求めて、グレースケールの明るさとします。
以下の式で表されます。

grayscale = (R + G + B) / 3

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import cv2
import numpy as np
# 画像を読み込む
src = cv2.imread('image.jpg')
# 後の計算のためにfloat64の浮動小数に変換
src_f = src.astype(np.float64)
# 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数)
height, width, channel = src.shape[:3]
# ----------------------------------
# 平均法(Average Method)
# ----------------------------------
# 出力画像
avg_result = np.zeros((height, width), np.uint8)
# 平均法(Average Method)
for i in range(height):
for j in range(width):
blue = src_f[i, j, 0]
green = src_f[i, j, 1]
red = src_f[i, j, 2]
avg_result[i, j] = (red + green + blue) / 3
# 画像を保存する
cv2.imwrite('avg_result.jpg', avg_result)
import cv2 import numpy as np # 画像を読み込む src = cv2.imread('image.jpg') # 後の計算のためにfloat64の浮動小数に変換 src_f = src.astype(np.float64) # 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数) height, width, channel = src.shape[:3] # ---------------------------------- # 平均法(Average Method) # ---------------------------------- # 出力画像 avg_result = np.zeros((height, width), np.uint8) # 平均法(Average Method) for i in range(height): for j in range(width): blue = src_f[i, j, 0] green = src_f[i, j, 1] red = src_f[i, j, 2] avg_result[i, j] = (red + green + blue) / 3 # 画像を保存する cv2.imwrite('avg_result.jpg', avg_result)
import cv2
import numpy as np

# 画像を読み込む
src = cv2.imread('image.jpg')

# 後の計算のためにfloat64の浮動小数に変換
src_f = src.astype(np.float64)

# 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数)
height, width, channel = src.shape[:3]

# ----------------------------------
# 平均法(Average Method)
# ----------------------------------
# 出力画像
avg_result = np.zeros((height, width), np.uint8)

# 平均法(Average Method)
for i in range(height):
    for j in range(width):
        blue = src_f[i, j, 0]
        green = src_f[i, j, 1]
        red = src_f[i, j, 2]
        avg_result[i, j] = (red + green + blue) / 3

# 画像を保存する
cv2.imwrite('avg_result.jpg', avg_result)

平均法はシンプルな変換方法であり、画像処理の初学者にとっては理解しやすいという利点があります。しかし、色の明るさが偏っている画像ではグレースケール化する際に色の情報が失われることがあるため、正確なグレースケール画像を得ることができない場合があります。このため、色のバランスが取れている画像に適しています。また、アイコンやボタンなどのシンプルな図形をグレースケール化する場合に使用されます。

ライトネス法(Lightness Method)

赤(R)、緑(G)、青(B)の成分値の最大値と最小値を求めて、その平均値をグレースケールの明るさとします。
以下の式で表されます。

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import cv2
import numpy as np
# 画像を読み込む
src = cv2.imread('image.jpg')
# 後の計算のためにfloat64の浮動小数に変換
src_f = src.astype(np.float64)
# 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数)
height, width, channel = src.shape[:3]
# ----------------------------------
# ライトネス法(Lightness Method)
# ----------------------------------
# 出力画像
lightness_result = np.zeros((height, width), np.uint8)
# ライトネス法(Lightness Method)
for i in range(height):
for j in range(width):
blue = src_f[i, j, 0]
green = src_f[i, j, 1]
red = src_f[i, j, 2]
lightness_result[i, j] = (max(blue, green, red) + min(blue, green, red)) / 2
# 画像を保存する
cv2.imwrite('lightness_result.jpg', lightness_result)
import cv2 import numpy as np # 画像を読み込む src = cv2.imread('image.jpg') # 後の計算のためにfloat64の浮動小数に変換 src_f = src.astype(np.float64) # 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数) height, width, channel = src.shape[:3] # ---------------------------------- # ライトネス法(Lightness Method) # ---------------------------------- # 出力画像 lightness_result = np.zeros((height, width), np.uint8) # ライトネス法(Lightness Method) for i in range(height): for j in range(width): blue = src_f[i, j, 0] green = src_f[i, j, 1] red = src_f[i, j, 2] lightness_result[i, j] = (max(blue, green, red) + min(blue, green, red)) / 2 # 画像を保存する cv2.imwrite('lightness_result.jpg', lightness_result)
import cv2
import numpy as np

# 画像を読み込む
src = cv2.imread('image.jpg')

# 後の計算のためにfloat64の浮動小数に変換
src_f = src.astype(np.float64)

# 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数)
height, width, channel = src.shape[:3]

# ----------------------------------
# ライトネス法(Lightness Method)
# ----------------------------------
# 出力画像
lightness_result = np.zeros((height, width), np.uint8)

# ライトネス法(Lightness Method)
for i in range(height):
    for j in range(width):
        blue = src_f[i, j, 0]
        green = src_f[i, j, 1]
        red = src_f[i, j, 2]
        lightness_result[i, j] = (max(blue, green, red) + min(blue, green, red)) / 2

# 画像を保存する
cv2.imwrite('lightness_result.jpg', lightness_result)

ライトネス法は彩度が高い画像に適しています。彩度が高い画像に対して平均法を用いると、画像が灰色になってしまう場合があるためです。そのため、色の明るさが偏っている場合でも、平均法よりも正確なグレースケール画像を得ることができます。また、写真のグレースケール化に使用されます。

ルミナンス法(Luminosity Method)

人間の目が赤(R)、緑(G)、青(B)の色に対して異なる感度を持っていることを考慮し、以下の重み係数を用いて、各成分に重みをかけて加算します。ITU-R BT.709標準に基づく式となり、XYZ色空間のYチャンネルとなります。

grayscale = 0.2126 R + 0.7152 G + 0.0722 B

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import cv2
import numpy as np
# 画像を読み込む
src = cv2.imread('image.jpg')
# 後の計算のためにfloat64の浮動小数に変換
src_f = src.astype(np.float64)
# 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数)
height, width, channel = src.shape[:3]
# ----------------------------------
# ルミナンス法(Luminosity Method)
# ----------------------------------
# 出力画像
luminosity_result = np.zeros((height, width), np.uint8)
# ルミナンス法(Luminosity Method)
for i in range(height):
for j in range(width):
blue = src_f[i, j, 0]
green = src_f[i, j, 1]
red = src_f[i, j, 2]
luminosity_result[i, j] = 0.2126*red + 0.7152*green + 0.0722*blue
# 画像を保存する
cv2.imwrite('luminosity_result.jpg', luminosity_result)
import cv2 import numpy as np # 画像を読み込む src = cv2.imread('image.jpg') # 後の計算のためにfloat64の浮動小数に変換 src_f = src.astype(np.float64) # 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数) height, width, channel = src.shape[:3] # ---------------------------------- # ルミナンス法(Luminosity Method) # ---------------------------------- # 出力画像 luminosity_result = np.zeros((height, width), np.uint8) # ルミナンス法(Luminosity Method) for i in range(height): for j in range(width): blue = src_f[i, j, 0] green = src_f[i, j, 1] red = src_f[i, j, 2] luminosity_result[i, j] = 0.2126*red + 0.7152*green + 0.0722*blue # 画像を保存する cv2.imwrite('luminosity_result.jpg', luminosity_result)
import cv2
import numpy as np

# 画像を読み込む
src = cv2.imread('image.jpg')

# 後の計算のためにfloat64の浮動小数に変換
src_f = src.astype(np.float64)

# 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数)
height, width, channel = src.shape[:3]

# ----------------------------------
# ルミナンス法(Luminosity Method)
# ----------------------------------
# 出力画像
luminosity_result = np.zeros((height, width), np.uint8)

# ルミナンス法(Luminosity Method)
for i in range(height):
    for j in range(width):
        blue = src_f[i, j, 0]
        green = src_f[i, j, 1]
        red = src_f[i, j, 2]
        luminosity_result[i, j] = 0.2126*red + 0.7152*green + 0.0722*blue

# 画像を保存する
cv2.imwrite('luminosity_result.jpg', luminosity_result)

人間の目の感度を考慮した法則(ITU-R BT.601)

ルミナンス法と同様に係数を用いる方法です。以下の式はITU-R BT.601標準に基づくものです。

grayscale = 0.299 R + 0.587 G + 0.114 B

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import cv2
import numpy as np
# 画像を読み込む
src = cv2.imread('image.jpg')
# 後の計算のためにfloat64の浮動小数に変換
src_f = src.astype(np.float64)
# 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数)
height, width, channel = src.shape[:3]
# ----------------------------------
# 人間の目の感度を考慮した法則(ITU-R BT.601)
# ----------------------------------
# 出力画像
bt601_result = np.zeros((height, width), np.uint8)
# 平均法(Average Method)
for i in range(height):
for j in range(width):
blue = src_f[i, j, 0]
green = src_f[i, j, 1]
red = src_f[i, j, 2]
bt601_result[i, j] = 0.299*red + 0.587*green + 0.114*blue
# 画像を保存する
cv2.imwrite('bt601_result.jpg', bt601_result)
import cv2 import numpy as np # 画像を読み込む src = cv2.imread('image.jpg') # 後の計算のためにfloat64の浮動小数に変換 src_f = src.astype(np.float64) # 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数) height, width, channel = src.shape[:3] # ---------------------------------- # 人間の目の感度を考慮した法則(ITU-R BT.601) # ---------------------------------- # 出力画像 bt601_result = np.zeros((height, width), np.uint8) # 平均法(Average Method) for i in range(height): for j in range(width): blue = src_f[i, j, 0] green = src_f[i, j, 1] red = src_f[i, j, 2] bt601_result[i, j] = 0.299*red + 0.587*green + 0.114*blue # 画像を保存する cv2.imwrite('bt601_result.jpg', bt601_result)
import cv2
import numpy as np

# 画像を読み込む
src = cv2.imread('image.jpg')

# 後の計算のためにfloat64の浮動小数に変換
src_f = src.astype(np.float64)

# 画像のサイズを取得(height:高さ, width:幅, channel:チャンネル数)
height, width, channel = src.shape[:3]

# ----------------------------------
# 人間の目の感度を考慮した法則(ITU-R BT.601)
# ----------------------------------
# 出力画像
bt601_result = np.zeros((height, width), np.uint8)

# 平均法(Average Method)
for i in range(height):
    for j in range(width):
        blue = src_f[i, j, 0]
        green = src_f[i, j, 1]
        red = src_f[i, j, 2]
        bt601_result[i, j] = 0.299*red + 0.587*green + 0.114*blue

# 画像を保存する
cv2.imwrite('bt601_result.jpg', bt601_result)

人間の目の感度を考慮した法則は、一般的なグレースケール化の方法としてよく知られています。この変換方法は、一般的な画像に対して適しており、特定のアプリケーションに限定されることはありません。ただし、彩度が高い画像に対してはライトネス法を使用することが推奨されています。

OpenCVの機能を使ったグレースケール変換

OpenCVに実装されている、カラー画像をグレースケール画像に変換する機能を紹介します。

cv2.imread関数

画像をファイルから読み込む際に直接グレースケールに変換するにはcv2.imread関数を使うことができます。
引数のImreadModescv2.IMREAD_GRAYSCALEを指定します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import cv2
# 画像読み込み
imread_result = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# 画像を保存する
cv2.imwrite('imread_result.jpg', imread_result)
import cv2 # 画像読み込み imread_result = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 画像を保存する cv2.imwrite('imread_result.jpg', imread_result)
import cv2

# 画像読み込み
imread_result = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# 画像を保存する
cv2.imwrite('imread_result.jpg', imread_result)

cv2.imread関数の詳細については下の投稿を参考にして下さい。

cv2.cvtColor関数

カラー画像のデータをグレースケール画像に変換する方法としてcv2.cvtColor関数を使うことができます。
引数のColorConversionCodescv2.COLOR_BGR2GRAYを指定します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import cv2
# 画像を読み込む
src = cv2.imread('image.jpg')
# ----------------------------------
# cvtColor
# ----------------------------------
# 出力画像
cvtcolor_result = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
# 画像を保存する
cv2.imwrite('cvtcolor_result.jpg', cvtcolor_result)
import cv2 # 画像を読み込む src = cv2.imread('image.jpg') # ---------------------------------- # cvtColor # ---------------------------------- # 出力画像 cvtcolor_result = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) # 画像を保存する cv2.imwrite('cvtcolor_result.jpg', cvtcolor_result)
import cv2

# 画像を読み込む
src = cv2.imread('image.jpg')

# ----------------------------------
# cvtColor
# ----------------------------------
# 出力画像
cvtcolor_result = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)

# 画像を保存する
cv2.imwrite('cvtcolor_result.jpg', cvtcolor_result)

cv2.decolor関数

cv2.decolor関数は、下記リンクの論文に基づく変換方法となります。

Contrast Preserving Decolorization

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import cv2
# 画像を読み込む
src = cv2.imread('image.jpg')
# ----------------------------------
# decolor
# ----------------------------------
# 出力画像
decolor_result, _ = cv2.decolor(src)
# 画像を保存する
cv2.imwrite('decolor_result.jpg', decolor_result)
import cv2 # 画像を読み込む src = cv2.imread('image.jpg') # ---------------------------------- # decolor # ---------------------------------- # 出力画像 decolor_result, _ = cv2.decolor(src) # 画像を保存する cv2.imwrite('decolor_result.jpg', decolor_result)
import cv2

# 画像を読み込む
src = cv2.imread('image.jpg')

# ----------------------------------
# decolor
# ----------------------------------
# 出力画像
decolor_result, _ = cv2.decolor(src)

# 画像を保存する
cv2.imwrite('decolor_result.jpg', decolor_result)

変換方法の比較

以上の変換の結果を比較しました。

ライトネス法は入力画像の明るさを十分に捉えることができていない様です。

また、cv2.decolorは赤系と青系の色の明るさの差を表現できていない様ですが、全体のコントラストは十分ですね。今回採用した入力画像は、このアルゴリズムに適した画像では無いのかもしれません。

人の目の感度を考慮した法則、cv2.cvtColor、cv2.imreadは非常によく似た結果となっていますが、厳密に見ていくとそれぞれが若干異なっている様です。

明るさの差を表現するという意味では、ルミナンス法が最もよく表現できている様に思います。

入力画像として、この画像を使用しました。

引用元
https://pixabay.com/ja/photos/%E8%90%BD%E6%9B%B8%E3%81%8D-%E5%A3%81-%E5%A3%81%E7%94%BB-569265/

おわりに

以上の様に、カラー画像からグレースケールに変換する方法には様々なバリエーションが存在します。
一概にこれが良いと決めることはできませんが、下のいずれかの変換方法が良さそうです。

  • ルミナンス法
  • 人の目の感度を考慮した法則
  • cv2.cvtColor
  • cv2.imread

最後までご覧いただきありがとうございました。

■(広告)OpenCVの参考書としてどうぞ!■

参考リンク

-Python, プログラミング, 画像処理
-

ja Japanese
S