【VBA×画像処理】画像を平滑化する(ガウシアン)

本ページでは、VBAで読み込んだビットマップ(BMP)画像を内部的に処理し、ガウシアンフィルタを用いて画像を平滑化(ぼかし)する方法を解説していきます。平滑化とは画像のノイズや細かな変化をなめらかにする処理のことを指し、画像処理の前処理として広く利用される基本的な処理の1つです。本ページでは平滑化処理の中でも代表的な「ガウシアンフィルタ」を実装します。

icon-warning 注意事項

本ページではビットマップ(BMP)画像の読み込みと書き出しページで実装したクラスに画像処理を追加していきます。前提の機能となる画像の入出力機能を実装しているため事前に一読ください。

ガウシアンフィルタによる平滑化

ガウシアンフィルタとは、注目ピクセルとその周囲のピクセルの色をガウス分布に従って加重平均することで画像をなめらかにする処理です。具体的には注目ピクセルを中心とした正方形の範囲のピクセルそれぞれに対して、中心からの距離に応じた重みを掛けて足し合わせる処理を全ピクセルに適用します。中心に近いピクセルほど大きな重みを持つため、画像の特徴は保ちつつ細かなノイズや色の急激な変化が緩和され、自然なぼかし効果が得られます。

各ピクセルに与える重みは下記のガウス分布の式で決まります。\(\sigma\) は標準偏差と呼ばれる値で、これがガウシアンフィルタのぼかしの強さを決める重要なパラメータとなります。

\[
G(x, y) = \frac{1}{2\pi\sigma^2} \exp\left( -\frac{x^2 + y^2}{2\sigma^2} \right)
\]

(※ここで \( x, y \) は中心ピクセルからの相対座標)

カーネル

ガウシアンフィルタの計算にはカーネルと呼ばれる小さな2次元配列を使います。カーネルは前述のガウス分布の式に基づいて生成され、配列の各要素にはピクセルごとの重み係数が格納されています。たとえば3×3のカーネルでは下記のような重みが設定されます。

\[
K = \begin{pmatrix}
0.075 & 0.124 & 0.075 \\
0.124 & 0.204 & 0.124 \\
0.075 & 0.124 & 0.075
\end{pmatrix}
\]

中心の要素ほど値が大きく、外側に行くほど小さな値になっていることがわかります。なお、カーネル全体の合計値は1になるように正規化されています。これは重みの合計が1でないと、フィルタを適用した後の画像全体が明るくなったり暗くなったりしてしまうためです。

畳み込み処理

ガウシアンフィルタの本体の処理は畳み込みと呼ばれる演算で、カーネルを画像上で1ピクセルずつずらしながら適用していきます。各位置でカーネルの各要素と対応するピクセル値を掛け合わせて合計し、それを注目ピクセルの新しい値として書き込んでいきます。式で表すと下記の通りです。

\[
I'(x, y) = \sum_{j=-k}^{k} \sum_{i=-k}^{k} K(i, j) \cdot I(x+i, y+j)
\]

(※ここで \( I \) は元画像、\( I’ \) は処理後の画像、\( K \) はカーネル、\( k \) はカーネルの中心からの距離。3×3カーネルなら \( k=1 \)、5×5カーネルなら \( k=2 \))

この処理を画像内のすべてのピクセルに対して行います。なお、画像の端にあるピクセルでは参照範囲が画像の外にはみ出す場合があるため、本実装でははみ出した位置の参照は最も近い端のピクセル値で代用するクランプ処理を行います。

パラメータの意味

本実装ではガウシアンフィルタを適用する際に「カーネルサイズ」と「標準偏差σ」の2つのパラメータを指定します。それぞれの役割は以下の通りです。

\(\sigma\) はぼかしの強さを決める値で、大きいほど強くぼやけます。一方カーネルサイズはガウス分布の効果が十分発揮できる範囲を確保するための値で、σの値に合わせて適切な大きさを設定する必要があります。経験則的にカーネルサイズは 6σ + 1 以上に設定するのが目安とされており、小さすぎるとガウス分布の裾が切り捨てられフィルタ性能が落ちる可能性があります。組み合わせの例を以下に示します。

