カラー画像処理の Python 実現ガイド
【概要】カラー画像処理の用語と Python 実装を、フィルタリング処理と画像変換に絞って示す。用語は、画像の基本概念、色空間と色表現、画質改善技術、ノイズ評価と除去、畳み込み演算、エッジ解析、画像変換、画像評価に分類し、対応する Python 関数を示す。演習として、画質改善処理、ノイズ量の評価、畳み込みフィルタの適用、エッジの解析、リサイズと補間の5つを提供する。本資料は Windows 上で、GPU の有無を問わず動作する(CUDA 等は不要)。
【目次】
【サイト内の Python 関連主要ページ】
- Windows AI支援Python開発環境構築ガイド: 別ページ »で説明
- AIエディタ Windsurf の活用: 別ページ »で説明
- AIエディタ Cursor ガイド: 別ページ »で説明
- Google Colaboratory: 別ページ »で説明
- Python(Google Colaboratory を含む)のまとめ: 別ページ »で説明
- 機械学習の Python 実現ガイド: 別ページ »で説明
- 行列計算の Python 実現ガイド: 別ページ »で説明
- 統計分析の Python 実現ガイド: 別ページ »で説明
- 音声信号処理の Python 実現ガイド: 別ページ »で説明
- カラー画像処理の Python 実現ガイド: 別ページ »で説明
- Python による簡単なアドベンチャーゲーム(変数、式、if、while、関数、print、time.sleep、def、global を使用): 別ページ »で説明
- Python プログラミング講座:基礎から応用まで(授業資料、全15回): 別ページ »で説明
- Python プログラミングの例と実践ガイド: 別ページ »で説明
【外部リソース】
- Python の公式サイト: https://www.python.org
- 東京大学の「Python プログラミング入門」: https://utokyo-ipp.github.io/IPP_textbook.pdf
- ITmedia 社の「Python チートシート」の記事: https://atmarkit.itmedia.co.jp/ait/articles/2004/20/news015.html
カラー画像処理の基本
本資料は、Python によるカラー画像処理の基礎(フィルタリングと変換)を学ぶための教材である。OpenCV と NumPy を用いた実装を対象とし、プログラミングの基礎知識を前提とする。Linux の知識は不要であり、Windows のパソコンで動作する。GPU の有無は問わない。
本資料で使用する基本概念を示す。詳細は「用語リスト」を参照する。
ピクセル(画素):デジタル画像を構成する最小単位。各ピクセルは位置座標と色情報を持つ。カラー画像では赤(R)、緑(G)、青(B)の3つの値で色を表す。
解像度:画像の幅と高さのピクセル数。解像度が高いほど詳細を保持できるが、データ量は増加する。
色空間:色を数値で表すための座標系。RGB はディスプレイ表示に、HSV は色相・彩度・明度に基づく処理に用いる。
チャンネル:画像の各色成分を表す配列。RGB 画像は3チャンネル、グレースケール画像は1チャンネルを持つ。各チャンネルは2次元配列として処理できる。
畳み込み演算:小さなフィルタ(カーネル)を画像全体に適用し、各ピクセルとその近傍の重み付き和を計算する演算。ぼかし、鮮鋭化、エッジ検出に用いる。
ノイズ:画像に含まれる不要な変動成分。センサーノイズ、圧縮ノイズ、伝送エラーなどが原因となる。ガウシアンノイズやソルト&ペッパーノイズなどがある。
エッジ:画像内で輝度や色が急変する境界。物体の輪郭に対応し、物体認識や領域分割の基礎となる。
ヒストグラム:画像内の各輝度値または色値の出現頻度を表すグラフ。明るさ分布や色分布の可視化、画質評価、コントラスト調整に用いる。
補間:リサイズや回転時に、新しいピクセル位置の値を周囲のピクセルから推定する処理。最近傍補間、バイリニア補間、バイキュービック補間がある。
フィルタ:画像の特定の性質を強調または抑制する処理。平滑化フィルタはノイズを低減し、鮮鋭化フィルタは細部を強調する。
用語リスト
画像の基本概念
画像サイズ:画像の幅と高さをピクセル単位で表した値。640×480(VGA)、1920×1080(Full HD)などがある。RGB 画像は (高さ, 幅, 3) の形状を持つ。
ビット深度:各色チャンネルを表すビット数。8 ビット(0〜255 の 256 階調)が一般的である。
アスペクト比:画像の幅と高さの比率。16:9、4:3、1:1 などがある。リサイズ時にアスペクト比を維持しないと画像が歪む。
色空間と色表現
RGB 色空間:赤・緑・青の加法混色により色を表す色空間。各成分は 0〜255 の整数値で表す。OpenCV は画像を BGR の順で扱う。
HSV 色空間:色相(Hue)、彩度(Saturation)、明度(Value)により色を表す色空間。OpenCV では色相は 0〜180、彩度と明度は 0〜255 で表す。色に基づく物体検出や領域抽出に適する。
グレースケール:輝度情報のみを持つ単色画像。RGB からの変換では重み付け(Y = 0.299R + 0.587G + 0.114B)を用いる。エッジ検出や勾配計算は通常このグレースケール画像に対して行う(カラーのままチャンネルごとに処理すると結果が一致せず、計算量も増えるため)。
色相:色の種類を表す値。HSV 色空間の H 成分に対応する。
彩度:色の鮮やかさを表す値。彩度が高いほど鮮やかで、低いほど無彩色に近くなる。HSV 色空間の S 成分に対応する。
明度:色の明るさを表す値。HSV 色空間の V 成分に対応する。
画質改善技術
ヒストグラム均等化:ヒストグラムを平坦化してコントラストを改善する処理。累積分布関数を変換関数として輝度値を再配置する。局所的なコントラスト差が大きい画像では CLAHE を用いる。
CLAHE(適応的ヒストグラム均等化):画像を小領域(タイル)に分割し、各領域でヒストグラム均等化を適用する手法。clipLimit はコントラスト増幅の上限を制御し、値が大きいほどコントラストが強くなる。tileGridSize は分割するタイル数(行数, 列数)で、タイル数が多いほど局所的な処理となる。
ガンマ補正:輝度を非線形に変換する処理。0〜1 に正規化した入力 Iin に対し、Iout = Iinγ である。γ < 1 で明るくなり、γ > 1 で暗くなる。
コントラスト調整:明暗差を変更する処理。線形変換 Iout = α × Iin + β による。α はコントラスト係数(1 より大きいとコントラストが増加)、β は輝度オフセット(正で明るく、負で暗くなる)である。
ノイズ評価と除去
ガウシアンノイズ:正規分布に従うノイズ。センサーノイズとして現れる。平均値と標準偏差で特徴づけられる。
ソルト&ペッパーノイズ:白点(ソルト)と黒点(ペッパー)がランダムに現れるノイズ。伝送エラーや不良画素により発生する。
ノイズ分散:ノイズの強さを表す統計量。値が大きいほどノイズが強い。
SNR(信号対雑音比):信号の電力とノイズの電力の比。単位は dB で、SNR = 10 log10 (Psignal / Pnoise) である。値が大きいほど信号品質が高い。本資料の演習では、元画像を信号、元画像と処理後画像の差をノイズとみなして算出する。
ガウシアンフィルタ:ガウス分布に基づく重み付けを行う平滑化フィルタ。ガウシアンノイズの除去に有効だが、エッジがぼやける。標準偏差でぼかしの強さを制御する。
メディアンフィルタ:近傍ピクセルの中央値を出力する非線形フィルタ。ソルト&ペッパーノイズの除去に有効で、エッジを保持しやすい。カーネルサイズ ksize は正の奇数(3, 5, 7 …)を指定する。
バイラテラルフィルタ:空間的な近さと色の類似性の両方を考慮する非線形フィルタ。エッジを保存しながら平滑化する。計算コストは高い。
畳み込み演算
カーネル(フィルタ):畳み込み演算で使用する小さな行列。3×3、5×5 などのサイズを用いる。値により、平滑化、鮮鋭化、エッジ検出などの処理を実現する。
畳み込み処理:カーネルを画像全体に適用し、各位置で重み付き和を計算する処理。多くのフィルタ処理の基礎となる。
平滑化カーネル:周囲のピクセル値を平均化するカーネル。画像をぼかしてノイズを低減する。全要素が等しい値を持つ形式が一般的である。
鮮鋭化カーネル:中心ピクセルを強調し、周囲を減算するカーネル。エッジや細部を強調する。
エッジ検出カーネル:輝度の勾配を計算するカーネル。Sobel カーネルなどがあり、水平方向や垂直方向のエッジを検出する。
エッジ解析
勾配:輝度変化の大きさと方向を表す量。勾配が大きい場所がエッジに対応する。X 方向と Y 方向の偏微分で計算する。
Sobel フィルタ:勾配を計算してエッジを検出するフィルタ。水平方向と垂直方向の微分を近似する 3×3 のカーネルを使用する。
Laplacian フィルタ:2次微分によりエッジを検出するフィルタ。全方向のエッジを一度に検出できるが、ノイズに敏感である。事前にガウシアンフィルタでノイズを低減してから適用する。結果は負値や 255 を超える値を含むため、表示前に絶対値化・8bit 化(cv2.convertScaleAbs)が必要である。
Canny エッジ検出:多段階アルゴリズムによるエッジ検出手法。ノイズ除去、勾配計算、非極大値抑制(エッジの細線化)、ヒステリシス閾値処理(2つの閾値による連結エッジの抽出)を順に実行する。
エッジ強度:各ピクセルでのエッジの強さ。√(Gx2 + Gy2) で計算する。
エッジ方向:エッジの向きを表す角度。勾配ベクトルの方向に対応し、arctan(Gy / Gx) で計算する。
画像変換
リサイズ:画像のサイズを変更する処理。拡大または縮小ができる。
最近傍補間:最も近いピクセルの値をそのまま使用する補間手法。計算が高速だが、ジャギー(階段状のギザギザ)が発生しやすい。マスク画像やラベル画像など、ピクセル値の混合を避けたい場合に使用する。
バイリニア補間:周囲4ピクセルの値を線形補間する手法。最近傍補間より滑らかな結果が得られる。
バイキュービック補間:周囲16ピクセルの値を3次関数で補間する手法。バイリニア補間より滑らかな結果が得られるが、計算コストが高い。
回転:画像を指定角度だけ回転させる処理。回転中心と角度を指定する。
反転:画像を水平または垂直に反転させる処理。鏡像を作成する。
画像評価
PSNR(ピーク信号対雑音比):画像の品質を評価する指標。元画像と処理後画像の平均二乗誤差から計算する。単位は dB で、値が大きいほど品質が高い。圧縮や軽い劣化の評価では 30dB 以上で良好とされることが多いが、用途により基準は異なる。
MSE(平均二乗誤差):2つの画像の差の二乗の平均。値が小さいほど類似度が高い。MSE = (1/N) Σ (I1 − I2)2 である。PSNR の計算に使用する。
ヒストグラム比較:2つの画像のヒストグラムの類似度を測定する手法。相関、カイ二乗、交差、バタチャリヤ距離などの方法がある。
ブラー検出:画像のぼやけ度合いを定量化する処理。Laplacian 分散により評価する。Laplacian 分散は画像の内容・解像度・コントラストに依存する相対指標であり、同一条件下の比較に用いる(単独画像の絶対判定には使えない)。
Python での実現(用語と関数の対応)
色空間と色表現
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| RGB 色空間 | cv2.cvtColor(image, cv2.COLOR_BGR2RGB) | BGR から RGB へ変換する |
| HSV 色空間 | cv2.cvtColor(image, cv2.COLOR_BGR2HSV) | BGR から HSV へ変換する |
| グレースケール | cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | カラーからグレースケールへ変換する |
| 色相 | hsv[:,:,0] | HSV 画像の H 成分を取得する |
| 彩度 | hsv[:,:,1] | HSV 画像の S 成分を取得する |
| 明度 | hsv[:,:,2] | HSV 画像の V 成分を取得する |
画質改善技術
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| ヒストグラム均等化 | cv2.equalizeHist(gray) | グレースケール画像のヒストグラムを均等化する |
| CLAHE | cv2.createCLAHE(clipLimit, tileGridSize).apply(gray) | 適応的ヒストグラム均等化を適用する |
| ガンマ補正 | np.power(image/255.0, gamma) * 255 | 非線形輝度変換を行う |
| コントラスト調整 | cv2.convertScaleAbs(image, alpha=alpha, beta=beta) | 線形変換でコントラストを調整する |
| 彩度調整 | HSV 空間の S 成分を乗算 | 彩度を増減する |
ノイズ評価と除去
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| ガウシアンノイズ生成 | np.random.normal(mean, std, image.shape) | 正規分布ノイズを生成する |
| ソルト&ペッパーノイズ生成 | np.random.rand() で確率的に生成 | 白黒点ノイズを生成する |
| ノイズ分散 | np.var(noise) | ノイズの分散を計算する |
| SNR | 10 * np.log10(signal_power / noise_power) | 信号対雑音比を計算する |
| ガウシアンフィルタ | cv2.GaussianBlur(image, ksize, sigma) | ガウス平滑化を行う |
| メディアンフィルタ | cv2.medianBlur(image, ksize) | 中央値フィルタを適用する(ksize は正の奇数) |
| バイラテラルフィルタ | cv2.bilateralFilter(image, d, sigmaColor, sigmaSpace) | エッジ保存平滑化を行う |
畳み込み演算
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| カーネル定義 | np.array([[...]], np.float32) | フィルタ行列を定義する |
| 畳み込み処理 | cv2.filter2D(image, ddepth, kernel) | カーネルを適用する。ddepth に -1 を指定すると入力と同じ深度(8bit)になり、結果の負値は 0 にクリップされる。負値を保持するには cv2.CV_64F を指定する |
| 平滑化カーネル | np.ones((k, k), np.float32) / (k**2) | 平均化フィルタを定義する |
| 鮮鋭化カーネル | 中心を強調するカーネル | エッジを強調する |
| エッジ検出カーネル | Sobel カーネルなど | 勾配を計算する |
エッジ解析
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| Sobel フィルタ | cv2.Sobel(image, cv2.CV_64F, dx, dy, ksize) | 勾配を計算する(符号保持のため cv2.CV_64F を使用) |
| Laplacian フィルタ | cv2.Laplacian(image, cv2.CV_64F) | 2次微分でエッジを検出する(表示時は cv2.convertScaleAbs) |
| Canny エッジ検出 | cv2.Canny(image, threshold1, threshold2) | 多段階エッジ検出を行う |
| エッジ強度 | cv2.magnitude(gx, gy) | 勾配の大きさを計算する |
| エッジ方向 | np.arctan2(gy, gx) | 勾配の方向を計算する |
画像変換
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| リサイズ | cv2.resize(image, dsize, interpolation=...) | サイズを変更する。dsize は(幅, 高さ)の順で、image.shape の(高さ, 幅)とは順序が逆である |
| 最近傍補間 | interpolation=cv2.INTER_NEAREST | 最近傍補間でリサイズする |
| バイリニア補間 | interpolation=cv2.INTER_LINEAR | 線形補間でリサイズする |
| バイキュービック補間 | interpolation=cv2.INTER_CUBIC | 3次補間でリサイズする |
| 回転 | cv2.getRotationMatrix2D() + cv2.warpAffine() | 回転変換を行う |
| 反転 | cv2.flip(image, flipCode) | 画像を反転する |
| トリミング | image[y1:y2, x1:x2] | NumPy スライシングで切り出す |
画像評価
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| PSNR | cv2.PSNR(image1, image2) | ピーク信号対雑音比を計算する(8bit・同一サイズの画像同士) |
| MSE | np.mean((image1.astype(float) - image2.astype(float))**2) | 平均二乗誤差を計算する |
| ヒストグラム比較 | cv2.compareHist(hist1, hist2, method) | ヒストグラム類似度を計算する |
| ブラー検出 | cv2.Laplacian(gray, cv2.CV_64F).var() | ぼやけ度を評価する |
Python 開発環境、ライブラリ類
本資料は機械学習・深層学習を含まないため、NVIDIA CUDA や Visual Studio は不要である。GPU 非搭載の Windows パソコンで動作する。Python の編集・実行には AI エディタ(Windsurf、Cursor など)または Google Colaboratory を利用できる。詳細は別ページ https://www.kkaneko.jp/cc/dev/aiassist.html を参照する。
Python 3.12 のインストール(Windows 上)
以下のいずれかの方法で Python 3.12 をインストールする。Python がインストール済みの場合、この手順は不要である。
方法1:winget によるインストール
管理者権限のコマンドプロンプトで以下を実行する。管理者権限のコマンドプロンプトは、Windows キーまたはスタートメニューから「cmd」と入力し、表示された「コマンドプロンプト」を右クリックして「管理者として実行」を選択して起動する。
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements
インストール完了後、コマンドプロンプトを再起動すると PATH が反映される。
方法2:インストーラーによるインストール
- Python 公式サイト(https://www.python.org/downloads/)から Windows 用インストーラーをダウンロードする。
- インストーラーを実行し、初期画面下部の「Add python.exe to PATH」にチェックを入れる(チェックを入れないと、コマンドプロンプトから
pythonコマンドを実行できない)。 - 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。
インストールの確認
コマンドプロンプトで以下を実行する。
python --version
バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。
AIエディタ Windsurf のインストール(Windows 上)
Python プログラムの編集・実行には、AI エディタの利用を推奨する。ここでは、Windsurf のインストールを説明する。Windsurf がインストール済みの場合、この手順は不要である。
管理者権限のコマンドプロンプトで以下を実行する。管理者権限のコマンドプロンプトは、Windows キーまたはスタートメニューから「cmd」と入力し、表示された「コマンドプロンプト」を右クリックして「管理者として実行」を選択して起動する。
winget install --scope machine --id Codeium.Windsurf -e --silent --accept-source-agreements --accept-package-agreements
インストール完了後、コマンドプロンプトを再起動すると PATH が反映される。
【関連する外部ページ】Windsurf の公式ページ: https://windsurf.com/
OpenCV(cv2)、NumPy、Matplotlib のインストール
管理者権限のコマンドプロンプトで以下を実行する。管理者権限のコマンドプロンプトは、Windows キーまたはスタートメニューから「cmd」と入力し、表示された「コマンドプロンプト」を右クリックして「管理者として実行」を選択して起動する。
pip install opencv-python numpy matplotlib
本資料の関数はすべて opencv-python(contrib なし)で利用できる。現行の opencv-python は NumPy 2.x に対応しており、本資料のコードは NumPy 1.x / 2.x のいずれでも動作する。
演習
各演習を実行する前に、次の前提条件を確認する。
依存ライブラリ:
import cv2、import numpy as np、import matplotlib.pyplot as pltが実行できる環境を用意する(いずれも Windows・CPU のみで動作する)。表示時の色順:OpenCV の画像は BGR の順である。Matplotlib で表示する場合は
cv2.cvtColor(image, cv2.COLOR_BGR2RGB)で RGB に変換する。評価指標の入力条件:PSNR・MSE などの比較指標は、同一サイズの画像同士を比較する。
テスト画像:各演習は画像ファイルを用意しなくても動くよう、プログラム内でテスト画像を生成する。
【フィルタリングと変換に共通する注意】
- エッジ処理はグレースケールに対して行う:勾配・エッジ検出はカラーをグレースケールに変換してから適用する。カラーのままチャンネルごとに処理すると結果が一致しない。
- cv2.resize の dsize は(幅, 高さ)の順:
image.shapeの(高さ, 幅)とは順序が逆である。 - 勾配・2次微分の結果は安全に8bit化する:負値や 255 を超える値を含むため、表示前に
cv2.convertScaleAbs(絶対値化+8bit化)またはcv2.normalize(..., 0, 255, cv2.NORM_MINMAX)を用いる。 - 一部の評価値は相対指標である:ブラー検出スコアや正規化したエッジ強度は画像ごとに基準が変わるため、異なる画像間の絶対比較には使えない。
演習1.画質改善処理
テーマ:明るさとコントラストを改善する5手法(ヒストグラム均等化、CLAHE、ガンマ補正、コントラスト調整、彩度調整)を適用し、結果を比較する。
手順
- 以下のコードをファイル(例:
ex1.py)として保存する。 - コマンドプロンプトで
python ex1.pyを実行する。 - 表示されるウィンドウで、各手法の出力とヒストグラムの変化を確認する。
import cv2
import numpy as np
import matplotlib.pyplot as plt
def create_dark_image():
image = np.zeros((300, 400, 3), dtype=np.uint8)
cv2.rectangle(image, (50, 50), (150, 150), (60, 60, 60), -1)
cv2.circle(image, (300, 100), 50, (80, 80, 80), -1)
cv2.rectangle(image, (100, 200), (300, 280), (40, 40, 40), -1)
return image
def create_color_image():
image = np.full((300, 400, 3), 128, dtype=np.uint8)
cv2.rectangle(image, (50, 50), (150, 150), (200, 100, 100), -1)
cv2.circle(image, (300, 100), 50, (100, 200, 100), -1)
cv2.rectangle(image, (100, 200), (300, 280), (100, 100, 200), -1)
return image
def adjust_saturation(image, scale):
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV).astype(np.float32)
hsv[:, :, 1] = np.clip(hsv[:, :, 1] * scale, 0, 255) # uint8 のままだと 255 超でラップアラウンドするため float で計算
return cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
dark = create_dark_image()
gray = cv2.cvtColor(dark, cv2.COLOR_BGR2GRAY)
eq = cv2.equalizeHist(gray)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)).apply(gray)
gamma = (np.power(dark / 255.0, 0.5) * 255).astype(np.uint8)
contrast = cv2.convertScaleAbs(dark, alpha=1.5, beta=30)
color = create_color_image()
sat_high = adjust_saturation(color, 1.5)
sat_low = adjust_saturation(color, 0.5)
plt.figure(figsize=(15, 10))
plt.subplot(3, 3, 1); plt.imshow(cv2.cvtColor(dark, cv2.COLOR_BGR2RGB)); plt.title('Original (Dark)'); plt.axis('off')
plt.subplot(3, 3, 2); plt.imshow(eq, cmap='gray'); plt.title('Histogram Equalization'); plt.axis('off')
plt.subplot(3, 3, 3); plt.imshow(clahe, cmap='gray'); plt.title('CLAHE'); plt.axis('off')
plt.subplot(3, 3, 4); plt.imshow(cv2.cvtColor(gamma, cv2.COLOR_BGR2RGB)); plt.title('Gamma (gamma=0.5)'); plt.axis('off')
plt.subplot(3, 3, 5); plt.imshow(cv2.cvtColor(contrast, cv2.COLOR_BGR2RGB)); plt.title('Contrast'); plt.axis('off')
plt.subplot(3, 3, 6)
plt.hist(gray.ravel(), bins=256, range=[0, 256], alpha=0.5, label='Original')
plt.hist(eq.ravel(), bins=256, range=[0, 256], alpha=0.5, label='Equalized')
plt.title('Histogram'); plt.legend()
plt.subplot(3, 3, 7); plt.imshow(cv2.cvtColor(color, cv2.COLOR_BGR2RGB)); plt.title('Original (Color)'); plt.axis('off')
plt.subplot(3, 3, 8); plt.imshow(cv2.cvtColor(sat_high, cv2.COLOR_BGR2RGB)); plt.title('High Saturation'); plt.axis('off')
plt.subplot(3, 3, 9); plt.imshow(cv2.cvtColor(sat_low, cv2.COLOR_BGR2RGB)); plt.title('Low Saturation'); plt.axis('off')
plt.tight_layout()
plt.show()

