概要
画像処理100本ノックのうち、画素値の分布を扱うQ.20〜24を解説します。ヒストグラムを 可視化し、線形変換、統計量、累積度数、ガンマ特性を使って画像の明るさとコントラストを 変える一連の処理です。
ヒストグラムを作る(Q.20)
0〜255に対応する256要素の配列を用意し、画素値を添字として出現回数を加算します。
const histogram = new Array<number>(256).fill(0);
for (let i = 0; i < src.data.length; i += 4) {
histogram[src.data[i]]++;
histogram[src.data[i + 1]]++;
histogram[src.data[i + 2]]++;
}
現在の実装ではChart.jsを使わず、Canvas 2D APIで棒グラフを直接描画しています。
線形な濃度変換(Q.21、Q.22)
Q.21の正規化では、入力画像の最小値vMinと最大値vMaxを、出力範囲dMin〜dMaxへ
線形に写します。
const normalize = (value: number) =>
((dMax - dMin) / (vMax - vMin)) * (value - vMin) + dMin;
Q.22では平均と標準偏差を求め、分布の中心を指定平均へ移し、分布の幅を指定標準偏差へ 拡大・縮小します。どちらも画素の大小関係を保ったままコントラストを調整する処理です。
累積ヒストグラムによる平坦化(Q.23)
ヒストグラム平坦化では、値v以下の画素数を累積し、全画素数に対する割合を0〜255へ
割り当てます。頻度が集中した濃度域を広く使うため、暗部や明部へ偏った画像の細部が
見えやすくなります。
単純な正規化は最小値と最大値だけを見ます。一方、平坦化は分布全体を使う点が違います。
非線形なガンマ補正(Q.24)
ガンマ補正は、正規化した画素値へべき乗を適用します。
const corrected = 255 * (value / 255) ** (1 / gamma);
線形変換と異なり、中間調の明るさを選択的に変えられます。gammaの値によって暗部を
持ち上げたり、明部を抑えたりできます。
実装で気を付ける数値処理
ヒストグラムは通常256要素の整数配列を用意し、画素値を添字として出現回数を加算します。 RGBを別々に数える方法と、輝度へ変換して1本だけ数える方法ではグラフの意味が異なるため、 目的を先に決めます。
正規化や平坦化では、最大値と最小値が等しい単色画像を入力すると分母が0になります。この場合は 入力をそのまま返すなどの分岐が必要です。累積分布関数を使う平坦化では、計算中は浮動小数点を 保ち、RGBA配列へ戻す直前に丸めると誤差が積み重なりにくくなります。
ガンマ補正ではgamma <= 0を拒否し、0〜255をいったん0〜1へ正規化します。補正後の値を
クランプし、アルファ値は変更しない、という規則を共通化しておくと色チャンネルごとの実装差を
防げます。
結果を検証する
処理前後でヒストグラムを並べ、平均値・最小値・最大値も表示すると変化を定量的に確認できます。 見た目が改善していても白飛びや黒つぶれが増えていることがあるため、グラフと画像の両方で判断します。
まとめ
ヒストグラム処理は「画像をどう変えるか」より先に「画素値がどう分布しているか」を調べます。 同じ画像に正規化、平坦化、ガンマ補正を適用し、ヒストグラムと見た目の両方を比較すると、 各手法の違いを理解しやすくなります。