Home

画像のウェーブレット解析の試み

OpenCVとndarray型配列

OpenCVのインストール(WindowsLinux

 

 ウェーブレット解析が金谷(2003362023)に分かり易く説明されていた。これは1変数の関数が扱われているが、2変数の関数(画像データ)の場合を、Nason (2008, sec. 2.13)を参考にして、以下のように考えた。

 2変量の関数において、領域の外では0であるものについて考える。さらに、各矩形領域の中では定数であるとする。すなわち、

である。画像はピクセル単位で値が与えられるので、各矩形内で定数であるという条件は本質的には制約ではない。

 いま、スケーリング関数とウェーブレット母関数を次のように置く。

 このとき、さらに次のように置く。

これらの関数は互いに直交している。したがって、ノルムを1に調整した以下の関数は正規直交系を構成する。

 いま、

と置けば、

である。

 さらに、

と置けば、

 

となる。は、レベルの空間の直和になっている。

 いま、が、空間において、次式のように表されているとする。

 

関数は、0あるいは1を値とする関数であるので、の関数値である。関数を、空間の成分に分解するために、それらの正規直交基底との内積を求めると、以下の漸化式を得る。詳しくは、PDFファイルWaveAnal.pdfを参照されたい。

 

上記の漸化式(下降サンプリング)により、式(1)に基づいて、式(2)における係数から空間における係数を求めることができる。

ウェーブレット解析のプログラムをリスト1のように作成した。ファイルは、圧縮ファイルwa_files.zipにまとめたので、自由にダウンロード展開して利用されたい。

リスト1のプログラムはモノクロ画像対応であり、カラー画像を読み込んだ場合はモノクロ化されて処理が行われる。

カラー画像対応のプログラムも作成してみた。入力画像ファイルもサイズは自由とした。読み込み後、512×512に調整している。カラー画像対応のプログラムは、このウェブサイトの後半で説明している。

 

リスト1は、Windowsにおいては、ライブラリopencv-pythonnumpyおよびmatplotlibがインストールされておれば実行できる。すなわち、

 

pip install opencv-python numpy matplotlib

 

とインストールしておけばよい。

 

 

リスト1 画像のウェーブレット解析

import cv2          # pip install opencv-python

import numpy as np

import matplotlib.pyplot as plt

 

img1 = cv2.imread('Sample_4.png')    #   画像512×512ファイルの読み込み

plt.imshow(img1)

plt.show()

 

f_0 = np.sum(img1, axis=2)/3         #   モノクロ化

 

#

#   ウェーブレット解析結果を辞書型として保存

#

WaveletAnal = {}

WaveletAnal['0'] = {'f0': f_0}

 

n = 9

N = 2**n

 

#

#      レベルjの解析結果の図示

#

def disp_map(j):

    Cj = WaveletAnal[f'{j}']['f0']     #   係数C_h,k

    DHj = WaveletAnal[f'{j}']['DHj']

    DVj = WaveletAnal[f'{j}']['DVj']

    DDj = WaveletAnal[f'{j}']['DDj']

    #

    #    画像を配列imgsに設定

    #    大きさ512×512を4分割して使用

    #

    imgs = np.full((512, 512, 3), 0.5)

    for h in range((2**(n-j))):

        for k in range((2**(n-j))):

            for ii in range((2**j)//2):       #   スケールに合わせて引き延ばし

                for jj in range((2**j)//2):

                    for kk in range(3):     #   赤、緑、青の3チャンネル

                        #

                        #      C_hkの値

                        #

                        imgs[512//2 + ((2**j)//2)*h + ii]\

                                    [512//2 + ((2**j)//2)*k + jj][kk] \

                            = Cj[h][k] / 255

                        #

                        #     DH, DV, DDは、値0を黒に設定

                        #

                        imgs[512//2 + ((2**j)//2)*h + ii][((2**j)//2)*k + jj][kk] \

                            = 0.0

                        imgs[((2**j)//2)*h + ii][(512//2) + \

                                                 ((2**j)//2)*k + jj][kk] \

                            = 0.0

                        imgs[((2**j)//2)*h + ii][((2**j)//2)*k + jj][kk] \

                            = 0.0

                    if DHj[h][k] > 0:    #   正ならば緑に設定

                        imgs[((2**j)//2)*h + ii][(512//2) + \

                                    ((2**j)//2)*k + jj][1] \

                            = DHj[h][k] / 255

                    elif DHj[h][k] < 0:  #   負ならば赤に設定

                        imgs[((2**j)//2)*h + ii][(512//2) +\

                                    ((2**j)//2)*k + jj][0] \

                            = -DHj[h][k] /255

                    if DVj[h][k] > 0:

                        imgs[512//2 + ((2**j)//2)*h + ii][((2**j)//2)*k + jj][1] \

                            = DVj[h][k] / 255

                    elif DVj[h][k] < 0:

                        imgs[512//2 + ((2**j)//2)*h + ii][((2**j)//2)*k + jj][0] \

                            = -DVj[h][k] /255

                    if DDj[h][k] > 0:

                        imgs[((2**j)//2)*h + ii][((2**j)//2)*k + jj][1] \

                            = DDj[h][k] / 255

                    elif DDj[h][k] < 0:

                        imgs[((2**j)//2)*h + ii][((2**j)//2)*k + jj][0] \

                            = -DDj[h][k] /255

                   

 

    plt.imshow(imgs)    #   画像配列imgsの表示

    plt.xticks([])

    plt.yticks([])

    plt.title(f'j = {j}')

    plt.show()

 

 

for j in range(1,n):

    #      レベルjの解析

    print(f'Level {j} started.')

    Cj_1 = WaveletAnal[f'{j-1}']['f0']

    Cj = np.empty((2**(n-j), 2**(n-j)))

    DHj = np.empty((2**(n-j), 2**(n-j)))

    DVj = np.empty((2**(n-j), 2**(n-j)))

    DDj = np.empty((2**(n-j), 2**(n-j)))

    #

    #    下降サンプリング

    #

    for h in range(2**(n-j)):

        for k in range(2**(n-j)):

            Cj[h][k] = (Cj_1[2*h][2*k] + Cj_1[2*h+1][2*k] +

                          Cj_1[2*h][2*k+1] + Cj_1[2*h+1][2*k+1]) / 4

            DHj[h][k] = (Cj_1[2*h][2*k] - Cj_1[2*h+1][2*k] +

                          Cj_1[2*h][2*k+1] - Cj_1[2*h+1][2*k+1]) / 4

            DVj[h][k] = (Cj_1[2*h][2*k] + Cj_1[2*h+1][2*k] -

                          Cj_1[2*h][2*k+1] - Cj_1[2*h+1][2*k+1]) / 4

            DDj[h][k] = (Cj_1[2*h][2*k] - Cj_1[2*h+1][2*k] -

                          Cj_1[2*h][2*k+1] + Cj_1[2*h+1][2*k+1]) / 4

    WaveletAnal[f'{j}'] = {'f0': Cj}

    WaveletAnal[f'{j}']['DHj'] = DHj

    WaveletAnal[f'{j}']['DVj'] = DVj

    WaveletAnal[f'{j}']['DDj'] = DDj

 

    disp_map(j)     #   解析結果の表示

 

 

while True:

    print(f'0 < j < {n}')

    j = int(input('j = '))

    if j <=0 or j >= n:

        break

    disp_map(j)

 

 

リスト1を実行すると、画像ファイルSample_4.pngが読み込まれて表示される(図1)。画像サイズは512ピクセル×512ピクセルであり、である。画像ファイルを512ピクセル×512ピクセルのサイズに変換するサンプルスクリプトも本サイトの後半に用意している。

図1

 

図1のフォームを閉じると、レベル1のウェーブレット解析が始まる。レベル1の解析が終わると、解析結果が図示される(図2)。

図2

 

表示画面の右下にのマップが表示されている。右上は、ウェーブレットの垂直成分、左下はウェーブレットの水平成分、左上が対角成分である。プログラム中では、画像はnumpyndarray型で表されているが、表示はOpneCVで行っている。OpenCVでの縦座標はndarray型の行に、横座標はndarray型の列に対応しているが、このことは本稿の最後の附記で確認している。

図2のフォームを閉じると、次のレベル、すなわちレベル2の解析が始まり、解析が終了すると、そお結果が表示される(図3)。

図3

 

図3のフォームを閉じると、次のレベル3の解析へと進む。この手順を繰り返すと、レベル8まで進む。画像の大きさが512ピクセル×512ピクセルのときは、であるので、レベル8の解析が最終レベルとなる(図4)。

図4

 

図4のフォームを閉じると、端末画面は以下のようになっている。

 

= RESTART: D:/WaveletAnal/wa_files/WaveAnal.py

Level 1 started.

Level 2 started.

Level 3 started.

Level 4 started.

Level 5 started.

Level 6 started.

Level 7 started.

Level 8 started.

0 < j < 9

j =

 

jに解析結果のレベル値jを設定すると、そのレベルの結果が表示される。例えば、5を設定すると、下図(図5)のようになる。

図5

 

図5のフォームを閉じて、jに範囲外の値、例えば、0を設定すると、プログラムの実行終了である。

 

0 < j < 9

j = 5

0 < j < 9

j = 0

 

 

附記 OpneCVの座標軸とnumpyndarray型の行・列との関係

 

画像を表すNumpyndarray型配列の行は、OpenCVの画像において縦軸に対応し、列はOpenCVの画像の横軸に対応している。このことを確認するスクリプトをリスト2のように用意した。

リスト2は、ライブラリopencv-pythonnumpyおよびmatplotlibがインストールされておれば実行できる。

Windowsの場合のインストールは、例えば

 

pip install opencv-python numpy matplotlib

 

とインストールしておけばよい。

Import

 

import cv2

 

である。

 

LinuxUbuntu/CentOS Stream)の場合のOpenCV 2のインストールは、AnacondaPython3.10の仮想環境において、

 

conda install opencv

 

でインストールできた。

ImportWindowsの場合と同じく

 

import cv2

 

である。

 

 

リスト2 Numpyndarray型配列とOpneCVの画像との対応

import cv2         # pip install opencv-python

import numpy as np

import matplotlib.pyplot as plt

 

img = np.full((300, 500, 3), 128, dtype = np.uint8)

#

#      Numpyndarray型配列の要素に値を設定

#

for i in range(130, 170):

    for j in range(500):

        img[i][j][1] = 255

#

#     OpenCVの関数で描画

#

cv2.rectangle(img, (225, 0), (275, 300), (0, 0, 255), thickness = -1)

#

#     画像を表すndarray型配列の表示

#

plt.imshow(img)

plt.show()

 

 

リスト2を実行すると図Aのフォームが表示される。

A

 

横の薄緑色の帯が、ndarray型配列imgの要素に直接値を設定して描いたものである。配列imgの行がOpenCVの画像の縦座標に、配列imgの列がOpenCVの画像の横軸に対応していることが分かる。青色の縦の帯は、OpenCVの関数rectangleで描いたものである。第1座標が横座標に、第2座標が縦座標に対応していることが分かる。

 

 

画像サイズの変換

 

画僧のサイズを512ピクセル×512ピクセルのサイズに変換するスクリプトをリスト3のように作成した。

 

 

リスト3 画像サイズの変換スクリプト

 

import cv2

import matplotlib.pyplot as plt

 

img = cv2.imread(input('File name = '))      #   画像の読み込み

plt.imshow(img)

plt.title('Read-in Image')

plt.show()

 

img1 = cv2.resize(img, dsize = (512, 512))   #   画像のリサイズ

plt.imshow(img1)

plt.title('Resized Image')

plt.show()

fn_img = input('Output file name = ')

cv2.imwrite(fn_img, img1)                    #   画像の保存

print(fn_img, 'was saved.')

 

 

リスト3のスクリプトを実行すると、入力画像ファイル名の設定が求められる。

 

File name =

 

サイズを変換したい画像ファイルの名前を設定する。

 

File name = Sample_p0.png

 

名前を設定すると読み込まれて、読み込まれた画像が表示される(図S.1)。

S.1

 

フォーム右上角のX印をクリックして閉じると、サイズ変換後の画像が表示される。サイズの変換は、図S.1の座標軸の値と座右S.2の座標軸の値が異なることに反映されている。

 

S.2

 

S.2のフォームを閉じると、変換された画像を保存するファイル名の設定が求められる。

 

Output file name = Sample_rs.png

 

ファイル名を設定すると、画像が保存されてプログラムの終了である。

端末画面には、次のメッセージが表示される。

 

Sample_rs.png was saved.

 

保存された画像ファイルを、リスト1のスクリプトの入力画像の設定のコードを

 

img1 = cv2.imread('Sample_rs.png')

 

に変えて解析してみると以下のようになる。

 

 

引用文献

 

金谷健一(2003)これなら分かる応用数学教室.共立出版

Nason, G. P. (2008) Wavelet methods in statistics with R. Springer

 

 

なお、Pythonによるデータ分析の解説を<岡本安晴「いまさら聞けないPythonでデータ分析」丸善出版>で行っている。

 

 

 

カラー画像のウェーブレット解析

 

カラー画像のウェーブレット解析のスクリプト例をリストC1に示す。2次元ウェーブレット解析の基本的考え方はWaveAnal.pdfに示した。カラー画像のウェーブレット解析を、RGBの各チャンネルに対応して行った。入力画像の大きさは任意としたが、読み込み後、512ピクセル×512ピクセルに調整した。リストC1のスクリプトファイルとサンプル画像ファイルは、まとめて圧縮ファイルwa_color_files.zipとした。ダウンロード展開して自由に利用できる。

リストC1のスクリプトを次のコマンドで実行する。

 

PS ******\wa_color_files> python wa_color.py

 

入力画像ファイル名を聞いてくるので、設定する。

 

File name = sample_pctr.png

 

上の例では、sample_pctr.pngを設定している。入力画像ファイルは、スクリプトファイルと同じフォルダに置いておくと指定が簡単である。

入力画像ファイルを設定すると、読み込まれた画像が表示される(図C1)。

C1

 

C1のフォームを閉じると、512ピクセル×512ピクセルに調整された画像が表示される(図C2)。

C2

 

画像の座標軸の値から、512ピクセル×512ピクセルに調整されていることが分かる。

C2のフォームを閉じると、レベル1でのウェーブレット解析が始まり、終了すると解析結果が表示される(図C3)。

C3

 

C3のフォームを閉じると、レベル2の解析が行われ、解析結果が表示される(図C4)。

C4

 

C4のフォームを閉じると、次のレベルの解析が行われ、結果が表示される。このフォームを閉じて、次の解析の結果が表示されるという手順が繰り返されて、図C5のレベル8の結果の表示まで進む。

C5

 

C5のフォームを閉じると、すべてのレベルの解析が終了となり、どのレベルでの解析結果を表示するか聞いてくる。

 

0 < j < 9

j =

 

レベル5の結果を表示するときは、次のように5と設定すればよい。

 

0 < j < 9

j = 5

 

レベルとして5を設定すると、レベル5での解析結果が表示される(図6)。

C6

 

レベルとして範囲外の値を設定すると、プログラムの実行終了である。次の例では、0を設定している。

 

0 < j < 9

j = 0

PS ******\wa_color_files>

 

 

 

リストC1のスクリプトは、サンプル画像ファイルとともに圧縮ファイルwa_color_files.zipにまとめた。ダウンロード展開すれば自由に利用できる。

 

リストC1 カラー画像対応のウェブレット解析(wa_color.py

 

"""

           Wavelet Analysis of Color Image

          

             Yasuharu Okamoto, 2023.11

 

"""

import cv2          # pip install opencv-python

import numpy as np

import matplotlib.pyplot as plt

 

img1 = cv2.imread(input('File name = '))      #   画像ファイルの読み込み

img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)  #   RGBに変換

plt.imshow(img1)

plt.title('Input image')

plt.show()

 

img1 = cv2.resize(img1, dsize = (512, 512))   #   512×512に調整

plt.imshow(img1)

plt.title('Resized image')

plt.show()

 

img1 = np.array(img1, dtype='float') / 255    #   int8からfloatへの変更

 

 

#

#   ウェーブレット解析結果を辞書型のリストWaveletAnalに保存

#

 

d1 = {'0': {'f0': img1[:,:,0]}}    #  R-channel

d2 = {'0': {'f0': img1[:,:,1]}}    #  G-channel

d3 = {'0': {'f0': img1[:,:,2]}}    #  B-channel

WaveletAnal = [d1, d2, d3]

 

n = 9

N = 2**n

 

#

#      レベルjの解析結果の図示

#

def disp_map(j):

    Cj = [0]*3

    DHj = [0]*3

    DVj = [0]*3

    DDj = [0]*3

    for i in range(3):

        Cj[i] = WaveletAnal[i][f'{j}']['f0']   

        DHj[i] = WaveletAnal[i][f'{j}']['DHj']

        DVj[i] = WaveletAnal[i][f'{j}']['DVj']

        DDj[i] = WaveletAnal[i][f'{j}']['DDj']

    #

    #    画像を配列imgsに設定

    #    大きさ512×512を4分割して使用

    #

    imgs = np.full((512, 512, 3), 0.5)

    for h in range((2**(n-j))):

        for k in range((2**(n-j))):

            for ii in range((2**j)//2):       #   スケールに合わせて引き延ばし

                for jj in range((2**j)//2):

                    for kk in range(3):       #   赤、緑、青の3チャンネル

                        #

                        #      C_hkの値

                        #

                        imgs[512//2 + ((2**j)//2)*h + ii]\

                                    [512//2 + ((2**j)//2)*k + jj][kk] \

                            = Cj[kk][h][k] #/ 255

                        #

                        #     DH, DV, DDは、値0を0.5にシフト

                        #

                        imgs[512//2 + ((2**j)//2)*h + ii][((2**j)//2)*k + jj][kk] \

                            = DHj[kk][h][k] + 0.5 

                        imgs[((2**j)//2)*h + ii][(512//2) + \

                                                 ((2**j)//2)*k + jj][kk] \

                            = DVj[kk][h][k] + 0.5

                        imgs[((2**j)//2)*h + ii][((2**j)//2)*k + jj][kk] \

                            = DDj[kk][h][k] + 0.5

                   

    plt.imshow(imgs)    #   画像配列imgsの表示

    plt.xticks([])

    plt.yticks([])

    plt.title(f'j = {j}')

    plt.show()

 

print('\nWavelet analysis started.')

 

for j in range(1,n):

    #      レベルjの解析

    print(f'Level {j} started.')

    Cj_1 = [0]*3

    for i in range(3):

        Cj_1[i] = WaveletAnal[i][f'{j-1}']['f0']

    #print('Cj_1 =\n', Cj_1)

   

    Cj = np.empty((3, 2**(n-j), 2**(n-j)))

    DHj = np.empty((3, 2**(n-j), 2**(n-j)))

    DVj = np.empty((3, 2**(n-j), 2**(n-j)))

    DDj = np.empty((3, 2**(n-j), 2**(n-j)))

    #

    #    下降サンプリング

    #

    for h in range(2**(n-j)):

        for k in range(2**(n-j)):

            for i in range(3):

                #print('h =', h, '  k =', k, '  i =', i)

                Cj[i][h][k] = (Cj_1[i][2*h][2*k] + Cj_1[i][2*h+1][2*k] +

                              Cj_1[i][2*h][2*k+1] + Cj_1[i][2*h+1][2*k+1]) / 4

                DHj[i][h][k] = (Cj_1[i][2*h][2*k] - Cj_1[i][2*h+1][2*k] +

                              Cj_1[i][2*h][2*k+1] - Cj_1[i][2*h+1][2*k+1]) / 4

                DVj[i][h][k] = (Cj_1[i][2*h][2*k] + Cj_1[i][2*h+1][2*k] -

                              Cj_1[i][2*h][2*k+1] - Cj_1[i][2*h+1][2*k+1]) / 4

                DDj[i][h][k] = (Cj_1[i][2*h][2*k] - Cj_1[i][2*h+1][2*k] -

                              Cj_1[i][2*h][2*k+1] + Cj_1[i][2*h+1][2*k+1]) / 4

    for i in range(3):

        WaveletAnal[i][f'{j}'] = {'f0': Cj[i]}

        WaveletAnal[i][f'{j}']['DHj'] = DHj[i]

        WaveletAnal[i][f'{j}']['DVj'] = DVj[i]

        WaveletAnal[i][f'{j}']['DDj'] = DDj[i]

 

    disp_map(j)     #   解析結果の表示

 

 

while True:

    print(f'0 < j < {n}')

    j = int(input('j = '))

    if j <=0 or j >= n:

        break

    disp_map(j)      #    レベルjの表示

 

 

 

 

Home