ヒント
- ヒストグラム均等化と CLAHE はグレースケール画像を入力とするため、出力は単一チャンネルになる(表示時に
cmap='gray'を指定する)。 - ガンマ補正の γ を 0.5 から 2.0 に変えると、明るくなる方向から暗くなる方向に変化する。
- 彩度調整の倍率を 1.5 と 0.5 で比較する。
考察ポイント
- ヒストグラム均等化と CLAHE で、暗い領域の見え方にどのような差が出るか。
- ヒストグラムが均等化の前後でどのように変化したか。
演習2.ノイズ量の評価
テーマ:2種類のノイズを付加し、SNR とノイズ分散で量を評価する。ノイズの種類に応じたフィルタを適用し、PSNR で除去効果を比較する。
手順
- 以下のコードをファイル(例:
ex2.py)として保存する。 - コマンドプロンプトで
python ex2.pyを実行する。 - 各ノイズ画像とフィルタ後の画像、評価値を確認する。
import cv2
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0) # 実行ごとに結果を再現するため乱数を固定する
def create_color_image():
image = np.full((300, 400, 3), 128, dtype=np.uint8)
cv2.rectangle(image, (50, 50), (150, 150), (200, 100, 100), -1)
cv2.circle(image, (300, 100), 50, (100, 200, 100), -1)
cv2.rectangle(image, (100, 200), (300, 280), (100, 100, 200), -1)
return image
def add_gaussian_noise(image, std=25):
noise = np.random.normal(0, std, image.shape)
return np.clip(image.astype(float) + noise, 0, 255).astype(np.uint8)
def add_salt_pepper_noise(image, prob=0.05):
noisy = image.copy()
rnd = np.random.rand(image.shape[0], image.shape[1])
noisy[rnd < prob / 2] = [255, 255, 255]
noisy[rnd > 1 - prob / 2] = [0, 0, 0]
return noisy
def snr(original, noisy):
o = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY).astype(float)
n = cv2.cvtColor(noisy, cv2.COLOR_BGR2GRAY).astype(float)
noise_power = np.mean((o - n) ** 2)
return 10 * np.log10(np.mean(o ** 2) / noise_power)
def noise_variance(original, noisy):
o = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY).astype(float)
n = cv2.cvtColor(noisy, cv2.COLOR_BGR2GRAY).astype(float)
return np.var(o - n)
original = create_color_image()
gaussian_noisy = add_gaussian_noise(original, std=25)
sp_noisy = add_salt_pepper_noise(original, prob=0.05)
# d=近傍直径, sigmaColor=色の類似度の許容幅, sigmaSpace=空間的な広がり
gaussian_filtered = cv2.GaussianBlur(gaussian_noisy, (5, 5), 0)
sp_filtered = cv2.medianBlur(sp_noisy, 5)
bilateral_filtered = cv2.bilateralFilter(gaussian_noisy, 9, 75, 75)
plt.figure(figsize=(15, 12))
plt.subplot(3, 3, 1); plt.imshow(cv2.cvtColor(original, cv2.COLOR_BGR2RGB)); plt.title('Original'); plt.axis('off')
plt.subplot(3, 3, 2); plt.imshow(cv2.cvtColor(gaussian_noisy, cv2.COLOR_BGR2RGB))
plt.title('Gaussian Noise\nSNR:%.2fdB Var:%.1f' % (snr(original, gaussian_noisy), noise_variance(original, gaussian_noisy))); plt.axis('off')
plt.subplot(3, 3, 3); plt.imshow(cv2.cvtColor(gaussian_filtered, cv2.COLOR_BGR2RGB))
plt.title('Gaussian Filtered\nPSNR:%.2fdB' % cv2.PSNR(original, gaussian_filtered)); plt.axis('off')
plt.subplot(3, 3, 5); plt.imshow(cv2.cvtColor(sp_noisy, cv2.COLOR_BGR2RGB))
plt.title('Salt & Pepper\nSNR:%.2fdB Var:%.1f' % (snr(original, sp_noisy), noise_variance(original, sp_noisy))); plt.axis('off')
plt.subplot(3, 3, 6); plt.imshow(cv2.cvtColor(sp_filtered, cv2.COLOR_BGR2RGB))
plt.title('Median Filtered\nPSNR:%.2fdB' % cv2.PSNR(original, sp_filtered)); plt.axis('off')
plt.subplot(3, 3, 8); plt.imshow(cv2.cvtColor(bilateral_filtered, cv2.COLOR_BGR2RGB))
plt.title('Bilateral Filtered\nPSNR:%.2fdB' % cv2.PSNR(original, bilateral_filtered)); plt.axis('off')
plt.tight_layout()
plt.show()

