Pynote

Python、機械学習、画像処理について

OpenCV - 画像の明るさやコントラストを変更、ガンマ補正など

積和演算で明るさ、コントラストを変更する。

画像の画素 (x, y) の画素値を src(x, y) としたとき、次の式で明るさを変更する。

\displaystyle
dst(x, y) = \alpha src(x, y) + \beta

ただし、\alpha, \beta は定数で、\alpha はゲイン (gain) またはコントラスト (contrast)、\beta はバイアス (bias) または明るさ (brightness) という。

コード

積和演算を行った後、値を画素値として有効な値にするため、[0, 255] の範囲でクリップを行い、型を numpy.uint8 にキャストしている。

import cv2
import numpy as np


def adjust(img, alpha=1.0, beta=0.0):
    # 積和演算を行う。
    dst = alpha * img + beta
    # [0, 255] でクリップし、uint8 型にする。
    return np.clip(dst, 0, 255).astype(np.uint8)

# 画像を読み込む。
src = cv2.imread("test.jpg")

# コントラスト、明るさを変更する。
dst = adjust(src, alpha=2.0, beta=30.0)

# 保存する。
cv2.imwrite("dst.png", dst)

各パラメータによる結果

バイアスを 1.0 で固定し、ゲインのパラメータをいくつか試して、結果を確認する。

import cv2
import matplotlib.pyplot as plt
import numpy as np

# 画像を読み込む。
img = cv2.imread("test.jpg")

# 結果を描画する。
fig = plt.figure(figsize=(10, 7))

params = np.linspace(0.2, 1.8, 9)
for i, p in enumerate(params, 1):
    # コントラスト、明るさを変更する。
    dst = adjust(img, alpha=p)

    # Axes を追加する。
    ax = fig.add_subplot(3, 3, i)
    ax.set_axis_off()
    ax.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB))  # matplotlib は RGB 順
    ax.set_title(f"alpha={p:.1f}")

plt.show()

コントラストを 0.0 で固定し、バイアスのパラメータをいくつか試して、結果を確認する。

import cv2
import matplotlib.pyplot as plt
import numpy as np

# 画像を読み込む。
img = cv2.imread("test.jpg")

# 結果を描画する。
fig = plt.figure(figsize=(10, 7))

params = np.linspace(-100.0, 100.0, 9)
for i, p in enumerate(params, 1):
    # コントラスト、明るさを変更する。
    dst = adjust(img, beta=p)

    # Axes を追加する。
    ax = fig.add_subplot(3, 3, i)
    ax.set_axis_off()
    ax.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB))  # matplotlib は RGB 順
    ax.set_title(f"beta={p:.1f}")

plt.show()


ipywidget で変更する。

import cv2
from IPython.display import Image, display
from ipywidgets import widgets


def imshow(img):
    """画像を Notebook 上に表示する。
    """
    encoded = cv2.imencode(".png", img)[1]
    display(Image(encoded, width=400))


def process(img, alpha, beta):
    """明るさ、コントラストを調整し、結果を表示する。
    """
    dst = adjust(img, alpha, beta)
    imshow(dst)


# パラメータ「ゲイン」を設定するスライダー
alpha_slider = widgets.FloatSlider(
    min=0.0, max=3.0, step=0.1, value=1.0, description="alpha: "
)
alpha_slider.layout.width = "400px"

# パラメータ「バイアス」を設定するスライダー
beta_slider = widgets.FloatSlider(
    min=-100.0, max=100.0, step=10.0, value=0.0, description="beta: "
)
beta_slider.layout.width = "400px"

# 画像を読み込む。
img = cv2.imread("test.jpg")

# ウィジェットを表示する。
widgets.interactive(
    process, img=widgets.fixed(img), alpha=alpha_slider, beta=beta_slider
)


ガンマ補正

$\gamma = 1$ のとき、$y = x$ となり、入力と出力が同じになる。

画像の画素 (x, y) の画素値を src(x, y) としたとき、次の式で変換する処理をガンマ補正 (gamma correction) という。

\displaystyle
y = \left(\frac{x}{255}\right)^\gamma \times 255

ただし、\gamma は定数である。

ガンマの値を変えたときの入力と出力の関係は以下のようになる。

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(figsize=(6, 6))
for gamma in np.linspace(0.2, 1.8, 9):
    x = np.arange(256)
    y = (np.arange(256) / 255) ** gamma * 255

    ax.plot(x, y, label=f"{gamma:.1f}")
ax.legend()


コード

画素値の変換には、OpenCVLUT を使う。

dst = cv.LUT(src, lut[, dst])
  • src: 入力画像
  • lut: 256個の画素値の変換後の値を表す配列
  • dst: 結果画像 (引数経由で受け取る場合は指定する。)

ガンマ補正の計算を行ったあと、値を画素値として有効な値にするため、[0, 255] の範囲でクリップを行い、型を numpy.uint8 にキャストしている。

import cv2
import numpy as np


def gamma_correction(img, gamma):
    # テーブルを作成する。
    table = (np.arange(256) / 255) ** gamma * 255
    # [0, 255] でクリップし、uint8 型にする。
    table = np.clip(table, 0, 255).astype(np.uint8)

    return cv2.LUT(img, table)


# 画像を読み込む。
src = cv2.imread("test.jpg")

# コントラスト、明るさを変更する。
dst = gamma_correction(src, gamma=2.0)

# 保存する。
cv2.imwrite("dst.png", dst)

各パラメータによる結果

import cv2
import matplotlib.pyplot as plt
import numpy as np

# 画像を読み込む。
img = cv2.imread("test.jpg")

# 結果を描画する。
fig = plt.figure(figsize=(10, 7))

params = np.linspace(0.2, 1.8, 9)
for i, p in enumerate(params, 1):
    # ガンマ補正を行う。
    dst = gamma_correction(img, p)

    # Axes を追加する。
    ax = fig.add_subplot(3, 3, i)
    ax.set_axis_off()
    ax.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB))  # matplotlib は RGB 順
    ax.set_title(f"gamma={p:.1f}")

plt.show()


ipywidget で変更する。

import cv2
from IPython.display import Image, display
from ipywidgets import widgets


def imshow(img):
    """画像を Notebook 上に表示する。
    """
    encoded = cv2.imencode(".png", img)[1]
    display(Image(encoded, width=400))


def process(img, gamma):
    """ガンマ補正を行い、結果を表示する。
    """
    dst = gamma_correction(img, gamma)
    imshow(dst)


# パラメータ「ガンマ」を設定するスライダー
slider = widgets.FloatSlider(
    min=0.0, max=3.0, step=0.1, value=1.0, description="gamma: "
)
slider.layout.width = "400px"

# 画像を読み込む。
img = cv2.imread("test.jpg")

# ウィジェットを表示する。
widgets.interactive(process, img=widgets.fixed(img), gamma=slider)