Se capisco correttamente il tuo metodo 1, con esso, se utilizzassi una regione circolare simmetrica e facessi la rotazione attorno al centro della regione, elimineresti la dipendenza della regione dall'angolo di rotazione e otterresti un confronto più equo dalla funzione di merito tra diversi angoli di rotazione. Suggerirò un metodo che è essenzialmente equivalente a quello, ma utilizza l'immagine completa e non richiede una rotazione dell'immagine ripetuta, e includerà il filtro passa-basso per rimuovere l'anisotropia della griglia di pixel e per il denoising.
Gradiente di immagine filtrata isotropicamente passa-basso
Innanzitutto, calcoliamo un vettore gradiente locale su ciascun pixel per il canale di colore verde nell'immagine di esempio a dimensione intera.
Ho derivato kernel di differenziazione orizzontale e verticale differenziando la risposta all'impulso dello spazio continuo di un filtro passa basso ideale con una risposta di frequenza circolare piatta che rimuove l'effetto della scelta degli assi dell'immagine assicurando che non vi sia un diverso livello di dettaglio in diagonale rispetto in orizzontale o in verticale, campionando la funzione risultante e applicando una finestra di coseno ruotata:
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)
dove è una funzione di Bessel del 2o ordine del primo tipo e è la frequenza di taglio in radianti. Python source (non ha i segni meno dell'Eq. 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()
Figura 1. Finestra coseno ruotata 2-d.
Figura 2. Gherigli di differenziazione isotropica orizzontale passa-basso a finestra, per diverse impostazioni di frequenza di taglio . Top: , centrale: , in basso: . Il segno meno dell'Eq. 1 è stato lasciato fuori. I kernel verticali sembrano uguali ma sono stati ruotati di 90 gradi. Una somma ponderata dei kernel orizzontale e verticale, rispettivamente con i pesi e , fornisce un kernel di analisi dello stesso tipo per l'angolo di gradiente .ωcomega_c = np.pi
omega_c = np.pi/4
omega_c = np.pi/16
cos(ϕ)sin(ϕ)ϕ
La differenziazione della risposta all'impulso non influisce sulla larghezza di banda, come si può vedere dalla sua trasformata di Fourier veloce 2-d (FFT), in 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()
Figura 3. Magnitudo della FFT 2-d di . Nel dominio della frequenza, la differenziazione appare come moltiplicazione della banda passante circolare piatta per e per uno sfasamento di 90 gradi che non è visibile nella grandezza.hxωx
Per eseguire la convoluzione per il canale verde e raccogliere un istogramma vettoriale con gradiente 2D, per l'ispezione visiva, in 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)))
Ciò inoltre ritaglia i dati, scartando i (N - 1)//2
pixel di ciascun bordo contaminati dal bordo rettangolare dell'immagine, prima dell'analisi dell'istogramma.
π
π2
π4
π8
π16
π32
π64
- Figura 4. Istogrammi 2-d di vettori di gradiente, per diverse impostazioni di frequenza di taglio del filtro passa basso . In ordine: prima con : , , (lo stesso in Python messa in vendita), , , quindi: : , : . La denigrazione mediante filtro passa-basso affina gli orientamenti del gradiente del bordo della traccia del circuito nell'istogramma.0
ωcN=41
omega_c = np.pi
omega_c = np.pi/2
omega_c = np.pi/4
omega_c = np.pi/8
omega_c = np.pi/16
N=81
omega_c = np.pi/32
N=161
omega_c = np.pi/64
Direzione media circolare ponderata lunghezza vettoriale
Esiste il metodo Yamartino per trovare la direzione del vento "media" da più campioni vettoriali di vento in un passaggio attraverso i campioni. Si basa sulla media delle quantità circolari , che viene calcolata come lo spostamento di un coseno che è una somma di coseni spostati ciascuno da una quantità circolare di periodo . Possiamo usare una versione ponderata in lunghezza vettoriale dello stesso metodo, ma prima dobbiamo raggruppare tutte le direzioni che sono uguali modulo . Possiamo farlo moltiplicando l'angolo di ciascun vettore gradiente per 4, usando una rappresentazione numerica complessa:2 ππ/2[Xk,Yk]
Zk=(XK+YKio)4X2K+Y2K−------√3=X4K- 6X2KY2K+Y4K+ ( 4X3KYK- 4XKY3K) iX2K+Y2K-------√3,(2)
soddisfacente e in seguito interpretando che le fasi di da a rappresentano gli angoli da a , dividendo la fase media circolare calcolata per 4:|ZK| =X2K+Y2K-------√ZK−ππ−π/4π/4
ϕ=14atan2(∑kIm(Zk),∑kRe(Zk))(3)
dove è l'orientamento stimato dell'immagine.ϕ
La qualità della stima può essere valutata eseguendo un altro passaggio attraverso i dati e calcolando la distanza circolare quadrata ponderata media , , tra le fasi dei numeri complessi e la fase media circolare stimata , concome il peso:MSCDZk4ϕ|Zk|
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)
che è stato minimizzato da calcolato per Eq. 3. In 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)")
Sulla base dei miei mpmath
esperimenti (non mostrati), penso che non saremo a corto di precursori numerici anche per immagini molto grandi. Per impostazioni di filtro diverse (annotate) le uscite sono, come riportato tra -45 e 45 gradi:
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)
Un forte filtro passa-basso appare utile, riducendo l'angolo equivalente di distanza circolare quadrata media (RMSCD) calcolato come . Senza la finestra del coseno ruotata 2-d, alcuni dei risultati sarebbero in qualche modo spenti (non mostrati), il che significa che è importante eseguire una corretta finestra dei filtri di analisi. L'angolo equivalente RMSCD non è direttamente una stima dell'errore nella stima dell'angolo, che dovrebbe essere molto inferiore.acos(1−MSCD)
Funzione alternativa di peso quadrato
Proviamo il quadrato della lunghezza del vettore come una funzione di peso alternativa, mediante:
Zk=(Xk+Yki)4X2k+Y2k−−−−−−−√2=X4k−6X2kY2k+Y4k+(4X3kYk−4XkY3k)iX2k+Y2k,(5)
In 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)")
Il peso di lunghezza quadrata riduce l'angolo equivalente RMSCD di circa un grado:
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)
Sembra una funzione di peso leggermente migliore. Ho aggiunto anche i cutoff e . Usano più grandi con conseguente diverso ritaglio dell'immagine e valori MSCD non strettamente comparabili.ωc=π/32ωc=π/64N
Istogramma 1-d
Il vantaggio della funzione di peso a lunghezza quadrata è più evidente con un istogramma ponderato 1-d delle fasi . Script Python:Zk
# 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()
Figura 5. Istogramma ponderato linearmente interpolato degli angoli del vettore gradiente, avvolto in e ponderato per (in ordine dal basso verso l'alto in corrispondenza del picco): nessuna ponderazione (nero), lunghezza del vettore gradiente ( rosso), quadrato della lunghezza del vettore gradiente (blu). La larghezza del cestino è di 0,1 gradi. Il cutoff del filtro era , come nella lista di Python. La figura in basso viene ingrandita sulle cime.−π/4…π/4omega_c = np.pi/4
Matematica del filtro orientabile
Abbiamo visto che l'approccio funziona, ma sarebbe bene avere una migliore comprensione matematica. Il ed filtra differenziazione impulso risposte fornite dall'Eq. 1 può essere inteso come le funzioni di base per formare la risposta all'impulso di un filtro di differenziazione orientabile che viene campionato da una rotazione del lato destro dell'equazione per (Eq. 1). Questo è più facilmente visibile convertendo l'Eq. 1 a coordinate polari:XyhX[ x , y]
hX( r , θ ) =hX[ r cos( θ ) , r sin( θ ) ]hy( r , θ ) =hy[ r cos( θ ) , r sin( θ ) ]f( r )=⎧⎩⎨0-ω2cr cos( θ )J2(ωcr )2 πr2se r = 0 ,altrimenti= cos( θ ) f( r ) ,=⎧⎩⎨0-ω2cr sin( θ )J2(ωcr )2 πr2se r = 0 ,altrimenti= peccato( θ ) f( r ) ,=⎧⎩⎨0-ω2crJ2(ωcr )2 πr2se r = 0 ,altrimenti,(6)
dove entrambe le risposte all'impulso del filtro di differenziazione orizzontale e verticale hanno la stessa funzione di fattore radiale . Qualsiasi versione ruotata di per angolo di sterzata è ottenuta da:f( r )h ( r , θ , ϕ )hX( r , θ )φ
h(r,θ,ϕ)=hx(r,θ−ϕ)=cos(θ−ϕ)f(r)(7)
L'idea era che il kernel guidato potesse essere costruito come una somma ponderata di e , con e come i pesi, e questo è davvero il caso:h(r,θ,ϕ)hx(r,θ)hX( r , θ )cos( ϕ )peccato( ϕ )
cos( ϕ )hX( r , θ ) + sin( ϕ )hy( r , θ ) = cos( ϕ ) cos( θ ) f( r ) + sin( ϕ ) peccato( θ ) f( r ) = cos( θ - ϕ ) f( r ) = h ( r , θ , ϕ ) .(8)
Arriveremo a una conclusione equivalente se pensiamo al segnale filtrato passa-basso isotropicamente come segnale di ingresso e costruiamo un operatore derivativo parziale rispetto alla prima delle coordinate ruotate , ruotate di angolo dalle coordinate , . (La derivazione può essere considerata un sistema invariante nel tempo lineare.) Abbiamo:XφyφφXy
x = cos( ϕ )Xφ- peccato( ϕ )yφ,y= peccato( ϕ )Xφ+ cos( ϕ )yφ(9)
Utilizzando la regola della catena per derivate parziali, l'operatore parziale derivata rispetto al può essere espresso come un coseno e seno somma pesata delle derivate parziali rispetto a ed :XφXy
∂∂Xφ=∂X∂Xφ∂∂X+∂y∂Xφ∂∂y=∂( cos( ϕ )Xφ- peccato( ϕ )yφ)∂Xφ∂∂X+∂( peccato( ϕ )Xφ+ cos( ϕ )yφ)∂Xφ∂∂y= cos( ϕ )∂∂X+ sin( ϕ )∂∂y(10)
Una domanda che resta da esplorare è come una media circolare opportunamente ponderata degli angoli del vettore gradiente sia correlata all'angolo in qualche modo del filtro di differenziazione orientato "più attivato".φ
Possibili miglioramenti
Per migliorare ulteriormente i risultati, il gradiente può essere calcolato anche per i canali di colore rosso e blu, da includere come dati aggiuntivi nel calcolo "medio".
Ho in mente possibili estensioni di questo metodo:
1) Utilizzare un set più ampio di kernel del filtro di analisi e rilevare i bordi anziché rilevare i gradienti. Questo deve essere realizzato con cura in modo che i bordi in tutte le direzioni siano trattati allo stesso modo, cioè un rilevatore di bordi per qualsiasi angolo dovrebbe essere ottenibile da una somma ponderata di kernel ortogonali. Una serie di kernel adatti può (penso) essere ottenuta applicando gli operatori differenziali dell'Eq. 11, Fig. 6 (vedi anche il mio post Scambio di stack di matematica ) sulla risposta all'impulso di spazio continuo di un filtro passa-basso simmetrico circolare.
limh → 0Σ4 N+ 1N= 0( - 1)nf( x+hcos(2 πn4 N+ 2) ,y+ h sin(2 πn4 N+ 2) )h2 N+ 1,limh → 0Σ4 N+ 1N= 0( - 1)nf( x+hsin(2 πn4 N+ 2) ,y+ h cos(2 πn4 N+ 2) )h2 N+ 1(11)
Figura 6. Posizioni relative del delta di Dirac negli operatori differenziali per la costruzione di rilevatori di bordi di ordine superiore.
2) Il calcolo di una media (ponderata) di quantità circolari può essere inteso come la somma di coseni della stessa frequenza spostati da campioni della quantità (e ridimensionati dal peso) e trovare il picco della funzione risultante. Se si aggiungono armoniche allo stesso modo spostate e ridimensionate del coseno spostato, con ampiezze relative accuratamente scelte, formando un kernel di livellamento più nitido, allora possono comparire più picchi nella somma totale e può essere riportato il picco con il valore più grande. Con un'adeguata miscela di armoniche, ciò darebbe una sorta di media locale che ignora ampiamente i valori anomali lontano dal picco principale della distribuzione.
Approcci alternativi
Sarebbe anche possibile convolgere l'immagine per angolo e angolo gherigli ruotati " lato lungo" e calcolare il quadrato medio dei pixel delle due immagini convolute. Verrà riportato l'angolo che massimizza il quadrato medio. Questo approccio potrebbe fornire un buon perfezionamento finale per la ricerca dell'orientamento dell'immagine, poiché è rischioso cercare l'angolo completo spazio a grandi passi.φϕ + π/ 2φφ
Un altro approccio sono i metodi non locali, come le regioni simili distanti tra loro correlate, applicabili se si conoscono tracce orizzontali o verticali lunghe o funzioni che si ripetono più volte in orizzontale o in verticale.