OpenCV – ガンマ補正、ネガポジ反転、ポスタリゼーションを行う方法

概要

OpenCV でネガポジ反転やポスタリゼーションなどの階調変換を画像に適用する方法を紹介します。

階調変換

画素値の入力を x[0,255]x \in [0, 255] としたとき、f(x)=y[0,255]f(x) = y \in [0, 255] という関数で変換することを階調変換 (gray level transformation)、階調変換を規定する関数を階調変換関数 (gray level transformation function) といいます。 実装上は、画素値は [0,255][0, 255] の 256 種類なので、入力値の変換後の値を格納した長さが 256 の 1 次元配列である LUT (Look Up Table) を用意して階調変換を行います。例えば、LUT[0] = 100 だった場合、入力値 0 は値 100 に変換されることを意味します。

cv2.LUT

OpenCV では、cv2.LUT() で階調変換が行えます。

dst = cv2.LUT(src, lut[, dst])
Python
引数
名前 デフォルト値
src ndarray
入力画像
lut sequence of int
Look Up Table の略。[0, 255] の値の変換後の値を (256,) の1次元配列で表す。例えば、lut[10] は入力10の変換後の値を表す。
返り値
名前 説明
dst 変換後の画像

sample.jpg

In [1]:
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))
Python

ネガポジ反転

画素値を反転させる階調変換をネガポジ反転といいます。

f(x)=255xf(x) = 255 – x

In [2]:
def invert(img):
    # ネガポジ反転
    y = 255 - np.arange(256)
    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)

dst = invert(img)
imshow(dst)
Python

2 値化

閾値などの条件で入力値を 2 段階にする階調変換を2 値化 (binarization) といいます。

f(x)={0xthreshold255その他の場合 f(x) = \begin{cases} 0 & x \le \mathrm{threshold} \\ 255 & その他の場合 \end{cases}
In [3]:
def binarize(img, threshold):
    # 2値化
    x = np.arange(256)
    y = np.where(x <= threshold, 0, 255)

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)

dst = binarize(img, threshold=100)
imshow(dst)
Python

ポスタリゼーション

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

In [4]:
def postalize(img, n):
    # ポスタリゼーション
    x = np.arange(256)
    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).astype(np.uint8)

    return dst


img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)

dst = postalize(img, n=7)
imshow(dst)
Python

ソラリゼーション

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

f(x)={xxthreshold255xその他の場合 f(x) = \begin{cases} x & x \le \mathrm{threshold} \\ 255 – x & その他の場合 \end{cases}
In [5]:
def solarize(img, threshold):
    # ソラリゼーション
    x = np.arange(256)
    y = np.where(x <= threshold, x, 255 - x)

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)

dst = solarize(img, threshold=128)
imshow(dst)
Python

折れ線型トーンカーブ

折れ線で表される階調変換関数を折れ線型トーンカーブといいます。

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

f(x)={255txt255x>t=clip(255t,0,255) f(x) = \begin{cases} \frac{255}{t} & x \le t \\ 255 & x > t \end{cases} = \text{clip}\left(\frac{255}{t}, 0, 255\right)

sample2.jpg

In [6]:
def increase_contrast(img, t):
    x = np.arange(256)
    y = np.clip(255 / t * x, 0, 255)

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)

dst = increase_contrast(img, t=155)
imshow(dst)
Python

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

f(x)=255t255x+t f(x) = \frac{255 – t}{255} x + t
In [7]:
def increase_contrast(img, t):
    x = np.arange(256)
    y = (255 - t) / 255 * x + t

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)

dst = increase_contrast(img, t=55)
imshow(dst)
Python

コントラストを下げる (1)

f(x)={0xt255+b255xbx>t=clip(255+b255xb,0,255) f(x) = \begin{cases} 0 & x \le t \\ \frac{255 + b}{255}x – b & x > t \end{cases} = \text{clip}\left(\frac{255 + b}{255}x – b, 0, 255\right)

ただし、bb は次を解いて得られる値です。

255+b255tb=0 \frac{255 + b}{255}t – b = 0
In [8]:
def decrease_contrast(img, t):
    x = np.arange(256)
    y = np.clip(255 / (255 - t) * x - 255 / (255 - t) * t, 0, 255)

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)

dst = decrease_contrast(img, t=55)
imshow(dst)
Python

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

f(x)=t255 f(x) = \frac{t}{255}
In [9]:
def decrease_contrast(img, t):
    x = np.arange(256)
    y = t / 255 * x

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)

dst = decrease_contrast(img, t=200)
imshow(dst)
Python

明るさ、コントラストの補正

f(x)=clip(αx+β,0,255) f(x) = \text{clip}(\alpha x + \beta , 0, 255)

α\alphaゲイン (gain) またはコントラスト (contrast)β\betaバイアス (bias) または明るさ (brightness) といいます。

In [10]:
def adjust(img, alpha, beta):
    x = np.arange(256)
    y = np.clip(alpha * x + beta, 0, 255)

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)

dst = adjust(img, alpha=2, beta=30)
imshow(dst)
Python

ガンマ補正

次の関数で行う階調変換をガンマ変換 (gamma transformation) またはガンマ補正 (gamma correction) といいます。

f(x)=(x255)γ×255 f(x) = \left(\frac{x}{255}\right)^\gamma \times 255

γ<1\gamma < 1 で暗く、γ>1\gamma > 1 で明るくなるように補正できます。

In [11]:
def gamma_correction(img, gamma):
    x = np.arange(256)
    y = (x / 255) ** gamma * 255

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)

dst = gamma_correction(img, gamma=0.3)
imshow(dst)
Python

gamma=0.3 の場合

gamma=3 の場合

S 字型トーンカーブ

S 字型のトーンカーブで階調変換すると、暗い部分は更に暗く、明るい部分は更に明るくすることができます。

以下の例では、arctan(x),x[5,5]\arctan(x), x \in [-5, 5] の値を [0,255][0, 255] にスケールすることで S 字型のトーンカーブを作成しました。

In [12]:
def s_curve(img):
    y = np.arctan(np.linspace(-5, 5, 256))
    y = 255 / (y.max() - y.min()) * (y - y.max()) + 255

    dst = cv2.LUT(img, y).astype(np.uint8)

    return dst


img = cv2.imread("sample2.jpg", cv2.IMREAD_GRAYSCALE)

dst = s_curve(img)
imshow(dst)
Python

コメント

コメントする