ヒント
- ガウシアンノイズにはガウシアンフィルタまたはバイラテラルフィルタ、ソルト&ペッパーノイズにはメディアンフィルタを用いる。
- SNR は値が大きいほど、PSNR は値が大きいほど品質が高い。
- ガウシアンノイズの
stdや、ソルト&ペッパーノイズのprobを変えると、評価値が変化する。
考察ポイント
- 各ノイズに対し、種類に合ったフィルタと合わないフィルタで PSNR がどう変わるか。
- バイラテラルフィルタは、ガウシアンフィルタと比べてエッジの保存にどのような差が出るか。
演習3.畳み込みフィルタの適用
テーマ:自作の畳み込みカーネルを画像に適用し、平滑化・鮮鋭化・エッジ検出の効果を比較する。
手順
- 以下のコードをファイル(例:
ex3.py)として保存する。 - コマンドプロンプトで
python ex3.pyを実行する。 - 各カーネルの出力を確認する。
import cv2
import numpy as np
import matplotlib.pyplot as plt
def create_shape_image():
image = np.full((300, 400, 3), 200, dtype=np.uint8)
cv2.rectangle(image, (50, 50), (150, 150), (100, 100, 100), -1)
cv2.circle(image, (300, 100), 50, (120, 120, 120), -1)
pts = np.array([[100, 200], [200, 200], [150, 280]], np.int32)
cv2.fillPoly(image, [pts], (130, 130, 130))
return image
def apply_edge_kernel(gray, kernel):
return cv2.convertScaleAbs(cv2.filter2D(gray, cv2.CV_64F, kernel)) # 負値・255超を絶対値化して8bitに収める
image = create_shape_image()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur_kernel = np.ones((3, 3), np.float32) / 9
sharpen_kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)
edge_h_kernel = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], np.float32)
edge_v_kernel = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], np.float32)
edge_enh_kernel = np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], np.float32)
blurred = cv2.filter2D(image, -1, blur_kernel)
sharpened = cv2.filter2D(image, -1, sharpen_kernel)
edge_h = apply_edge_kernel(gray, edge_h_kernel)
edge_v = apply_edge_kernel(gray, edge_v_kernel)
edge_enhanced = apply_edge_kernel(gray, edge_enh_kernel)
plt.figure(figsize=(15, 10))
plt.subplot(2, 3, 1); plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)); plt.title('Original'); plt.axis('off')
plt.subplot(2, 3, 2); plt.imshow(cv2.cvtColor(blurred, cv2.COLOR_BGR2RGB)); plt.title('Blur'); plt.axis('off')
plt.subplot(2, 3, 3); plt.imshow(cv2.cvtColor(sharpened, cv2.COLOR_BGR2RGB)); plt.title('Sharpen'); plt.axis('off')
plt.subplot(2, 3, 4); plt.imshow(edge_h, cmap='gray'); plt.title('Edge (Horizontal)'); plt.axis('off')
plt.subplot(2, 3, 5); plt.imshow(edge_v, cmap='gray'); plt.title('Edge (Vertical)'); plt.axis('off')
plt.subplot(2, 3, 6); plt.imshow(edge_enhanced, cmap='gray'); plt.title('Edge Enhancement'); plt.axis('off')
plt.tight_layout()
plt.show()