カーネルサイズ 標準偏差σ ぼかしの強さ 用途の目安
3×3 0.5 ごく弱い 軽微なノイズ除去
5×5 1.0 標準 一般的な平滑化
7×7 1.5 強め 明確なぼかし効果
11×11 2.0 強い 大きな構造のみ残したい場合
icon-warning VBAでの処理時間に関する注意

ガウシアンフィルタは「全ピクセル × カーネル全要素」の二重ループで計算されるため、画像サイズやカーネルサイズが大きくなると計算量が増大します。

 
たとえば 400×300 の画像に 5×5 カーネルを適用すると、合計で 300万回以上の演算 が発生します。VBAはインタプリタ言語のためC++やPythonと比べて実行速度が遅く、カーネルサイズが大きい場合は数十秒〜数分の処理時間がかかる可能性があります。
 
処理時間を短縮するには下記の対策が有効です。
 ・ カーネルサイズを小さくする:カーネルサイズの2乗で計算量が増えるため最も効果的
 ・ 画像のサイズを小さくする:事前に画像を縮小することで計算量を削減できる
 ・ 必要十分なぼかし強度に留める:σを大きくしすぎず必要最小限のぼかしに抑える

サンプルコード

以下はガウシアンフィルタによる画像の平滑化を行うためのコードです。事前に用意しておいたclsBitmap内に下記コードをコピペすることで利用可能になります。本実装ではGaussianBlur関数のみを呼び出せば、内部的にカーネル生成と畳み込み処理が自動で行われます。

クラスモジュール (clsBitmapに追記)

