Jika saya memahami metode 1 Anda dengan benar, dengan itu, jika Anda menggunakan wilayah simetris sirkuler dan melakukan rotasi di sekitar pusat wilayah, Anda akan menghilangkan ketergantungan wilayah pada sudut rotasi dan mendapatkan perbandingan yang lebih adil dengan fungsi jasa antara sudut rotasi berbeda. Saya akan menyarankan metode yang pada dasarnya setara dengan itu, tetapi menggunakan gambar penuh dan tidak memerlukan rotasi gambar berulang, dan akan mencakup penyaringan low-pass untuk menghapus pixel grid anisotropy dan untuk denoising.
Gradien dari gambar dengan filter isotropik low-pass
Pertama, mari kita hitung vektor gradien lokal di setiap piksel untuk saluran warna hijau di gambar sampel ukuran penuh.
Saya mendapatkan kernel diferensiasi horizontal dan vertikal dengan membedakan respons impuls ruang kontinu dari filter low-pass yang ideal dengan respons frekuensi melingkar datar yang menghilangkan efek dari pilihan sumbu gambar dengan memastikan bahwa tidak ada tingkat detail yang berbeda secara diagonal dibandingkan secara horizontal atau vertikal, dengan mengambil sampel fungsi yang dihasilkan, dan dengan menerapkan jendela kosinus yang diputar:
hx[x,y]=⎧⎩⎨⎪⎪0−ω2cxJ2(ωcx2+y2−−−−−−√)2π(x2+y2)if x=y=0,otherwise,hy[x,y]=⎧⎩⎨⎪⎪0−ω2cyJ2(ωcx2+y2−−−−−−√)2π(x2+y2)if x=y=0,otherwise,(1)
dimana J2 adalah fungsi Bessel urutan kedua dari jenis pertama, dan ωcadalah frekuensi cut-off dalam radian. Sumber python (tidak memiliki tanda minus Persamaan 1):
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()
Gambar 1. Jendela kosinus diputar 2-d.
Gambar 2. Inti diferensiasi low-pass isotropik windowed, untuk frekuensi cut-off yang berbeda ωcpengaturan. Top: omega_c = np.pi
, tengah: omega_c = np.pi/4
, bottom: omega_c = np.pi/16
. Tanda minus Persamaan. 1 ditinggalkan. Kernel vertikal terlihat sama tetapi telah diputar 90 derajat. Sejumlah bobot kernel horisontal dan vertikal, dengan bobotcos(ϕ) dan sin(ϕ), masing-masing, memberikan kernel analisis dengan tipe yang sama untuk sudut gradien ϕ.
Diferensiasi respon impuls tidak memengaruhi bandwidth, seperti yang dapat dilihat oleh 2-d fast Fourier transform (FFT), dengan Python:
# 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()
Gambar 3. Besarnya FFT 2-d hx. Dalam domain frekuensi, diferensiasi muncul sebagai penggandaan dari band pass sirkular datar olehωx, dan dengan pergeseran fasa 90 derajat yang tidak terlihat dalam besarnya.
Untuk melakukan konvolusi untuk saluran hijau dan untuk mengumpulkan histogram vektor gradien 2-d, untuk inspeksi visual, dengan Python:
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)))
Ini juga memotong data, membuang (N - 1)//2
piksel dari setiap tepi yang terkontaminasi oleh batas gambar persegi panjang, sebelum analisis histogram.
π
π2
π4
π8
π16
π32
π64
-0
Gambar 4. Histogram gradien vektor 2-d, untuk frekuensi cutoff filter low-pass yang berbeda ωcpengaturan. Dalam rangka: pertama dengan N=41
: omega_c = np.pi
, omega_c = np.pi/2
, omega_c = np.pi/4
(sama seperti di Python listing), omega_c = np.pi/8
, omega_c = np.pi/16
, maka: N=81
: omega_c = np.pi/32
, N=161
: omega_c = np.pi/64
. Denoising dengan low-pass filtering mempertajam orientasi gradien tepi jejak sirkuit dalam histogram.
Panjang vektor tertimbang arah rata-rata melingkar
Ada metode Yamartino untuk menemukan arah angin "rata-rata" dari beberapa sampel vektor angin dalam satu kali melewati sampel. Ini didasarkan pada rata - rata jumlah sirkuler , yang dihitung sebagai pergeseran kosinus yang merupakan jumlah kosinus yang masing-masing digeser oleh kuantitas sirkular periode2π. Kita dapat menggunakan versi vektor panjang tertimbang dari metode yang sama, tetapi pertama-tama kita harus menyatukan semua arah yang modulo samaπ/2. Kita dapat melakukan ini dengan mengalikan sudut setiap vektor gradien[Xk,Yk] oleh 4, menggunakan representasi bilangan kompleks:
Zk=(Xk+Yki)4X2k+Y2k−−−−−−−√3=X4k−6X2kY2k+Y4k+(4X3kYk−4XkY3k)iX2k+Y2k−−−−−−−√3,(2)
memuaskan |Zk|=X2k+Y2k−−−−−−−√ dan kemudian menafsirkan bahwa fase Zk dari −π untuk π mewakili sudut dari −π/4 untuk π/4, dengan membagi fase rata-rata lingkaran yang dihitung dengan 4:
ϕ=14atan2(∑kIm(Zk),∑kRe(Zk))(3)
dimana ϕ adalah perkiraan orientasi gambar.
Kualitas estimasi dapat dinilai dengan melakukan lintasan lain melalui data dan dengan menghitung rata-rata jarak melingkar persegi tertimbang ,MSCD, di antara fase-fase bilangan kompleks Zk dan fase rata-rata lingkaran yang diperkirakan 4ϕ, dengan |Zk| sebagai berat:
MSCD=∑k|Zk|(1−cos(4ϕ−atan2(Im(Zk),Re(Zk))))∑k|Zk|=∑k|Zk|2((cos(4ϕ)−Re(Zk)|Zk|)2+(sin(4ϕ)−Im(Zk)|Zk|)2)∑k|Zk|=∑k(|Zk|−Re(Zk)cos(4ϕ)−Im(Zk)sin(4ϕ))∑k|Zk|,(4)
yang diminimalkan oleh ϕdihitung per Persamaan. 3. Dengan 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)")
Berdasarkan mpmath
eksperimen saya (tidak diperlihatkan), saya pikir kami tidak akan kehabisan angka numerik bahkan untuk gambar yang sangat besar. Untuk pengaturan filter yang berbeda (beranotasi) hasilnya adalah, seperti yang dilaporkan antara -45 dan 45 derajat:
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)
Pemfilteran low-pass yang kuat tampak berguna, mengurangi sudut yang setara dengan RMSCD dihitung sebagai acos(1−MSCD). Tanpa jendela cosine diputar 2-d, beberapa hasil akan mati sekitar satu derajat (tidak ditampilkan), yang berarti bahwa penting untuk melakukan windowing yang tepat dari filter analisis. Sudut setara RMSCD tidak secara langsung merupakan estimasi kesalahan dalam estimasi sudut, yang seharusnya jauh lebih sedikit.
Alternatif fungsi berat persegi panjang
Mari kita coba kuadrat dari panjang vektor sebagai fungsi bobot alternatif, dengan:
Zk=(Xk+Yki)4X2k+Y2k−−−−−−−√2=X4k−6X2kY2k+Y4k+(4X3kYk−4XkY3k)iX2k+Y2k,(5)
Dengan 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)")
Berat persegi panjang mengurangi sudut setara RMSCD sekitar satu derajat:
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)
Ini sepertinya fungsi berat badan yang sedikit lebih baik. Saya menambahkan juga cutoffsωc=π/32 dan ωc=π/64. Mereka menggunakan yang lebih besarN
menghasilkan pemotongan gambar yang berbeda dan nilai MSCD yang tidak dapat dibandingkan.
Histogram 1-d
Manfaat dari fungsi berat persegi panjang lebih jelas dengan histogram tertimbang 1-d Zkfase. Skrip 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()
Gambar 5. Histogram interpolasi tertimbang linear dari sudut vektor gradien, dibungkus dengan −π/4…π/4dan ditimbang dengan (dalam urutan dari bawah ke atas di puncak): tanpa bobot (hitam), panjang vektor gradien (merah), kuadrat panjang vektor gradien (biru). Lebar bin adalah 0,1 derajat. Cutoff filter adalah omega_c = np.pi/4
, sama seperti dalam daftar Python. Sosok bawah diperbesar di puncak.
Filter matematika yang mudah dikendalikan
Kami telah melihat bahwa pendekatan ini berhasil, tetapi akan lebih baik jika memiliki pemahaman matematika yang lebih baik. Itux dan ytanggapan impuls filter diferensiasi diberikan oleh Persamaan. Gambar 1 dapat dipahami sebagai fungsi dasar untuk membentuk respons impuls dari filter diferensiasi yang dapat disembelih yang disampel dari rotasi sisi kanan persamaan untukhx[x,y](Persamaan 1). Ini lebih mudah dilihat dengan mengkonversi Persamaan. 1 ke koordinat kutub:
hx(r,θ)=hx[rcos(θ),rsin(θ)]hy(r,θ)=hy[rcos(θ),rsin(θ)]f(r)=⎧⎩⎨0−ω2crcos(θ)J2(ωcr)2πr2if r=0,otherwise=cos(θ)f(r),=⎧⎩⎨0−ω2crsin(θ)J2(ωcr)2πr2if r=0,otherwise=sin(θ)f(r),=⎧⎩⎨0−ω2crJ2(ωcr)2πr2if r=0,otherwise,(6)
where both the horizontal and the vertical differentiation filter impulse responses have the same radial factor function f(r). Any rotated version h(r,θ,ϕ) of hx(r,θ) by steering angle ϕ is obtained by:
h(r,θ,ϕ)=hx(r,θ−ϕ)=cos(θ−ϕ)f(r)(7)
The idea was that the steered kernel h(r,θ,ϕ) can be constructed as a weighted sum of hx(r,θ) and hx(r,θ), with cos(ϕ) and sin(ϕ) as the weights, and that is indeed the case:
cos(ϕ)hx(r,θ)+sin(ϕ)hy(r,θ)=cos(ϕ)cos(θ)f(r)+sin(ϕ)sin(θ)f(r)=cos(θ−ϕ)f(r)=h(r,θ,ϕ).(8)
We will arrive at an equivalent conclusion if we think of the isotropically low-pass filtered signal as the input signal and construct a partial derivative operator with respect to the first of rotated coordinates xϕ, yϕ rotated by angle ϕ from coordinates x, y. (Derivation can be considered a linear-time-invariant system.) We have:
x=cos(ϕ)xϕ−sin(ϕ)yϕ,y=sin(ϕ)xϕ+cos(ϕ)yϕ(9)
Using the chain rule for partial derivatives, the partial derivative operator with respect to xϕ can be expressed as a cosine and sine weighted sum of partial derivatives with respect to x and y:
∂∂xϕ=∂x∂xϕ∂∂x+∂y∂xϕ∂∂y=∂(cos(ϕ)xϕ−sin(ϕ)yϕ)∂xϕ∂∂x+∂(sin(ϕ)xϕ+cos(ϕ)yϕ)∂xϕ∂∂y=cos(ϕ)∂∂x+sin(ϕ)∂∂y(10)
A question that remains to be explored is how a suitably weighted circular mean of gradient vector angles is related to the angle ϕ of in some way the "most activated" steered differentiation filter.
Possible improvements
To possibly improve results further, the gradient can be calculated also for the red and blue color channels, to be included as additional data in the "average" calculation.
I have in mind possible extensions of this method:
1) Use a larger set of analysis filter kernels and detect edges rather than detecting gradients. This needs to be carefully crafted so that edges in all directions are treated equally, that is, an edge detector for any angle should be obtainable by a weighted sum of orthogonal kernels. A set of suitable kernels can (I think) be obtained by applying the differential operators of Eq. 11, Fig. 6 (see also my Mathematics Stack Exchange post) on the continuous-space impulse response of a circularly symmetric low-pass filter.
limh→0∑4N+1N=0(−1)nf(x+hcos(2πn4N+2),y+hsin(2πn4N+2))h2N+1,limh→0∑4N+1N=0(−1)nf(x+hsin(2πn4N+2),y+hcos(2πn4N+2))h2N+1(11)
Figure 6. Dirac delta relative locations in differential operators for construction of higher-order edge detectors.
2) The calculation of a (weighted) mean of circular quantities can be understood as summing of cosines of the same frequency shifted by samples of the quantity (and scaled by the weight), and finding the peak of the resulting function. If similarly shifted and scaled harmonics of the shifted cosine, with carefully chosen relative amplitudes, are added to the mix, forming a sharper smoothing kernel, then multiple peaks may appear in the total sum and the peak with the largest value can be reported. With a suitable mixture of harmonics, that would give a kind of local average that largely ignores outliers away from the main peak of the distribution.
Alternative approaches
It would also be possible to convolve the image by angle ϕ and angle ϕ+π/2 rotated "long edge" kernels, and to calculate the mean square of the pixels of the two convolved images. The angle ϕ that maximizes the mean square would be reported. This approach might give a good final refinement for the image orientation finding, because it is risky to search the complete angle ϕ space at large steps.
Another approach is non-local methods, like cross-correlating distant similar regions, applicable if you know that there are long horizontal or vertical traces, or features that repeat many times horizontally or vertically.