ヒント
- 平滑化カーネルは全要素の和が 1 になるよう正規化している(和が 1 でないと全体の輝度が変わる)。
- 水平方向のエッジ検出カーネルと垂直方向のカーネルで、検出されるエッジの向きが異なる。
- 画像端部の処理(パディング)は、OpenCV では既定で鏡映が用いられる。
考察ポイント
- 平滑化と鮮鋭化で、図形の境界の見え方がどう変わるか。
- 水平方向と垂直方向のエッジ検出で、強く出るエッジの向きの違い。
演習4.エッジの解析
テーマ:Sobel、Laplacian、Canny の3手法でエッジを検出し、エッジ強度と方向を解析する。
手順
- 以下のコードをファイル(例:
ex4.py)として保存する。 - コマンドプロンプトで
python ex4.pyを実行する。 - 各手法の出力と、エッジ強度のヒストグラムを確認する。
import cv2
import numpy as np
import matplotlib.pyplot as plt
def create_shape_image():
image = np.full((300, 400, 3), 200, dtype=np.uint8)
cv2.rectangle(image, (50, 50), (150, 150), (100, 100, 100), -1)
cv2.circle(image, (300, 100), 50, (120, 120, 120), -1)
cv2.line(image, (250, 200), (350, 280), (80, 80, 80), 3)
return image
image = create_shape_image()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
magnitude = cv2.magnitude(sobel_x, sobel_y)
magnitude = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) # 画像ごとの最大値で 0〜255 に正規化
direction = np.arctan2(sobel_y, sobel_x)
# threshold1=低閾値, threshold2=高閾値(一般に高:低=2:1〜3:1 を目安にする)
canny_edges = cv2.Canny(cv2.GaussianBlur(gray, (5, 5), 1.4), 50, 150)
laplacian_edges = cv2.convertScaleAbs(cv2.Laplacian(gray, cv2.CV_64F))
blur_score = cv2.Laplacian(gray, cv2.CV_64F).var()
plt.figure(figsize=(15, 10))
plt.subplot(3, 3, 1); plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)); plt.title('Original'); plt.axis('off')
plt.subplot(3, 3, 2); plt.imshow(magnitude, cmap='gray'); plt.title('Sobel Magnitude'); plt.axis('off')
ax = plt.subplot(3, 3, 3) # カラーバーを付ける軸では axis('off') を使わず目盛りを消す
im = ax.imshow(direction, cmap='hsv')
ax.set_title('Sobel Direction'); ax.set_xticks([]); ax.set_yticks([])
plt.colorbar(im, ax=ax, label='Angle (radians)', fraction=0.046)
plt.subplot(3, 3, 4); plt.imshow(canny_edges, cmap='gray'); plt.title('Canny Edges'); plt.axis('off')
plt.subplot(3, 3, 5); plt.imshow(cv2.convertScaleAbs(sobel_x), cmap='gray'); plt.title('Sobel X'); plt.axis('off')
plt.subplot(3, 3, 6); plt.imshow(cv2.convertScaleAbs(sobel_y), cmap='gray'); plt.title('Sobel Y'); plt.axis('off')
plt.subplot(3, 3, 7); plt.imshow(laplacian_edges, cmap='gray'); plt.title('Laplacian'); plt.axis('off')
plt.subplot(3, 3, 8); plt.hist(magnitude.ravel(), bins=50, color='blue', alpha=0.7); plt.title('Edge Strength Histogram')
plt.subplot(3, 3, 9); plt.text(0.1, 0.5, 'Blur Score:\n%.2f\n(Higher = Sharper)' % blur_score, fontsize=14, va='center'); plt.axis('off')
plt.tight_layout()
plt.show()