'****************************************************************************
'*  ガウシアンフィルタによる平滑化
'*
'*      (lKernelSize):カーネルサイズ[px] (奇数, 省略時は3)
'*      (dSigma)     :標準偏差σ (省略時は1.0)
'****************************************************************************
Public Sub GaussianBlur(Optional lKernelSize As Long = 3, _
                        Optional dSigma As Double = 1#)
    
    Dim rgbDataOrg()  As RGBTRIPLE '元画像RGBデータ
    Dim rgbDataBlur() As RGBTRIPLE '平滑化後画像RGBデータ
    Dim dKernel()     As Double    'ガウシアンカーネル
    
    Dim lHeight     As Long
    Dim lWidth      As Long
    Dim lX          As Long
    Dim lY          As Long
    Dim lKx         As Long
    Dim lKy         As Long
    Dim lRefX       As Long
    Dim lRefY       As Long
    Dim lKernelHalf As Long
    Dim dSumR       As Double
    Dim dSumG       As Double
    Dim dSumB       As Double
    Dim dWeight     As Double
    
    '入力値確認 (カーネルサイズが正の奇数でない、またはσが正の値でない場合は処理終了)
    If lKernelSize < 1 Or lKernelSize Mod 2 = 0 Then
        Exit Sub
    End If
    If dSigma <= 0 Then
        Exit Sub
    End If
    
    'ガウシアンカーネルを生成
    dKernel = GenerateGaussianKernel(lKernelSize, dSigma)
    lKernelHalf = lKernelSize \ 2
    
    '元画像情報を取得
    rgbDataOrg = m_rgbImageData
    lHeight = UBound(rgbDataOrg, 1) + 1  '配列は0始まりなので+1で補正
    lWidth = UBound(rgbDataOrg, 2) + 1   '配列は0始まりなので+1で補正
    
    '配列サイズを定義(元画像と同じ2次元配列)
    ReDim rgbDataBlur(UBound(rgbDataOrg, 1), UBound(rgbDataOrg, 2))
    
    '畳み込み処理 (全ピクセルに対してカーネルを適用)
    For lY = 0 To lHeight - 1
        For lX = 0 To lWidth - 1
            
            dSumR = 0
            dSumG = 0
            dSumB = 0
            
            'カーネルの全要素について加重合計を計算
            For lKy = 0 To lKernelSize - 1
                For lKx = 0 To lKernelSize - 1
                    
                    '参照位置を計算
                    lRefX = lX + lKx - lKernelHalf
                    lRefY = lY + lKy - lKernelHalf
                    
                    '境界処理:端の外側は最も近い端のピクセル値で代用 (クランプ)
                    If lRefX < 0 Then lRefX = 0
                    If lRefX > lWidth - 1 Then lRefX = lWidth - 1
                    If lRefY < 0 Then lRefY = 0
                    If lRefY > lHeight - 1 Then lRefY = lHeight - 1
                    
                    'カーネル係数で重み付けして加算
                    dWeight = dKernel(lKy, lKx)
                    dSumR = dSumR + rgbDataOrg(lRefY, lRefX).rgbRed * dWeight
                    dSumG = dSumG + rgbDataOrg(lRefY, lRefX).rgbGreen * dWeight
                    dSumB = dSumB + rgbDataOrg(lRefY, lRefX).rgbBlue * dWeight
                Next
            Next
            
            '0~255の範囲にクランプして格納 (浮動小数点誤差対策)
            If dSumR < 0 Then dSumR = 0
            If dSumR > 255 Then dSumR = 255
            If dSumG < 0 Then dSumG = 0
            If dSumG > 255 Then dSumG = 255
            If dSumB < 0 Then dSumB = 0
            If dSumB > 255 Then dSumB = 255
            
            rgbDataBlur(lY, lX).rgbRed = CByte(dSumR)
            rgbDataBlur(lY, lX).rgbGreen = CByte(dSumG)
            rgbDataBlur(lY, lX).rgbBlue = CByte(dSumB)
        Next
    Next
    
    'RGBデータを更新
    m_rgbImageData = rgbDataBlur

End Sub

'----------------------------------------------------------------------------
'-  ガウシアンカーネルを生成
'----------------------------------------------------------------------------
Private Function GenerateGaussianKernel(ByVal lKernelSize As Long, _
                                        ByVal dSigma As Double) As Double()
    
    Dim dKernel()   As Double
    Dim lKernelHalf As Long
    Dim lX          As Long
    Dim lY          As Long
    Dim dDx         As Double
    Dim dDy         As Double
    Dim dSum        As Double
    Dim dCoef       As Double
    Dim dPI         As Double
    
    dPI = 3.14159265358979
    lKernelHalf = lKernelSize \ 2
    ReDim dKernel(lKernelSize - 1, lKernelSize - 1)
    
    'ガウシアン分布の係数 1/(2πσ²)
    dCoef = 1# / (2# * dPI * dSigma * dSigma)
    
    'ガウシアン分布に基づいて各要素の値を計算
    dSum = 0
    For lY = 0 To lKernelSize - 1
        For lX = 0 To lKernelSize - 1
            dDx = lX - lKernelHalf
            dDy = lY - lKernelHalf
            dKernel(lY, lX) = dCoef * Exp(-(dDx * dDx + dDy * dDy) / (2# * dSigma * dSigma))
            dSum = dSum + dKernel(lY, lX)
        Next
    Next
    
    'カーネルの合計値が1になるように正規化
    For lY = 0 To lKernelSize - 1
        For lX = 0 To lKernelSize - 1
            dKernel(lY, lX) = dKernel(lY, lX) / dSum
        Next
    Next
    
    GenerateGaussianKernel = dKernel
    
End Function

 
標準モジュール

Option Explicit
Sub main()

    Dim sPathBmpSrc As String
    Dim sPathBmpExport As String
    Dim oBitmap As clsBitmap

    sPathBmpSrc = "C:\...\source.bmp"
    sPathBmpExport = "C:\...\output.bmp"

    Set oBitmap = New clsBitmap
    Call oBitmap.LoadBitmap(sPathBmpSrc)        '画像読み込み
    Call oBitmap.GaussianBlur(5, 1#)            'ガウシアンフィルタ(5×5, σ=1.0)
    Call oBitmap.ExportBitmap24(sPathBmpExport) '画像出力

End Sub

使い方は非常にシンプルで上記コードのように既に用意しておいた画像の入出力処理の間に、ガウシアンフィルタの処理を呼び出すだけです。第1引数でカーネルサイズ、第2引数でσを指定できます。引数を省略するとデフォルト値の3×3カーネル・σ=1.0で実行されます。
 

関連情報

VBA×画像処理ページ

次回 >> マスクを適用して領域抽出する
前回 >> テンプレートマッチング
画像処理のメインページへ戻る

参考

外部リンク:Gaussian blur – Wikipedia
     :Kernel (image processing) – Wikipedia

Excel,VBA,画像処理