24

移動するベースラインに対する時間の経過に伴う輝度の変化を表す一連のビデオ フレームで構成されるデータがあります。これらのビデオでは、発生する可能性のある 2 種類の「イベント」があります。クラスター化されたピクセルの小さなグループの輝度変化で構成される「ローカライズされた」イベントと、フレーム内のほとんどのピクセルに影響を与える汚染された「拡散」イベントです。

ここに画像の説明を入力

輝度の局所的な変化を拡散イベントから分離できるようにしたいと考えています。各フレームの適切にローパス フィルター処理されたバージョンを差し引くことで、これを行うことを計画しています。最適なフィルターを設計するために、フレームのどの空間周波数が拡散およびローカル イベント中に変調されるかを知りたいです。つまり、時間をかけて映画のスペクトログラムを生成したいと考えています。

1D データ (オーディオなど) のスペクトログラムの生成に関する多くの情報を見つけることができますが、2D データのスペクトログラムの生成についてはあまり知りません。私がこれまでに試したことは、フレームのフーリエ変換から 2D パワー スペクトルを生成し、次に DC 成分について極変換を実行し、角度全体で平均して 1D パワー スペクトルを取得することです。

ここに画像の説明を入力

次に、これをムービーのすべてのフレームに適用し、経時的なスペクトル パワーのラスター プロットを生成します。

ここに画像の説明を入力

これは賢明なアプローチのように思えますか? 2D データのスペクトル解析を行うためのより「標準的な」アプローチはありますか?

これが私のコードです:

import numpy as np
# from pyfftw.interfaces.scipy_fftpack import fft2, fftshift, fftfreq
from scipy.fftpack import fft2, fftshift, fftfreq
from matplotlib import pyplot as pp
from matplotlib.colors import LogNorm
from scipy.signal import windows
from scipy.ndimage.interpolation import map_coordinates

def compute_2d_psd(img, doplot=True, winfun=windows.hamming, winfunargs={}):

    nr, nc = img.shape
    win = make2DWindow((nr, nc), winfun, **winfunargs)

    f2 = fftshift(fft2(img*win))
    psd = np.abs(f2*f2)
    pol_psd = polar_transform(psd, centre=(nr//2, nc//2))

    mpow = np.nanmean(pol_psd, 0)
    stdpow = np.nanstd(pol_psd, 0)

    freq_r = fftshift(fftfreq(nr))
    freq_c = fftshift(fftfreq(nc))
    pos_freq = np.linspace(0, np.hypot(freq_r[-1], freq_c[-1]), 
        pol_psd.shape[1])

    if doplot:
        fig,ax = pp.subplots(2,2)

        im0 = ax[0,0].imshow(img*win, cmap=pp.cm.gray)
        ax[0,0].set_axis_off()
        ax[0,0].set_title('Windowed image')

        lnorm = LogNorm(vmin=psd.min(), vmax=psd.max())
        ax[0,1].set_axis_bgcolor('k')
        im1 = ax[0,1].imshow(psd, extent=(freq_c[0], freq_c[-1], 
            freq_r[0], freq_r[-1]), aspect='auto', 
            cmap=pp.cm.hot, norm=lnorm)
        # cb1 = pp.colorbar(im1, ax=ax[0,1], use_gridspec=True)
        # cb1.set_label('Power (A.U.)')
        ax[0,1].set_title('2D power spectrum')

        ax[1,0].set_axis_bgcolor('k')
        im2 = ax[1,0].imshow(pol_psd, cmap=pp.cm.hot, norm=lnorm, 
            extent=(pos_freq[0],pos_freq[-1],0,360), 
            aspect='auto')
        ax[1,0].set_ylabel('Angle (deg)')
        ax[1,0].set_xlabel('Frequency (cycles/px)')
        # cb2 = pp.colorbar(im2, ax=(ax[0,1],ax[1,1]), use_gridspec=True)
        # cb2.set_label('Power (A.U.)')
        ax[1,0].set_title('Polar-transformed power spectrum')

        ax[1,1].hold(True)
        # ax[1,1].fill_between(pos_freq, mpow - stdpow, mpow + stdpow, 
        #   color='r', alpha=0.3)
        ax[1,1].axvline(0, c='k', ls='--', alpha=0.3)
        ax[1,1].plot(pos_freq, mpow, lw=3, c='r')
        ax[1,1].set_xlabel('Frequency (cycles/px)')
        ax[1,1].set_ylabel('Power (A.U.)')
        ax[1,1].set_yscale('log')
        ax[1,1].set_xlim(-0.05, None)
        ax[1,1].set_title('1D power spectrum')

        fig.tight_layout()

    return mpow, stdpow, pos_freq

def make2DWindow(shape,winfunc,*args,**kwargs):
    assert callable(winfunc)
    r,c = shape
    rvec = winfunc(r,*args,**kwargs)
    cvec = winfunc(c,*args,**kwargs)
    return np.outer(rvec,cvec)

def polar_transform(image, centre=(0,0), n_angles=None, n_radii=None):
    """
    Polar transformation of an image about the specified centre coordinate
    """
    shape = image.shape
    if n_angles is None:
        n_angles = shape[0]
    if n_radii is None:
        n_radii = shape[1]
    theta = -np.linspace(0, 2*np.pi, n_angles, endpoint=False).reshape(-1,1)
    d = np.hypot(shape[0]-centre[0], shape[1]-centre[1])
    radius = np.linspace(0, d, n_radii).reshape(1,-1)
    x = radius * np.sin(theta) + centre[0]
    y = radius * np.cos(theta) + centre[1]

    # nb: map_coordinates can give crazy negative values using higher order
    # interpolation, which introduce nans when you take the log later on
    output = map_coordinates(image, [x, y], order=1, cval=np.nan, 
        prefilter=True)
    return output
4

1 に答える 1

2

あなたが説明するアプローチは、一般的にこの分析を行うための最良の方法だと思います。

ただし、コードにエラーが見つかりました。なので:

np.abs(f2*f2)

は複素数配列 f2 の PSD ではありません。f2 自体ではなく複素共役を掛ける必要があります (|f2^2| は |f2|^2 と同じではありません)。

代わりに、次のようなことをする必要があります

(f2*np.conjugate(f2)).astype(float)

または、よりきれいに:

np.abs(f2)**2.

2D パワー スペクトルの振動は、この種のエラーの明らかな兆候です (私は自分自身でこれを行ったことがあります!)。

于 2015-07-08T12:47:54.387 に答える