ヒント
- Sobel と Laplacian の結果は負値や 255 超を含むため、表示前に8bit化している。
- Sobel X は垂直方向のエッジ、Sobel Y は水平方向のエッジに強く反応する。
- Canny の2つの閾値(低閾値・高閾値)はヒステリシス処理の下限・上限である。値を変えると検出されるエッジの量が変化する。GaussianBlur は前処理で、ノイズが強い画像で有効である。
- ブラー検出スコアは値が大きいほど鮮明だが、同一条件下の比較にのみ用いる。
考察ポイント
- Sobel、Laplacian、Canny で、検出されるエッジの太さや連続性の違い。
- Canny の閾値を変えると、検出されるエッジの量がどう変化するか。
演習5.リサイズと補間
テーマ:3種類の補間手法でリサイズし、結果を比較する。回転・反転・トリミングも併せて確認する。
手順
- 以下のコードをファイル(例:
ex5.py)として保存する。 - コマンドプロンプトで
python ex5.pyを実行する。 - 拡大・縮小・回転・反転・トリミングの結果を確認する。
import cv2
import numpy as np
import matplotlib.pyplot as plt
def create_grid_image():
image = np.full((200, 200, 3), 200, dtype=np.uint8)
for i in range(0, 200, 20):
cv2.line(image, (i, 0), (i, 200), (100, 100, 100), 2)
cv2.line(image, (0, i), (200, i), (100, 100, 100), 2)
cv2.circle(image, (100, 100), 50, (150, 100, 100), -1)
cv2.rectangle(image, (50, 50), (150, 150), (100, 150, 100), 3)
return image
def hist_correlation(image1, image2):
g1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
h1 = cv2.calcHist([g1], [0], None, [256], [0, 256])
h2 = cv2.calcHist([g2], [0], None, [256], [0, 256])
return cv2.compareHist(h1, h2, cv2.HISTCMP_CORREL)
image = create_grid_image()
h, w = image.shape[:2]
up = (w * 2, h * 2) # dsize は(幅, 高さ)の順
down = (w // 2, h // 2)
nearest_up = cv2.resize(image, up, interpolation=cv2.INTER_NEAREST)
linear_up = cv2.resize(image, up, interpolation=cv2.INTER_LINEAR)
cubic_up = cv2.resize(image, up, interpolation=cv2.INTER_CUBIC)
nearest_down = cv2.resize(image, down, interpolation=cv2.INTER_NEAREST)
linear_down = cv2.resize(image, down, interpolation=cv2.INTER_LINEAR)
cubic_down = cv2.resize(image, down, interpolation=cv2.INTER_CUBIC)
matrix = cv2.getRotationMatrix2D((w // 2, h // 2), 45, 1.0)
rotated = cv2.warpAffine(image, matrix, (w, h)) # 回転後の画像外領域は既定で黒に埋められる
h_flip = cv2.flip(image, 1)
v_flip = cv2.flip(image, 0)
cropped = image[50:150, 50:150]
corr_n = hist_correlation(image, cv2.resize(nearest_down, (w, h)))
corr_l = hist_correlation(image, cv2.resize(linear_down, (w, h)))
corr_c = hist_correlation(image, cv2.resize(cubic_down, (w, h)))
plt.figure(figsize=(20, 12))
plt.subplot(3, 5, 1); plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)); plt.title('Original'); plt.axis('off')
plt.subplot(3, 5, 2); plt.imshow(cv2.cvtColor(nearest_up, cv2.COLOR_BGR2RGB)); plt.title('2x Nearest'); plt.axis('off')
plt.subplot(3, 5, 3); plt.imshow(cv2.cvtColor(linear_up, cv2.COLOR_BGR2RGB)); plt.title('2x Bilinear'); plt.axis('off')
plt.subplot(3, 5, 4); plt.imshow(cv2.cvtColor(cubic_up, cv2.COLOR_BGR2RGB)); plt.title('2x Bicubic'); plt.axis('off')
plt.subplot(3, 5, 6); plt.imshow(cv2.cvtColor(nearest_down, cv2.COLOR_BGR2RGB)); plt.title('0.5x Nearest\nCorr:%.3f' % corr_n); plt.axis('off')
plt.subplot(3, 5, 7); plt.imshow(cv2.cvtColor(linear_down, cv2.COLOR_BGR2RGB)); plt.title('0.5x Bilinear\nCorr:%.3f' % corr_l); plt.axis('off')
plt.subplot(3, 5, 8); plt.imshow(cv2.cvtColor(cubic_down, cv2.COLOR_BGR2RGB)); plt.title('0.5x Bicubic\nCorr:%.3f' % corr_c); plt.axis('off')
plt.subplot(3, 5, 11); plt.imshow(cv2.cvtColor(rotated, cv2.COLOR_BGR2RGB)); plt.title('Rotated 45deg'); plt.axis('off')
plt.subplot(3, 5, 12); plt.imshow(cv2.cvtColor(h_flip, cv2.COLOR_BGR2RGB)); plt.title('Horizontal Flip'); plt.axis('off')
plt.subplot(3, 5, 13); plt.imshow(cv2.cvtColor(v_flip, cv2.COLOR_BGR2RGB)); plt.title('Vertical Flip'); plt.axis('off')
plt.subplot(3, 5, 14); plt.imshow(cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB)); plt.title('Cropped'); plt.axis('off')
plt.tight_layout()
plt.show()

ヒント
- 拡大時、最近傍補間はジャギーが目立ち、バイキュービック補間は滑らかになる。
- 縮小と拡大を行っても元画像は復元されない(縮小で高周波成分が失われるため)。
- ヒストグラム相関は画素の空間配置を評価しないため、補間手法による差は小さく、優劣の判定には見た目の確認が必要である。
考察ポイント
- 拡大時、3手法でグリッド線や円の輪郭の滑らかさにどのような差が出るか。
- ヒストグラム相関の値だけで補間手法の優劣を判断できるか。