カラー画像処理の Python 実現ガイド
【概要】カラー画像処理の用語(40項目)とPython実装を示す。用語は画像の基本概念、色空間と色表現、画質改善技術、ノイズ評価と除去、畳み込み演算、エッジ解析、画像変換、画像評価に分類され、対応するPython関数を明示する。実装例として、画質改善処理、ノイズ量の評価、畳み込みフィルタの適用、エッジの解析、リサイズと補間の5つのプログラムを提供する。
【目次】
【サイト内の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を用いた画像処理の実装を対象とし、プログラミングの基礎知識を前提とする。
カラー画像処理で使用する基本概念を示す。各概念の詳細は「用語リスト」を参照されたい。
ピクセル(画素):デジタル画像を構成する最小単位である。各ピクセルは位置座標と色情報を持つ。カラー画像では通常、赤(R)、緑(G)、青(B)の3つの値で色を表現する。
解像度:画像の精細さを表す指標であり、幅と高さのピクセル数で表現される。解像度が高いほど詳細な情報を保持できるが、データ量は増加する。
色空間:色を数値で表現するための座標系である。RGB色空間はディスプレイ表示に、HSV色空間は色相・彩度・明度に基づく処理に用いられる。
チャンネル:画像の各色成分を表す配列である。RGB画像は3チャンネル、グレースケール画像は1チャンネルを持つ。各チャンネルは独立した2次元配列として処理できる。
畳み込み演算:画像処理における基本演算である。小さなフィルタ(カーネル)を画像全体に適用し、各ピクセルとその近傍ピクセルの重み付き和を計算する。ぼかし、シャープ化、エッジ検出に使用される。
ノイズ:画像に含まれる不要な変動成分である。撮影時のセンサーノイズ、圧縮ノイズ、伝送エラーなどが原因となる。ガウシアンノイズやソルト&ペッパーノイズなどの種類がある。
エッジ:画像内で輝度や色が急変する境界である。物体の輪郭や表面の不連続点に対応する。エッジ検出は物体認識や領域分割の基礎となる。
ヒストグラム:画像内の各輝度値または色値の出現頻度を表すグラフである。画像の明るさ分布や色分布を可視化し、画質評価やコントラスト調整に利用する。
補間:画像のリサイズや回転時に、新しいピクセル位置の値を周囲のピクセルから推定する処理である。最近傍補間、バイリニア補間、バイキュービック補間などの手法がある。
フィルタ:画像の特定の性質を強調または抑制する処理である。平滑化フィルタはノイズを低減し、鮮鋭化フィルタは細部を強調する。
用語リスト
画像の基本概念
画像サイズ:画像の幅と高さをピクセル単位で表した値である。640×480(VGA)、1920×1080(Full HD)、3840×2160(4K)などの形式がある。RGB画像は(高さ, 幅, 3)の形状を持つ。
ビット深度:各色チャンネルを表現するビット数である。8ビット(0-255の256階調)が一般的であり、ビット深度が大きいほど滑らかな階調表現が可能となる。
アスペクト比:画像の幅と高さの比率である。16:9、4:3、1:1などがある。リサイズ時にアスペクト比を維持しないと画像が歪む。
色空間と色表現
RGB色空間:赤(Red)、緑(Green)、青(Blue)の3原色の加法混色により色を表現する色空間である。各成分は0-255の整数値で表現され、ディスプレイやカメラで広く用いられる。
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は画像を分割するタイルの数であり、値が小さいほど局所的な処理となる。
ガンマ補正:画像の輝度を非線形に変換する処理である。変換式は Iout = Iinγ である。γ < 1で画像が明るくなり、γ > 1で暗くなる。ディスプレイの特性補正や明るさ調整に使用される。
コントラスト調整:画像の明暗差を変更する処理である。線形変換 Iout = α × Iin + β により実現される。αはコントラスト係数(1より大きいとコントラスト増加)、βは明度オフセット(正で明るく、負で暗くなる)である。
ノイズ評価と除去
ガウシアンノイズ:正規分布に従うランダムなノイズである。撮影時のセンサーノイズとして現れる。平均値と標準偏差により特徴づけられる。
ソルト&ペッパーノイズ:ランダムに白点(ソルト)と黒点(ペッパー)が現れるノイズである。伝送エラーやセンサーの不良画素により発生する。
ノイズ分散:ノイズの強さを表す統計量である。局所領域の輝度値の分散により推定される。値が大きいほどノイズが強い。
SNR(信号対雑音比):信号の強度とノイズの強度の比である。単位はdBで、計算式は SNR = 10 log10 (Psignal / Pnoise) である。値が大きいほど信号品質が高い。
ガウシアンフィルタ:ガウス分布に基づく重み付けを行う平滑化フィルタである。ガウシアンノイズの除去に有効だが、エッジがぼやける副作用がある。標準偏差パラメータでぼかしの強さを制御する。
メディアンフィルタ:近傍ピクセルの中央値を出力する非線形フィルタである。ソルト&ペッパーノイズの除去に有効で、エッジを保持しやすい。
バイラテラルフィルタ:空間的な近さと色の類似性の両方を考慮する非線形フィルタである。エッジを保存しながら平滑化を行うため、ノイズ除去と詳細保存を両立できる。計算コストは高い。
畳み込み演算
カーネル(フィルタ):畳み込み演算で使用する小さな行列である。3×3、5×5などのサイズが用いられる。カーネルの値により、平滑化、鮮鋭化、エッジ検出などの処理を実現できる。
畳み込み処理:カーネルを画像全体に適用し、各位置で重み付き和を計算する処理である。多くのフィルタ処理の基礎となる。
平滑化カーネル:周囲のピクセル値を平均化するカーネルである。画像をぼかしてノイズを低減する。全要素が等しい値を持つ形式が一般的である。
鮮鋭化カーネル:中心ピクセルを強調し、周囲を減算するカーネルである。画像のエッジや細部を強調する。ぼやけた画像の改善に有効である。
エッジ検出カーネル:輝度の勾配を計算するカーネルである。Sobelカーネルなどがあり、水平方向や垂直方向のエッジを検出する。
エッジ解析
勾配:画像の輝度変化の大きさと方向を表す量である。勾配が大きい場所がエッジに対応する。X方向とY方向の偏微分により計算される。
Sobelフィルタ:画像の勾配を計算してエッジを検出するフィルタである。水平方向と垂直方向の微分を近似する3×3のカーネルを使用する。実装が容易で広く用いられる。
Laplacianフィルタ:2次微分によりエッジを検出するフィルタである。全方向のエッジを一度に検出できるが、ノイズに敏感である。事前にガウシアンフィルタでノイズを低減してから適用する。
Cannyエッジ検出:多段階アルゴリズムによるエッジ検出手法である。ノイズ除去、勾配計算、非極大値抑制(エッジの細線化)、ヒステリシス閾値処理(2つの閾値による連結エッジの抽出)を順に実行する。ノイズに強い。
エッジ強度:各ピクセルでのエッジの強さを表す値である。√(Gx2 + Gy2) で計算される。
エッジ方向:エッジの向きを表す角度である。勾配ベクトルの方向に対応し、arctan(Gy / Gx) で計算される。
画像変換
リサイズ:画像のサイズを変更する処理である。拡大または縮小が可能であり、Webページ用の画像縮小や印刷用の画像拡大に使用される。
最近傍補間:最も近いピクセルの値をそのまま使用する補間手法である。計算が高速だが、ジャギー(階段状のギザギザ)が発生しやすい。マスク画像やラベル画像など、ピクセル値の混合を避けたい場合に使用する。
バイリニア補間:周囲4ピクセルの値を線形補間する手法である。最近傍補間より滑らかな結果が得られる。計算コストと画質のバランスが良い。
バイキュービック補間:周囲16ピクセルの値を3次関数で補間する手法である。高品質な結果が得られるが計算コストが高い。画質を重視する用途に適する。
回転:画像を指定角度だけ回転させる処理である。回転中心と角度を指定する。画像の向き補正やデータ拡張に使用される。
反転:画像を水平または垂直に反転させる処理である。鏡像を作成する。データ拡張や画像の向き調整に使用される。
画像評価
PSNR(ピーク信号対雑音比):画像の品質を評価する客観的指標である。元画像と処理後画像の平均二乗誤差から計算され、単位はdBである。値が大きいほど品質が高く、30dB以上で良好とされる。
MSE(平均二乗誤差):2つの画像の差の二乗の平均である。値が小さいほど類似度が高い。計算式は MSE = (1/N) Σ (I1 - I2)2 である。PSNRの計算に使用される。
ヒストグラム比較:2つの画像のヒストグラムの類似度を測定する手法である。相関、カイ二乗、交差、バタチャリヤ距離(確率分布間の類似度指標)などの方法があり、画像検索や類似画像判定に応用される。
ブラー検出:画像のぼやけ度合いを定量化する処理である。Laplacian分散により評価され、値が小さいほどぼやけている。ピンぼけ画像の自動検出に使用される。
Pythonでの実現
カラー画像処理の用語と関数の対応表を示す。各関数の使用方法は「Pythonプログラム例」を参照されたい。
画像の基本概念
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| 画像サイズ | image.shape |
(高さ, 幅, チャンネル数)のタプルを返す |
| ビット深度 | image.dtype |
データ型を返す(uint8, uint16など) |
| データ型変換 | image.astype(dtype) |
データ型を変換する |
| アスペクト比 | width / height |
幅と高さの比率を計算する |
色空間と色表現
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| 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) |
線形変換でコントラストを調整する |
| 明度調整 | cv2.add(image, value) |
全ピクセルに値を加算する |
| 彩度調整 | 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) |
中央値フィルタを適用する |
| バイラテラルフィルタ | cv2.bilateralFilter(image, d, sigmaColor, sigmaSpace) |
エッジ保存平滑化を行う |
畳み込み演算
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| カーネル定義 | np.array([[...], [...], [...]]) |
フィルタ行列を定義する |
| 畳み込み処理 | cv2.filter2D(image, -1, kernel) |
カスタムカーネルを適用する |
| 平滑化カーネル | np.ones((ksize, ksize)) / (ksize**2) |
平均化フィルタを定義する |
| 鮮鋭化カーネル | 中心を強調するカーネル | エッジを強調する |
| エッジ検出カーネル | Sobelカーネルなど | 勾配を計算する |
エッジ解析
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| 勾配 | cv2.Sobel() の結果 |
輝度変化の大きさと方向を得る |
| Sobelフィルタ | cv2.Sobel(image, ddepth, dx, dy, ksize) |
勾配を計算する |
| Laplacianフィルタ | cv2.Laplacian(image, ddepth) |
2次微分でエッジを検出する |
| Cannyエッジ検出 | cv2.Canny(image, threshold1, threshold2) |
多段階エッジ検出を行う |
| エッジ強度 | np.sqrt(gx**2 + gy**2) |
勾配の大きさを計算する |
| エッジ方向 | np.arctan2(gy, gx) |
勾配の方向を計算する |
画像変換
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| リサイズ | cv2.resize(image, dsize, interpolation) |
サイズを変更する |
| 最近傍補間 | cv2.resize(..., interpolation=cv2.INTER_NEAREST) |
最近傍補間でリサイズする |
| バイリニア補間 | cv2.resize(..., interpolation=cv2.INTER_LINEAR) |
線形補間でリサイズする |
| バイキュービック補間 | cv2.resize(..., interpolation=cv2.INTER_CUBIC) |
3次補間でリサイズする |
| 回転 | cv2.getRotationMatrix2D() + cv2.warpAffine() |
回転変換を行う |
| 反転 | cv2.flip(image, flipCode) |
画像を反転する |
| トリミング | image[y1:y2, x1:x2] |
NumPyスライシングで切り出す |
画像評価
| 用語 | 関数・メソッド | 説明 |
|---|---|---|
| PSNR | cv2.PSNR(image1, image2) |
ピーク信号対雑音比を計算する |
| MSE | np.mean((image1 - image2)**2) |
平均二乗誤差を計算する |
| ヒストグラム比較 | cv2.compareHist(hist1, hist2, method) |
ヒストグラム類似度を計算する |
| ブラー検出 | cv2.Laplacian(gray, cv2.CV_64F).var() |
ぼやけ度を評価する |
Python開発環境、ライブラリ類
ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどの追加インストールが便利である。詳細は別ページ https://www.kkaneko.jp/cc/dev/aiassist.html を参照されたい。
Python 3.12 のインストール
インストール済みの場合は実行不要である。
管理者権限でコマンドプロンプトを起動し(Windowsキーまたはスタートメニュー、cmdと入力、右クリック、「管理者として実行」)、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にインストールするために必要である。
REM Python 3.12 をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent --accept-source-agreements --accept-package-agreements
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
if exist "%PYTHON_PATH%" setx PYTHON_PATH "%PYTHON_PATH%" /M >nul
if exist "%PYTHON_SCRIPTS_PATH%" setx PYTHON_SCRIPTS_PATH "%PYTHON_SCRIPTS_PATH%" /M >nul
for /f "skip=2 tokens=2*" %a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path') do set "SYSTEM_PATH=%b"
echo "%SYSTEM_PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PYTHON_PATH%;%PYTHON_SCRIPTS_PATH%;%SYSTEM_PATH%" /M >nul
関連する外部ページ
Python の公式ページ: https://www.python.org/
AIエディタ Windsurf のインストール
Pythonプログラムの編集・実行には、AIエディタの利用を推奨する。ここでは、Windsurfのインストールを説明する。
管理者権限でコマンドプロンプトを起動し(Windowsキーまたはスタートメニュー、cmdと入力、右クリック、「管理者として実行」)、以下を実行する。
winget install --scope machine --id Codeium.Windsurf -e --silent --accept-source-agreements --accept-package-agreements
関連する外部ページ
Windsurf の公式ページ: https://windsurf.com/
OpenCV(cv2)、NumPy、Matplotlib のインストール
コマンドプロンプトを管理者として実行し(Windowsキーまたはスタートメニュー、cmdと入力、右クリック、「管理者として実行」)、以下を実行する。
pip install opencv-python numpy matplotlib
Pythonプログラム例
以下のプログラム例を実行する前に、次の前提条件を確認する。
依存ライブラリ:
import cv2、import numpy as np、import matplotlib.pyplot as pltが実行できる環境を用意する。表示時の色順:OpenCVの画像はBGR順である。Matplotlibで表示する場合は
cv2.cvtColor(image, cv2.COLOR_BGR2RGB)でRGBに変換する。評価指標の入力条件:PSNR/MSEなどの比較指標は、同一サイズの画像同士を比較する前提である。
1. 画質改善処理
このプログラムは画像の明るさとコントラストを改善する手法を実装する。暗い画像や明るすぎる画像の視認性向上に用いる。ヒストグラム均等化、CLAHE、ガンマ補正、コントラスト調整、彩度調整の5つの手法を比較する。
この操作の目的:画質調整の代表的手法を同一条件で実行し、出力の違いを比較して、用途に応じた手法選択の指針を得る。
注意:ヒストグラム均等化やCLAHEはグレースケール画像を前提とするため、出力が単一チャンネルとなる。
OpenCVの色チャンネル順:OpenCVの画像はBGR順である。
plt.imshow()はRGB順を前提とするため、cv2.cvtColor(image, cv2.COLOR_BGR2RGB)で変換する。HSVのH成分の範囲:OpenCVのHSVではH(色相)が0〜180で表現される。一般的な0〜360表現と異なる。
dtypeと正規化:ガンマ補正では
image / 255.0で0〜1に正規化してからnp.powerを適用する。整数型のまま計算すると丸め誤差が大きくなる。コントラスト調整の型:
cv2.convertScaleAbsは結果を8bitに収める。中間結果を保持したい場合はfloatで計算する。彩度調整時のクリップ:HSVのS成分を乗算した後は
np.clip(..., 0, 255)で範囲外を抑制する。
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 histogram_equalization(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
equalized = cv2.equalizeHist(gray)
return equalized
# CLAHE(適応的ヒストグラム均等化)
def clahe_enhancement(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
enhanced = clahe.apply(gray)
return enhanced
# ガンマ補正
def gamma_correction(image, gamma):
normalized = image / 255.0
corrected = np.power(normalized, gamma)
return (corrected * 255).astype(np.uint8)
# コントラスト調整
def adjust_contrast(image, alpha, beta):
adjusted = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)
return adjusted
# 彩度調整
def adjust_saturation(image, scale):
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV).astype(np.float32)
hsv[:,:,1] = hsv[:,:,1] * scale
hsv[:,:,1] = np.clip(hsv[:,:,1], 0, 255)
hsv = hsv.astype(np.uint8)
return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
# 実行
image = create_dark_image()
eq_result = histogram_equalization(image)
clahe_result = clahe_enhancement(image)
gamma_bright = gamma_correction(image, 0.5)
contrast_result = adjust_contrast(image, alpha=1.5, beta=30)
# カラー画像での彩度調整用のテスト画像
color_image = np.ones((300, 400, 3), dtype=np.uint8) * 128
cv2.rectangle(color_image, (50, 50), (150, 150), (200, 100, 100), -1)
cv2.circle(color_image, (300, 100), 50, (100, 200, 100), -1)
cv2.rectangle(color_image, (100, 200), (300, 280), (100, 100, 200), -1)
saturation_high = adjust_saturation(color_image, 1.5)
saturation_low = adjust_saturation(color_image, 0.5)
# 表示
plt.figure(figsize=(15, 10))
plt.subplot(3, 3, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title('Original (Dark)')
plt.axis('off')
plt.subplot(3, 3, 2)
plt.imshow(eq_result, cmap='gray')
plt.title('Histogram Equalization')
plt.axis('off')
plt.subplot(3, 3, 3)
plt.imshow(clahe_result, cmap='gray')
plt.title('CLAHE')
plt.axis('off')
plt.subplot(3, 3, 4)
plt.imshow(cv2.cvtColor(gamma_bright, cv2.COLOR_BGR2RGB))
plt.title('Gamma Correction (γ=0.5)')
plt.axis('off')
plt.subplot(3, 3, 5)
plt.imshow(cv2.cvtColor(contrast_result, cv2.COLOR_BGR2RGB))
plt.title('Contrast Adjustment')
plt.axis('off')
plt.subplot(3, 3, 6)
gray_original = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
plt.hist(gray_original.ravel(), bins=256, range=[0, 256], alpha=0.5, label='Original')
plt.hist(eq_result.ravel(), bins=256, range=[0, 256], alpha=0.5, label='Equalized')
plt.title('Histogram Comparison')
plt.xlabel('Pixel Value')
plt.ylabel('Frequency')
plt.legend()
plt.subplot(3, 3, 7)
plt.imshow(cv2.cvtColor(color_image, cv2.COLOR_BGR2RGB))
plt.title('Original (Color)')
plt.axis('off')
plt.subplot(3, 3, 8)
plt.imshow(cv2.cvtColor(saturation_high, cv2.COLOR_BGR2RGB))
plt.title('High Saturation (×1.5)')
plt.axis('off')
plt.subplot(3, 3, 9)
plt.imshow(cv2.cvtColor(saturation_low, cv2.COLOR_BGR2RGB))
plt.title('Low Saturation (×0.5)')
plt.axis('off')
plt.tight_layout()
plt.show()
print("画像サイズ:", image.shape)
print("データ型:", image.dtype)
print("アスペクト比:", image.shape[1] / image.shape[0])
2. ノイズ量の評価
このプログラムは画像に含まれるノイズの量を定量的に評価する。ノイズの種類に応じたフィルタ選択の指標を示す。ガウシアンノイズにはガウシアンフィルタまたはバイラテラルフィルタ、ソルト&ペッパーノイズにはメディアンフィルタを用いる。
この操作の目的:ノイズ量の指標を計算し、ノイズ除去前後での変化を確認して、フィルタの効果を定量的に評価する。
注意:SNRの算出は「差分をノイズとみなす」定義に依存する。別のノイズモデルでは定義が変わる。
ノイズ生成とdtype:
np.random.normalの結果はfloatである。image.astype(float) + noiseとし、最後にnp.clip(..., 0, 255).astype(np.uint8)で8bitに戻す。SNRの前提:本コードのSNRは「元画像と劣化画像の差」をノイズとして扱う。
PSNR/MSEの前提:同一サイズ・同一対応画素の2枚の画像を比較する指標である。位置ずれやリサイズがあると値の解釈が変わる。
OpenCVのPSNR入力:
cv2.PSNRは8bit(0〜255)画像同士を想定している。バイラテラルフィルタの計算量:パラメータにより計算量が増える。実験では入力サイズやパラメータを固定して比較する。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# テスト画像の生成
def create_test_image():
image = np.ones((300, 400, 3), dtype=np.uint8) * 128
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, mean=0, std=25):
noise = np.random.normal(mean, std, image.shape)
noisy = image.astype(float) + noise
return np.clip(noisy, 0, 255).astype(np.uint8)
# ソルト&ペッパーノイズの付加
def add_salt_pepper_noise(image, prob=0.05):
noisy = image.copy()
salt = np.random.rand(*image.shape[:2]) < prob / 2
noisy[salt] = 255
pepper = np.random.rand(*image.shape[:2]) < prob / 2
noisy[pepper] = 0
return noisy
# SNR計算
def calculate_snr(original, noisy):
original_gray = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY).astype(float)
noisy_gray = cv2.cvtColor(noisy, cv2.COLOR_BGR2GRAY).astype(float)
signal_power = np.mean(original_gray ** 2)
noise = original_gray - noisy_gray
noise_power = np.mean(noise ** 2)
if noise_power == 0:
return float('inf')
snr = 10 * np.log10(signal_power / noise_power)
return snr
# ノイズ分散の計算
def calculate_noise_variance(original, noisy):
original_gray = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY).astype(float)
noisy_gray = cv2.cvtColor(noisy, cv2.COLOR_BGR2GRAY).astype(float)
noise = original_gray - noisy_gray
return np.var(noise)
# 実行
original = create_test_image()
gaussian_noisy = add_gaussian_noise(original, std=25)
sp_noisy = add_salt_pepper_noise(original, prob=0.05)
# ノイズ評価
gaussian_snr = calculate_snr(original, gaussian_noisy)
sp_snr = calculate_snr(original, sp_noisy)
gaussian_var = calculate_noise_variance(original, gaussian_noisy)
sp_var = calculate_noise_variance(original, sp_noisy)
# ノイズ除去
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)
# PSNR計算
psnr_gaussian_filtered = cv2.PSNR(original, gaussian_filtered)
psnr_sp_filtered = cv2.PSNR(original, sp_filtered)
psnr_bilateral = cv2.PSNR(original, bilateral_filtered)
# MSE計算
mse_gaussian = np.mean((original.astype(float) - gaussian_noisy.astype(float))**2)
mse_sp = np.mean((original.astype(float) - sp_noisy.astype(float))**2)
# 表示
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(f'Gaussian Noise\nSNR: {gaussian_snr:.2f} dB\nVar: {gaussian_var:.2f}\nMSE: {mse_gaussian:.2f}')
plt.axis('off')
plt.subplot(3, 3, 3)
plt.imshow(cv2.cvtColor(gaussian_filtered, cv2.COLOR_BGR2RGB))
plt.title(f'Gaussian Filtered\nPSNR: {psnr_gaussian_filtered:.2f} dB')
plt.axis('off')
plt.subplot(3, 3, 5)
plt.imshow(cv2.cvtColor(sp_noisy, cv2.COLOR_BGR2RGB))
plt.title(f'Salt & Pepper\nSNR: {sp_snr:.2f} dB\nVar: {sp_var:.2f}\nMSE: {mse_sp:.2f}')
plt.axis('off')
plt.subplot(3, 3, 6)
plt.imshow(cv2.cvtColor(sp_filtered, cv2.COLOR_BGR2RGB))
plt.title(f'Median Filtered\nPSNR: {psnr_sp_filtered:.2f} dB')
plt.axis('off')
plt.subplot(3, 3, 8)
plt.imshow(cv2.cvtColor(bilateral_filtered, cv2.COLOR_BGR2RGB))
plt.title(f'Bilateral Filtered\nPSNR: {psnr_bilateral:.2f} dB')
plt.axis('off')
plt.tight_layout()
plt.show()
print(f"ガウシアンノイズ - SNR: {gaussian_snr:.2f} dB, 分散: {gaussian_var:.2f}, MSE: {mse_gaussian:.2f}")
print(f"ソルト&ペッパーノイズ - SNR: {sp_snr:.2f} dB, 分散: {sp_var:.2f}, MSE: {mse_sp:.2f}")
print(f"ガウシアンフィルタ後 PSNR: {psnr_gaussian_filtered:.2f} dB")
print(f"メディアンフィルタ後 PSNR: {psnr_sp_filtered:.2f} dB")
print(f"バイラテラルフィルタ後 PSNR: {psnr_bilateral:.2f} dB")
3. 畳み込みフィルタの適用
このプログラムは畳み込みカーネルを画像に適用し、その効果を比較する。カーネルの設計により平滑化、鮮鋭化、エッジ検出などの処理を実現できる。カスタムカーネル作成の参考となる。
この操作の目的:カーネルの要素と出力の対応を確認し、カーネル設計の基礎を理解する。
注意:エッジ検出では負値が出るため、出力の型や可視化方法により見え方が変わる。
dtypeと負値:エッジ検出カーネルは負の値を持つため、演算結果は負値を含む。
cv2.filter2Dの出力が8bitの場合、負値は0に切り詰められる。カラーとグレースケール:本例ではエッジ検出をグレースケールに対して行う。カラー画像に各チャンネル別にエッジを取ると結果が異なる。
カーネルの正規化:平均化カーネルは和が1になるように正規化する。正規化しないと画像全体の輝度が変化する。
境界の扱い:畳み込みでは画像端部の扱い(パディング)が結果に影響する。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# テスト画像の生成
def create_test_image():
image = np.ones((300, 400, 3), dtype=np.uint8) * 200
cv2.rectangle(image, (50, 50), (150, 150), (100, 100, 100), -1)
cv2.rectangle(image, (50, 50), (150, 150), (80, 80, 80), 2)
cv2.circle(image, (300, 100), 50, (120, 120, 120), -1)
cv2.circle(image, (300, 100), 50, (90, 90, 90), 2)
pts = np.array([[100, 200], [200, 200], [150, 280]], np.int32)
cv2.fillPoly(image, [pts], (130, 130, 130))
cv2.polylines(image, [pts], True, (100, 100, 100), 2)
return image
# カーネルの定義
def create_kernels():
# 平滑化カーネル(3x3平均化)
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_enhance_kernel = np.array([
[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]
], np.float32)
return blur_kernel, sharpen_kernel, edge_h_kernel, edge_v_kernel, edge_enhance_kernel
# 畳み込み処理
def apply_convolution(image, kernel):
return cv2.filter2D(image, -1, kernel)
# 実行
image = create_test_image()
blur_k, sharp_k, edge_h_k, edge_v_k, edge_enh_k = create_kernels()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 各カーネルの適用
blurred = apply_convolution(image, blur_k)
sharpened = apply_convolution(image, sharp_k)
edge_h = apply_convolution(gray, edge_h_k)
edge_v = apply_convolution(gray, edge_v_k)
edge_enhanced = apply_convolution(gray, edge_enh_k)
# 表示
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 (3×3 Average)')
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 Detection (Horizontal)')
plt.axis('off')
plt.subplot(2, 3, 5)
plt.imshow(edge_v, cmap='gray')
plt.title('Edge Detection (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()
print("カーネルサイズ:")
print(f"平滑化カーネル: {blur_k.shape}")
print(f"鮮鋭化カーネル: {sharp_k.shape}")
print(f"エッジ検出カーネル: {edge_h_k.shape}")
4. エッジの解析
このプログラムは画像内のエッジを検出し、その強度と方向を解析する。物体の輪郭抽出や形状解析の基礎となる処理である。Sobel、Laplacian、Cannyの3つの手法を比較し、用途に応じた手法選択の参考を示す。ノイズが多い画像ではCannyエッジ検出を用いる。
この操作の目的:エッジを抽出し、強度と方向を図で確認して、手法ごとの特性を把握する。
注意:Cannyの閾値は入力画像のコントラストやノイズ量に依存するため、画像ごとに調整する。
Sobelの深度:
cv2.Sobelはddepth=cv2.CV_64Fで符号付きの勾配を保持する。8bitで計算すると負値が失われる。勾配強度の正規化:
magnitudeは最大値で割って0〜255へ正規化している。最大値が0の場合は除算エラーとなる。方向画像の表示:
directionはラジアンであり、cmap='hsv'で角度を色として可視化している。Cannyの前処理:Cannyは内部で平滑化を行う。閾値は画像のノイズ量やコントラストに依存する。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# テスト画像の生成
def create_test_image():
image = np.ones((300, 400, 3), dtype=np.uint8) * 200
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))
cv2.line(image, (250, 200), (350, 280), (80, 80, 80), 3)
return image
# Sobelエッジ検出
def sobel_edge_detection(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 = np.sqrt(sobel_x**2 + sobel_y**2)
magnitude = np.uint8(255 * magnitude / magnitude.max())
direction = np.arctan2(sobel_y, sobel_x)
return magnitude, direction, sobel_x, sobel_y
# Cannyエッジ検出
def canny_edge_detection(image, low_threshold, high_threshold):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
edges = cv2.Canny(blurred, low_threshold, high_threshold)
return edges
# Laplacianエッジ検出
def laplacian_edge_detection(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
laplacian = np.uint8(np.absolute(laplacian))
return laplacian
# ブラー検出
def detect_blur(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
return laplacian_var
# 実行
image = create_test_image()
magnitude, direction, sobel_x, sobel_y = sobel_edge_detection(image)
canny_edges = canny_edge_detection(image, 50, 150)
laplacian_edges = laplacian_edge_detection(image)
blur_score = detect_blur(image)
# 表示
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 (Edge Strength)')
plt.axis('off')
plt.subplot(3, 3, 3)
plt.imshow(direction, cmap='hsv')
plt.title('Sobel Direction (Edge Direction)')
plt.axis('off')
plt.colorbar(label='Angle (radians)')
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(np.abs(sobel_x), cmap='gray')
plt.title('Sobel X (Gradient)')
plt.axis('off')
plt.subplot(3, 3, 6)
plt.imshow(np.abs(sobel_y), cmap='gray')
plt.title('Sobel Y (Gradient)')
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.xlabel('Magnitude')
plt.ylabel('Frequency')
plt.subplot(3, 3, 9)
plt.text(0.1, 0.5, f'Blur Detection Score:\n{blur_score:.2f}\n\n(Higher = Sharper)',
fontsize=14, verticalalignment='center')
plt.axis('off')
plt.tight_layout()
plt.show()
print(f"検出されたエッジピクセル数(Canny): {np.sum(canny_edges > 0)}")
print(f"平均エッジ強度: {np.mean(magnitude):.2f}")
print(f"ブラー検出スコア: {blur_score:.2f}")
5. リサイズと補間
このプログラムは画像のリサイズと補間手法を比較する。用途に応じた補間手法の選択指針を示す。最近傍補間はマスク画像やラベル画像に、バイリニア補間は一般的な用途に、バイキュービック補間は写真の拡大に使用される。
この操作の目的:補間法による見え方の差と、処理後の評価値(ヒストグラム相関)の変化を確認して、補間手法の選択基準を理解する。
注意:ヒストグラム相関は空間配置を評価しないため、相関が近くても見た目が異なる場合がある。
リサイズは情報を保存しない:縮小では高周波成分が失われ、拡大では新しい画素値を補間で生成する。縮小→拡大で元画像は復元されない。
補間手法と用途:最近傍補間はラベル値を混ぜないため、セマンティックセグメンテーション(画像の各ピクセルにカテゴリラベルを割り当てる処理)のマスクなどに用いる。自然画像では線形・3次補間を用いる。
ヒストグラム相関の限界:ヒストグラム相関は画素の空間配置を評価しない。同じヒストグラムでも見た目が異なる場合がある。
回転の境界:
cv2.warpAffineでは回転後に画像外の領域が既定値で埋められる。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# テスト画像の生成
def create_test_image():
image = np.ones((200, 200, 3), dtype=np.uint8) * 200
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)
for i in range(60, 140, 10):
cv2.line(image, (60, i), (140, i), (80, 80, 80), 1)
return image
# 各種補間手法でのリサイズ
def resize_with_interpolation(image, scale):
h, w = image.shape[:2]
new_size = (int(w * scale), int(h * scale))
nearest = cv2.resize(image, new_size, interpolation=cv2.INTER_NEAREST)
linear = cv2.resize(image, new_size, interpolation=cv2.INTER_LINEAR)
cubic = cv2.resize(image, new_size, interpolation=cv2.INTER_CUBIC)
return nearest, linear, cubic
# 回転
def rotate_image(image, angle):
h, w = image.shape[:2]
center = (w // 2, h // 2)
matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(image, matrix, (w, h))
return rotated
# 反転
def flip_image(image):
horizontal = cv2.flip(image, 1)
vertical = cv2.flip(image, 0)
return horizontal, vertical
# トリミング
def crop_image(image, x, y, w, h):
return image[y:y+h, x:x+w]
# ヒストグラム比較
def compare_histograms(image1, image2):
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
hist1 = cv2.calcHist([gray1], [0], None, [256], [0, 256])
hist2 = cv2.calcHist([gray2], [0], None, [256], [0, 256])
correlation = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
return correlation
# 実行
image = create_test_image()
# 拡大(2倍)
nearest_up, linear_up, cubic_up = resize_with_interpolation(image, 2.0)
# 縮小(0.5倍)
nearest_down, linear_down, cubic_down = resize_with_interpolation(image, 0.5)
# 回転と反転
rotated_45 = rotate_image(image, 45)
h_flip, v_flip = flip_image(image)
# トリミング
cropped = crop_image(image, 50, 50, 100, 100)
# ヒストグラム比較
hist_corr_nearest = compare_histograms(image, cv2.resize(nearest_down, (200, 200)))
hist_corr_linear = compare_histograms(image, cv2.resize(linear_down, (200, 200)))
hist_corr_cubic = compare_histograms(image, cv2.resize(cubic_down, (200, 200)))
# 表示
fig = plt.figure(figsize=(20, 12))
# 元画像
plt.subplot(3, 5, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title('Original (200×200)')
plt.axis('off')
# 拡大(2倍)
plt.subplot(3, 5, 2)
plt.imshow(cv2.cvtColor(nearest_up, cv2.COLOR_BGR2RGB))
plt.title('2× Nearest Neighbor')
plt.axis('off')
plt.subplot(3, 5, 3)
plt.imshow(cv2.cvtColor(linear_up, cv2.COLOR_BGR2RGB))
plt.title('2× Bilinear')
plt.axis('off')
plt.subplot(3, 5, 4)
plt.imshow(cv2.cvtColor(cubic_up, cv2.COLOR_BGR2RGB))
plt.title('2× Bicubic')
plt.axis('off')
# 縮小(0.5倍)
plt.subplot(3, 5, 6)
plt.imshow(cv2.cvtColor(nearest_down, cv2.COLOR_BGR2RGB))
plt.title(f'0.5× Nearest\nHist Corr: {hist_corr_nearest:.3f}')
plt.axis('off')
plt.subplot(3, 5, 7)
plt.imshow(cv2.cvtColor(linear_down, cv2.COLOR_BGR2RGB))
plt.title(f'0.5× Bilinear\nHist Corr: {hist_corr_linear:.3f}')
plt.axis('off')
plt.subplot(3, 5, 8)
plt.imshow(cv2.cvtColor(cubic_down, cv2.COLOR_BGR2RGB))
plt.title(f'0.5× Bicubic\nHist Corr: {hist_corr_cubic:.3f}')
plt.axis('off')
# 回転
plt.subplot(3, 5, 11)
plt.imshow(cv2.cvtColor(rotated_45, cv2.COLOR_BGR2RGB))
plt.title('Rotated 45°')
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 (100×100)')
plt.axis('off')
plt.tight_layout()
plt.show()
print("画像サイズ情報:")
print(f"元画像: {image.shape}")
print(f"2倍拡大(最近傍補間): {nearest_up.shape}")
print(f"0.5倍縮小(最近傍補間): {nearest_down.shape}")
print(f"トリミング: {cropped.shape}")
print(f"\nヒストグラム相関(縮小→拡大後):")
print(f"最近傍補間: {hist_corr_nearest:.3f}")
print(f"バイリニア補間: {hist_corr_linear:.3f}")
print(f"バイキュービック補間: {hist_corr_cubic:.3f}")