私があなたの方法1を正しく理解している場合、それを使用して、円対称の領域を使用し、領域の中心を中心に回転させた場合、回転角度に対する領域の依存性を排除し、間のメリット関数によってより公平な比較を得るでしょう。異なる回転角度。基本的にそれと同等の方法を提案しますが、画像全体を使用し、画像の回転を繰り返す必要がなく、ローパスフィルタリングを含めて、ピクセルグリッドの異方性を除去し、ノイズを除去します。
等方性ローパスフィルター処理されたイメージの勾配
最初に、フルサイズのサンプル画像の緑のカラーチャネルの各ピクセルでローカルグラデーションベクトルを計算します。
理想的なローパスフィルターの連続空間インパルス応答をフラットな円形周波数応答で微分することにより、水平および垂直微分カーネルを導出しました。これにより、対角線上で比較される詳細レベルに違いがないことを確認することにより、画像軸の選択の影響を排除します。結果の関数をサンプリングし、回転した余弦ウィンドウを適用することにより、水平または垂直に:
hバツ[ x 、y] =⎧⎩⎨⎪⎪0−ω2cバツJ2(ωcバツ2+y2−−−−−−√)2個のπ(バツ2+y2)x =の場合 yの= 0 、さもないと、hy[ x 、y] =⎧⎩⎨⎪⎪0−ω2cyJ2(ωcバツ2+y2−−−−−−√)2個のπ(バツ2+y2)x =の場合 yの= 0 、さもないと、(1)
ここで、は第1種の2次ベッセル関数、はラジアン単位のカットオフ周波数です。Pythonソース(式1のマイナス記号はありません):J2ωc
import matplotlib.pyplot as plt
import scipy
import scipy.special
import numpy as np
def rotatedCosineWindow(N): # N = horizontal size of the targeted kernel, also its vertical size, must be odd.
return np.fromfunction(lambda y, x: np.maximum(np.cos(np.pi/2*np.sqrt(((x - (N - 1)/2)/((N - 1)/2 + 1))**2 + ((y - (N - 1)/2)/((N - 1)/2 + 1))**2)), 0), [N, N])
def circularLowpassKernelX(omega_c, N): # omega = cutoff frequency in radians (pi is max), N = horizontal size of the kernel, also its vertical size, must be odd.
kernel = np.fromfunction(lambda y, x: omega_c**2*(x - (N - 1)/2)*scipy.special.jv(2, omega_c*np.sqrt((x - (N - 1)/2)**2 + (y - (N - 1)/2)**2))/(2*np.pi*((x - (N - 1)/2)**2 + (y - (N - 1)/2)**2)), [N, N])
kernel[(N - 1)//2, (N - 1)//2] = 0
return kernel
def circularLowpassKernelY(omega_c, N): # omega = cutoff frequency in radians (pi is max), N = horizontal size of the kernel, also its vertical size, must be odd.
kernel = np.fromfunction(lambda y, x: omega_c**2*(y - (N - 1)/2)*scipy.special.jv(2, omega_c*np.sqrt((x - (N - 1)/2)**2 + (y - (N - 1)/2)**2))/(2*np.pi*((x - (N - 1)/2)**2 + (y - (N - 1)/2)**2)), [N, N])
kernel[(N - 1)//2, (N - 1)//2] = 0
return kernel
N = 41 # Horizontal size of the kernel, also its vertical size. Must be odd.
window = rotatedCosineWindow(N)
# Optional window function plot
#plt.imshow(window, vmin=-np.max(window), vmax=np.max(window), cmap='bwr')
#plt.colorbar()
#plt.show()
omega_c = np.pi/4 # Cutoff frequency in radians <= pi
kernelX = circularLowpassKernelX(omega_c, N)*window
kernelY = circularLowpassKernelY(omega_c, N)*window
# Optional kernel plot
#plt.imshow(kernelX, vmin=-np.max(kernelX), vmax=np.max(kernelX), cmap='bwr')
#plt.colorbar()
#plt.show()
図1. 2次元回転コサインウィンドウ。
図2.ウィンドウ化された水平等方性ローパス微分カーネル、異なるカットオフ周波数 ωc設定。上:omega_c = np.pi
、中央:omega_c = np.pi/4
、下:omega_c = np.pi/16
。式のマイナス記号。1つが除外されました。垂直カーネルは同じように見えますが、90度回転されています。重み付きの水平カーネルと垂直カーネルの重み付き合計cos(ϕ ) そして 罪(ϕ )、それぞれ、勾配角度について同じタイプの分析カーネルを提供します φ。
Pythonの2次元高速フーリエ変換(FFT)でわかるように、インパルス応答の微分は帯域幅に影響を与えません。
# Optional FFT plot
absF = np.abs(np.fft.fftshift(np.fft.fft2(circularLowpassKernelX(np.pi, N)*window)))
plt.imshow(absF, vmin=0, vmax=np.max(absF), cmap='Greys', extent=[-np.pi, np.pi, -np.pi, np.pi])
plt.colorbar()
plt.show()
図3. 2次元FFTの大きさ hバツ。周波数領域では、微分はフラットな円形通過帯域の乗算として表示されます。ωバツ、そしてマグニチュードでは見えない90度の位相シフトによって。
緑のチャネルのたたみ込みを実行し、Pythonで目視検査のために2次元勾配ベクトルヒストグラムを収集するには:
import scipy.ndimage
img = plt.imread('sample.tif').astype(float)
X = scipy.ndimage.convolve(img[:,:,1], kernelX)[(N - 1)//2:-(N - 1)//2, (N - 1)//2:-(N - 1)//2] # Green channel only
Y = scipy.ndimage.convolve(img[:,:,1], kernelY)[(N - 1)//2:-(N - 1)//2, (N - 1)//2:-(N - 1)//2] # ...
# Optional 2-d histogram
#hist2d, xEdges, yEdges = np.histogram2d(X.flatten(), Y.flatten(), bins=199)
#plt.imshow(hist2d**(1/2.2), vmin=0, cmap='Greys')
#plt.show()
#plt.imsave('hist2d.png', plt.cm.Greys(plt.Normalize(vmin=0, vmax=hist2d.max()**(1/2.2))(hist2d**(1/2.2)))) # To save the histogram image
#plt.imsave('histkey.png', plt.cm.Greys(np.repeat([(np.arange(200)/199)**(1/2.2)], 16, 0)))
これはまた(N - 1)//2
、ヒストグラム分析の前に、長方形の画像の境界によって汚染された各エッジからのピクセルを破棄して、データをトリミングします。
π
π2
π4
π8
π16
π32
π64
—0
図4.さまざまなローパスフィルターのカットオフ周波数に対する、勾配ベクトルの2次元ヒストグラム ωc設定。順番に:第有するN=41
:omega_c = np.pi
、omega_c = np.pi/2
、omega_c = np.pi/4
(リストのPythonと同じ)、 、omega_c = np.pi/8
、omega_c = np.pi/16
その後:N=81
:omega_c = np.pi/32
、N=161
:omega_c = np.pi/64
。ローパスフィルタリングによるノイズ除去により、ヒストグラムの回路トレースエッジグラディエントの方向がシャープになります。
ベクトル長加重円形平均方向
複数の風ベクトルサンプルからサンプルを1回通過することで「平均的な」風向を見つけるYamartinoの方法があります。これは、循環量の平均に基づいています。これは、周期の循環量によってシフトされたコサインの合計であるコサインのシフトとして計算されます。2個のπ。同じ方法のベクトル長で重み付けされたバージョンを使用できますが、最初に、モジュロが等しいすべての方向をまとめる必要がありますπ/ 2。これを行うには、各勾配ベクトルの角度を掛けます[バツk、Yk] 4で、複素数表現を使用して:
Zk=(バツk+Yk私)4バツ2k+Y2k−−−−−−−√3=バツ4k− 6バツ2kY2k+Y4k+ (4バツ3kYk− 4バツkY3k)私バツ2k+Y2k−−−−−−−√3、(2)
満足 |Zk| =バツ2k+Y2k−−−−−−−√ と後で解釈することによって Zk から - π に π からの角度を表す - π/ 4 に π/ 4、計算された循環平均位相を4で割ることにより、
ϕ =14atan2(Σkイム(Zk)、Σk再(Zk))(3)
どこ φ 推定される画像の向きです。
推定の質は、データをもう一度通過し、平均の重み付けされた正方形の円形距離を計算することによって評価できます。MSCD、複素数のフェーズ間 Zk 推定された循環平均位相 4 φ、 |Zk| 重量として:
MSCD =Σk|Zk| ( 1−cos( 4ϕ−atan2(イム(Zk)、Re(Zk)))))Σk|Zk|=Σk|Zk|2(( cos(4 ϕ )−再(Zk)|Zk|)2+(罪(4 ϕ )−イム(Zk)|Zk|)2)Σk|Zk|=Σk( |Zk| −再(Zk)cos(4 ϕ )− イム(Zk)罪(4 ϕ ))Σk|Zk|、(4)
によって最小化されました φ式ごとに計算 3. Pythonの場合:
absZ = np.sqrt(X**2 + Y**2)
reZ = (X**4 - 6*X**2*Y**2 + Y**4)/absZ**3
imZ = (4*X**3*Y - 4*X*Y**3)/absZ**3
phi = np.arctan2(np.sum(imZ), np.sum(reZ))/4
sumWeighted = np.sum(absZ - reZ*np.cos(4*phi) - imZ*np.sin(4*phi))
sumAbsZ = np.sum(absZ)
mscd = sumWeighted/sumAbsZ
print("rotate", -phi*180/np.pi, "deg, RMSCD =", np.arccos(1 - mscd)/4*180/np.pi, "deg equivalent (weight = length)")
私のmpmath
実験に基づいて(図には示されていません)、非常に大きな画像でも数値の精度が不足することはないと思います。異なるフィルター設定(注釈付き)の場合、出力は-45度から45度の間で報告されます。
rotate 32.29809399495655 deg, RMSCD = 17.057059965741338 deg equivalent (omega_c = np.pi)
rotate 32.07672617150525 deg, RMSCD = 16.699056648843566 deg equivalent (omega_c = np.pi/2)
rotate 32.13115293914797 deg, RMSCD = 15.217534399922902 deg equivalent (omega_c = np.pi/4, same as in the Python listing)
rotate 32.18444156018288 deg, RMSCD = 14.239347706786056 deg equivalent (omega_c = np.pi/8)
rotate 32.23705383489169 deg, RMSCD = 13.63694582160468 deg equivalent (omega_c = np.pi/16)
強力なローパスフィルタリングは有用であると思われ、次のように計算される二乗平均平方根距離(RMSCD)の等価角度を減らします。 acos(1 - MSCD )。2次元回転コサインウィンドウがない場合、結果の一部がある程度ずれます(図には示されていません)。これは、分析フィルターの適切なウィンドウ処理を行うことが重要であることを意味します。RMSCDの等価角度は、角度推定の誤差を直接推定するものではないため、はるかに小さくする必要があります。
代替の正方形の長さの重み関数
代替の重み関数として、ベクトルの長さの2乗を試してみましょう。
Zk=(バツk+Yk私)4バツ2k+Y2k−−−−−−−√2=バツ4k− 6バツ2kY2k+Y4k+ (4バツ3kYk− 4バツkY3k)私バツ2k+Y2k、(5)
Pythonの場合:
absZ_alt = X**2 + Y**2
reZ_alt = (X**4 - 6*X**2*Y**2 + Y**4)/absZ_alt
imZ_alt = (4*X**3*Y - 4*X*Y**3)/absZ_alt
phi_alt = np.arctan2(np.sum(imZ_alt), np.sum(reZ_alt))/4
sumWeighted_alt = np.sum(absZ_alt - reZ_alt*np.cos(4*phi_alt) - imZ_alt*np.sin(4*phi_alt))
sumAbsZ_alt = np.sum(absZ_alt)
mscd_alt = sumWeighted_alt/sumAbsZ_alt
print("rotate", -phi_alt*180/np.pi, "deg, RMSCD =", np.arccos(1 - mscd_alt)/4*180/np.pi, "deg equivalent (weight = length^2)")
正方形の長さの重みにより、RMSCDの等価角度が約1度減少します。
rotate 32.264713568426764 deg, RMSCD = 16.06582418749094 deg equivalent (weight = length^2, omega_c = np.pi, N = 41)
rotate 32.03693157762725 deg, RMSCD = 15.839593856962486 deg equivalent (weight = length^2, omega_c = np.pi/2, N = 41)
rotate 32.11471435914187 deg, RMSCD = 14.315371970649874 deg equivalent (weight = length^2, omega_c = np.pi/4, N = 41)
rotate 32.16968341455537 deg, RMSCD = 13.624896827482049 deg equivalent (weight = length^2, omega_c = np.pi/8, N = 41)
rotate 32.22062839958777 deg, RMSCD = 12.495324176281466 deg equivalent (weight = length^2, omega_c = np.pi/16, N = 41)
rotate 32.22385477783647 deg, RMSCD = 13.629915935941973 deg equivalent (weight = length^2, omega_c = np.pi/32, N = 81)
rotate 32.284350817263906 deg, RMSCD = 12.308297934977746 deg equivalent (weight = length^2, omega_c = np.pi/64, N = 161)
これは少し良い重み関数のようです。カットオフも追加しましたωc= π/ 32 そして ωc= π/ 64。より大きな値を使用するN
と、画像のトリミングが異なり、厳密に比較可能なMSCD値は得られません。
1次元ヒストグラム
平方長の重み関数の利点は、次の1次元の重み付きヒストグラムでより明確になります。 Zkフェーズ。Pythonスクリプト:
# Optional histogram
hist_plain, bin_edges = np.histogram(np.arctan2(imZ, reZ), weights=np.ones(absZ.shape)/absZ.size, bins=900)
hist, bin_edges = np.histogram(np.arctan2(imZ, reZ), weights=absZ/np.sum(absZ), bins=900)
hist_alt, bin_edges = np.histogram(np.arctan2(imZ_alt, reZ_alt), weights=absZ_alt/np.sum(absZ_alt), bins=900)
plt.plot((bin_edges[:-1]+(bin_edges[1]-bin_edges[0]))*45/np.pi, hist_plain, "black")
plt.plot((bin_edges[:-1]+(bin_edges[1]-bin_edges[0]))*45/np.pi, hist, "red")
plt.plot((bin_edges[:-1]+(bin_edges[1]-bin_edges[0]))*45/np.pi, hist_alt, "blue")
plt.xlabel("angle (degrees)")
plt.show()
図5.ラップされた勾配ベクトル角度の線形補間された重み付きヒストグラム - π/ 4…π/ 4重み付け(ピークで下から上へ):重み付けなし(黒)、勾配ベクトル長(赤)、勾配ベクトル長の2乗(青)。ビンの幅は0.1度です。フィルターのカットオフはomega_c = np.pi/4
、Pythonリストと同じでした。下の図はピークで拡大されています。
操縦可能なフィルター演算
このアプローチが機能することを確認しましたが、より良い数学的理解が得られると良いでしょう。のバツ そして y式によって与えられる微分フィルターインパルス応答。1 は、方程式の右辺の回転からサンプリングされた、ステアリング可能な微分フィルターのインパルス応答を形成するための基底関数として理解できます。hバツ[ x 、y](方程式1)。これは、Eq。1から極座標:
hバツ(r 、θ )=hバツ[ r cos(θ )、r sin(θ )]hy(r 、θ )=hy[ r cos(θ )、r sin(θ )]f(r )=⎧⎩⎨0−ω2cr cos(θ )J2(ωcr )2個のπr2r = 0の場合 、さもないと= cos(θ )f(r )、=⎧⎩⎨0−ω2cRの罪(θ )J2(ωcr )2個のπr2r = 0の場合 、さもないと= 罪(θ )f(r )、=⎧⎩⎨0−ω2crJ2(ωcr )2個のπr2r = 0の場合 、さもないと、(6)
ここで、水平と垂直の両方の微分フィルターインパルス応答は同じ動径因子関数を持ちます f(r )。ローテーションバージョンh (r 、θ 、ϕ ) の hバツ(r 、θ ) ステアリング角度によって φ によって取得されます:
h (r 、θ 、ϕ )=hバツ(R 、θ - φ )= COS(θ - φ )F(r )(7)
考えは操縦されたカーネル h (r 、θ 、ϕ ) の加重和として構築できます hバツ(r 、θ ) そして hバツ(r 、θ )、 cos(ϕ ) そして 罪(ϕ ) 重みとして、そしてそれは確かにそうです:
cos(ϕ )hバツ(r 、θ )+ sin(ϕ )hy(r 、θ )= cos(ϕ )cos(θ )f(r )+ 罪(ϕ )罪(θ )f(r )= cos(θ - φ )F(r )= h (r 、θ 、ϕ )。(8)
等方性ローパスフィルター処理された信号を入力信号と見なし、最初の回転座標に関する偏微分演算子を作成すると、同等の結論が得られます。 バツφ、 yφ 角度で回転 φ 座標から バツ、 y。(導出は、線形時間不変システムと見なすことができます。)
x = cos(ϕ )バツφ− 罪(ϕ )yφ、y= 罪(ϕ )バツφ+ cos(ϕ )yφ(9)
偏導関数のチェーンルールを使用すると、バツφ に関する偏導関数のコサインおよびサイン加重和として表すことができます バツ そして y:
∂∂バツφ=∂バツ∂バツφ∂∂バツ+∂y∂バツφ∂∂y=∂( cos(ϕ )バツφ− 罪(ϕ )yφ)∂バツφ∂∂バツ+∂(罪(ϕ )バツφ+ cos(ϕ )yφ)∂バツφ∂∂y= cos(ϕ )∂∂バツ+ 罪(ϕ )∂∂y(10)
まだ検討されていない問題は、勾配ベクトル角度の適切に重み付けされた円形平均がどのように角度に関係するかです φ ある意味で「最も活性化された」操縦微分フィルター。
可能な改善
結果をさらに改善するために、赤と青のカラーチャネルについても勾配を計算し、「平均」計算に追加データとして含めることができます。
私はこの方法の可能な拡張を念頭に置いています:
1)勾配を検出するのではなく、より大きな分析フィルターカーネルのセットを使用して、エッジを検出します。これは、すべての方向のエッジが等しく扱われるように注意深く作成する必要があります。つまり、任意の角度のエッジ検出器は、直交カーネルの加重和によって取得できる必要があります。式(1)の微分演算子を適用することで、適切なカーネルのセットを取得できます(私はそう思います)。円対称ローパスフィルターの連続空間インパルス応答の11、図6(私の数学スタック交換の投稿も参照)。
リムh → 0Σ4 N+ 1N= 0(− 1)んf( x+hcos(2個のπん4 N+ 2)、y+ h 罪(2個のπん4 N+ 2))h2 N+ 1、リムh → 0Σ4 N+ 1N= 0(− 1)んf( x+h罪(2個のπん4 N+ 2)、y+ h cos(2個のπん4 N+ 2))h2 N+ 1(11)
図6. 高次エッジ検出器を構築するための微分演算子のディラックデルタ相対位置。
2)循環量の(加重)平均の計算は、量のサンプルによってシフトされた(および加重によってスケーリングされた)同じ周波数のコサインを合計し、結果の関数のピークを見つけることとして理解できます。慎重に選択された相対振幅を使用して、シフトされた余弦の同様にシフトおよびスケーリングされた高調波が混合に追加され、よりシャープな平滑化カーネルが形成されると、合計に複数のピークが表示され、最大値のピークが報告されます。高調波の適切な混合により、分布のメインピークから離れた外れ値をほとんど無視する一種の局所平均が得られます。
代替アプローチ
画像を角度で畳み込むことも可能です φ と角度 ϕ + π/ 2回転した「長いエッジ」カーネル、および2つの畳み込み画像のピクセルの平均二乗を計算するため。角度φ平均二乗を最大化することが報告されます。このアプローチは、完全な角度を検索するのが危険であるため、画像の向きを見つけるための良い最終的な改善をもたらすかもしれませんφ 大きなステップのスペース。
別のアプローチは、非局所的な方法です。たとえば、長い水平または垂直のトレース、または水平または垂直に何度も繰り返されるフィーチャがあることがわかっている場合に適用できる、遠くの類似した領域を相互相関させます。