OpenCV – cv2.LUT の使い方、ガンマ補正、ネガポジ反転、ポスタリゼーション

概要
OpenCV でネガポジ反転やポスタリゼーションなどの階調変換を画像に適用する方法を紹介します。
階調変換
画素値の入力を $x \in [0, 255]$ としたとき、$f(x) = y \in [0, 255]$ という関数で変換することを階調変換 (gray level transformation)、階調変換を規定する関数を階調変換関数 (gray level transformation function) といいます。 実装上は、画素値は $[0, 255]$ の256種類なので、各値の変換後の値を格納した長さが256の1次元配列である LUT (Look Up Table) を用意して階調変換を行います。
cv2.LUT
OpenCV では、cv2.LUT() で階調変換が行えます。
dst = cv2.LUT(src, lut[, dst])
名前 | 型 | デフォルト値 |
---|---|---|
src | ndarray | |
入力画像 | ||
lut | sequence of int | |
Look Up Table の略。[0, 255] の値の変換後の値を (256,) の1次元配列で表す。例えば、lut[10] は入力10の変換後の値を表す。 |
名前 | 説明 | ||
---|---|---|---|
dst | 変換後の画像 |
import cv2
import numpy as np
from IPython.display import Image, display
def imshow(img):
"""ndarray 配列をインラインで Notebook 上に表示する。
"""
ret, encoded = cv2.imencode(".jpg", img)
display(Image(encoded))
# 画像を読み込む。
img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)
imshow(img)

ネガポジ反転
画素値を反転させる階調変換をネガポジ反転といいます。
$f(x) = 255 – x$

def plot_grayscale_conversion(src, dst):
fig = plt.figure(figsize=(10, 6))
ax1 = plt.subplot2grid((3, 2), (0, 0), rowspan=2)
ax2 = plt.subplot2grid((3, 2), (0, 1), rowspan=2)
ax3 = plt.subplot2grid((3, 2), (2, 0))
ax4 = plt.subplot2grid((3, 2), (2, 1))
# 入力画像を描画する。
ax1.set_title("Input")
ax1.imshow(src, cmap="gray", vmin=0, vmax=255)
ax1.set_axis_off()
# 出力画像を描画する。
ax2.set_title("Output")
ax2.imshow(dst, cmap="gray", vmin=0, vmax=255)
ax2.set_axis_off()
# 入力画像のヒストグラムを描画する。
ax3.hist(src.ravel(), bins=256, range=(0, 255), color="k")
ax3.grid()
ax3.set_xticks([0, 255])
ax3.set_yticks([])
# 出力画像のヒストグラムを描画する。
ax4.hist(dst.ravel(), bins=256, range=(0, 255), color="k")
ax4.set_xticks([0, 255])
ax4.set_yticks([])
ax4.grid()
# ネガポジ反転
x = np.arange(256)
y = 255 - x
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

2値化
閾値などなんらかの条件で入力値を2段階にする階調変換を2値化 (binarization) といいます。
$$ f(x) = \begin{cases} 0 & x \le \mathrm{threshold} \\ 255 & その他の場合 \end{cases} $$
# 2値化
x = np.arange(256)
y = np.where(x <= 100, 0, 255)
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

ポスタリゼーション
色を $n$ 段階にする階調変換をポスタリゼーション (postarization) といいます。
$[0, 255]$ の範囲に $n$ 個の区間を用意して、各値がどの区間に属するかを numpy.digitize()
で調べて、LUT を作成します。

# ポスタリゼーション
x = np.arange(256)
n = 7 # 画素値を何段階で表現するか
bins = np.linspace(0, 255, n + 1)
y = np.array([bins[i - 1] for i in np.digitize(x, bins)]).astype(int)
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

ソラリゼーション
過度の露光で画像の一部が反転する現象を ソラリゼーション (solarization) といいます。 以下の関数でソラリゼーションを擬似的に再現できます。

# ソラリゼーション
x = np.arange(256)
y = (np.sin(x * 2 * np.pi / 255) + 1) * 255 / 2
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

折れ線型トーンカーブ
折れ線で表される階調変換関数を折れ線型トーンカーブといいます。
コントラストを上げる (1)
$$ f(x) = \begin{cases} \frac{255}{t} & x \le t \\ 255 & x > t \end{cases} = \text{clip}(\frac{255}{t}, 0, 255) $$
# 画像を読み込む。
img = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)
# コントラストを上げる折れ線型トーンカーブ
t = 155
x = np.arange(256)
y = np.clip(255 / t * x, 0, 255)
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

コントラストを上げる (2)

# コントラストを上げる折れ線型トーンカーブ
t = 55
x = np.arange(256)
y = (255 - t) / 255 * x + t
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

コントラストを下げる (1)
$$ f(x) = \begin{cases} 0 & x \le t \\ \frac{255 + b}{255}x – b & x > t \end{cases} = \text{clip}(\frac{255 + b}{255}x – b, 0, 255) $$ただし、$b$ は次を解いて得られる値です。
$$ \frac{255 + b}{255}t – b = 0 $$
# コントラストを下げる折れ線型トーンカーブ
t = 55
x = np.arange(256)
y = np.clip(255 / (255 - t) * x - 255 / (255 - t) * t, 0, 255)
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

コントラストを下げる (2)
$$ f(x) = \frac{t}{255} $$
# コントラストを下げる折れ線型トーンカーブ
t = 200
x = np.arange(256)
y = t / 255 * x
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

明るさ、コントラストの補正
$$ f(x) = \text{clip}(\alpha x + \beta , 0, 255) $$$\alpha$ はゲイン (gain) またはコントラスト (contrast)、$\beta$ はバイアス (bias) または明るさ (brightness) といいます。

# 明るさ、コントラストの補正
alpha = 2
beta = 30
x = np.arange(256)
y = np.clip(alpha * x + beta, 0, 255)
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

ガンマ補正
次の関数で行う階調変換をガンマ変換 (gamma transformation) またはガンマ補正 (gamma correction) といいます。
$$ f(x) = \left(\frac{x}{255}\right)^\gamma \times 255 $$$\gamma < 1$ で黒く、$\gamma > 1$ で明るくなるように補正できます。

gamma = 1 / 3
x = np.arange(256)
y = (x / 255) ** gamma * 255
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

gamma = 3
x = np.arange(256)
y = (x / 255) ** gamma * 255
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

S 字型トーンカーブ
S 字型のトーンカーブで階調変換すると、暗い部分は更に暗く、明るい部分は更に明るくすることができます。
以下の例では、$\arctan(x), x \in [-5, 5]$ の値を $[0, 255]$ にスケールすることで S 字型のトーンカーブを作成しました。

def scale(x):
"""x の範囲を [0, 255] にスケールする。
"""
return 255 / (x.max() - x.min()) * (x - x.max()) + 255
x = np.arange(256)
y = np.arctan(np.linspace(-5, 5, 256))
y = scale(y) # [atan(-5), atan(5)] -> [0, 255] にスケール
# 変換する。
dst = cv2.LUT(img, y)
plot_grayscale_conversion(img, dst)

-
前の記事
OpenCV – resize で画像をリサイズする方法 2020.06.07
-
次の記事
OpenCV – アルファブレンドで画像を合成する方法 2020.06.10