Rilevamento automatico dell'angolo di rotazione sull'immagine arbitraria con caratteristiche ortogonali


9

Ho un compito a portata di mano in cui devo rilevare l'angolazione di un'immagine come il seguente esempio (parte della fotografia di microchip). L'immagine contiene caratteristiche ortogonali, ma potrebbero avere dimensioni diverse, con diversa risoluzione / nitidezza. L'immagine sarà leggermente imperfetta a causa di distorsioni ottiche e aberrazioni. È richiesta la precisione del rilevamento dell'angolo sub-pixel (cioè dovrebbe essere ben al di sotto dell'errore <0,1 °, qualcosa come 0,01 ° sarebbe tollerabile). Per riferimento, per questa immagine l'angolo ottimale è di circa 32,19 °.

inserisci qui la descrizione dell'immagine Attualmente ho provato 2 approcci: entrambi eseguono una ricerca della forza bruta per un minimo locale con un gradino di 2 °, quindi il gradiente scende fino a un gradino di 0,0001 °.

  1. La funzione di merito viene sum(pow(img(x+1)-img(x-1), 2) + pow(img(y+1)-img(y-1))calcolata sull'immagine. Quando le linee orizzontali / verticali sono allineate, c'è meno cambiamento nelle direzioni orizzontale / verticale. La precisione era di circa 0,2 °.
  2. La funzione di merito è (max-min) su una larghezza / altezza della striscia dell'immagine. Questa striscia viene inoltre riprodotta in loop sull'immagine e viene accumulata la funzione di merito. Questo approccio si concentra anche su piccoli cambiamenti di luminosità quando le linee orizzontali / verticali sono allineate, ma può rilevare piccoli cambiamenti su una base più grande (larghezza della striscia - che potrebbe essere di circa 100 pixel di larghezza). Ciò offre una migliore precisione, fino a 0,01 ° - ma ha molti parametri da modificare (la larghezza / altezza della striscia, ad esempio, è abbastanza sensibile) che potrebbero non essere affidabili nel mondo reale.

Il filtro di rilevamento dei bordi non ha aiutato molto.

La mia preoccupazione è un piccolo cambiamento nella funzione di merito in entrambi i casi tra angoli peggiori e angoli migliori (differenza <2 volte).

Hai qualche suggerimento migliore sulla scrittura della funzione di merito per il rilevamento dell'angolo?

Aggiornamento: l' immagine di esempio a dimensione intera viene caricata qui (51 MiB)

Dopo tutta l'elaborazione finirà per apparire così.


1
È molto triste che sia passato da stackoverflow a dsp. Non vedo una soluzione simile a DSP qui, e le possibilità sono ora molto ridotte. Il 99,9% degli algoritmi e dei trucchi DSP sono inutili per questo compito. Sembra che qui sia necessario un algoritmo o un approccio personalizzato, non una FFT.
BarsMonster,

2
Sono super felice di dirti che è totalmente sbagliato essere tristi; DSP.SE è il posto giusto in assoluto per chiedere questo! (Non molto stackoverflow. Non è una domanda di programmazione. Conosci la tua programmazione. Non sai come elaborare questa immagine.) Le immagini sono segnali e DSP.SE si occupa molto dell'elaborazione delle immagini! Inoltre, molti trucchi DSP generali (anche noti per esempio segnali di comunicazione) sono molto applicabili al tuo problema :)
Marcus Müller,

1
Quanto è importante l'efficienza?
Cedron Dawg,

a proposito, anche quando si esegue con una risoluzione di 0,04 °, sono abbastanza sicuro che la rotazione sia esattamente di 32 °, non di 32.19 ° - quali sono le risoluzioni della tua fotografia originale? Perché a una larghezza di 800 px, una rotazione non corretta di 0,01 ° non è che 0,14 px di differenza di altezza, e ciò sarebbe persino evidente sotto un'interpolazione sincera.
Marcus Müller,

@CedronDawg Sicuramente nessun requisito in tempo reale, posso tollerare circa 10-60 secondi di calcolo su circa 8-12 core.
BarsMonster,

Risposte:


12

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:

(1)hx[x,y]={0if x=y=0,ωc2xJ2(ωcx2+y2)2π(x2+y2)otherwise,hy[x,y]={0if x=y=0,ωc2yJ2(ωcx2+y2)2π(x2+y2)altrimenti,

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()

inserisci qui la descrizione dell'immagine
Figura 1. Finestra coseno ruotata 2-d.

inserisci qui la descrizione dell'immagine
inserisci qui la descrizione dell'immagine
inserisci qui la descrizione dell'immagine
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.piomega_c = np.pi/4omega_c = np.pi/16cos(ϕ)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()

inserisci qui la descrizione dell'immagine
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)//2pixel di ciascun bordo contaminati dal bordo rettangolare dell'immagine, prima dell'analisi dell'istogramma.

inserisci qui la descrizione dell'immagineπ inserisci qui la descrizione dell'immagineπ2 inserisci qui la descrizione dell'immagineπ4
inserisci qui la descrizione dell'immagineπ8 inserisci qui la descrizione dell'immagineπ16 inserisci qui la descrizione dell'immagineπ32 inserisci qui la descrizione dell'immagineπ64 inserisci qui la descrizione dell'immagine - 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=41omega_c = np.piomega_c = np.pi/2omega_c = np.pi/4omega_c = np.pi/8omega_c = np.pi/16N=81omega_c = np.pi/32N=161omega_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]

(2)ZK=(XK+YKio)4XK2+YK23=XK4-6XK2YK2+YK4+(4XK3YK-4XKYK3)ioXK2+YK23,

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|=XK2+YK2ZKπππ/4π/4

(3)ϕ=14atan2(kIm(Zk),kRe(Zk))

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|

(4)MSCD=k|Zk|(1cos(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|,

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 mpmathesperimenti (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(1MSCD)

Funzione alternativa di peso quadrato

Proviamo il quadrato della lunghezza del vettore come una funzione di peso alternativa, mediante:

(5)Zk=(Xk+Yki)4Xk2+Yk22=Xk46Xk2Yk2+Yk4+(4Xk3Yk4XkYk3)iXk2+Yk2,

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()

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine
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]

(6)hX(r,θ)=hX[rcos(θ),rpeccato(θ)]={0Se r=0,-ωc2rcos(θ)J2(ωcr)2πr2altrimenti=cos(θ)f(r),hy(r,θ)=hy[rcos(θ),rpeccato(θ)]={0Se r=0,-ωc2rpeccato(θ)J2(ωcr)2πr2altrimenti=peccato(θ)f(r),f(r)={0Se r=0,-ωc2rJ2(ωcr)2πr2altrimenti,

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,θ)φ

(7)h(r,θ,ϕ)=hx(r,θϕ)=cos(θϕ)f(r)

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(φ)

(8)cos(φ)hX(r,θ)+peccato(φ)hy(r,θ)=cos(φ)cos(θ)f(r)+peccato(φ)peccato(θ)f(r)=cos(θ-φ)f(r)=h(r,θ,φ).

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

(9)X=cos(φ)Xφ-peccato(φ)yφ,y=peccato(φ)Xφ+cos(φ)yφ

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

(10)Xφ=XXφX+yXφy=(cos(φ)Xφ-peccato(φ)yφ)XφX+(peccato(φ)Xφ+cos(φ)yφ)Xφy=cos(φ)X+peccato(φ)y

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.

(11)limh0ΣN=04N+1(-1)nf(X+hcos(2πn4N+2),y+hpeccato(2πn4N+2))h2N+1,limh0ΣN=04N+1(-1)nf(X+hpeccato(2πn4N+2),y+hcos(2πn4N+2))h2N+1

inserisci qui la descrizione dell'immagine
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.


Quanto è preciso il risultato ottenuto?
Royi,

@Royi Forse intorno a 0,1 gradi.
Olli Niemitalo,

@OlliNiemitalo che è piuttosto impressionante, data la risoluzione limitata!
Marcus Müller,

3
@OlliNiemitalo parlando di impressionante: questo. risposta. è. quello. parole. molto. definizione.
Marcus Müller,

@ MarcusMüller Grazie Marcus, prevedo che anche la prima estensione sarà molto interessante.
Olli Niemitalo,

5

C'è un trucco DSP simile qui, ma non ricordo esattamente i dettagli.

Ne ho letto da qualche parte, qualche tempo fa. Ha a che fare con la ricerca di combinazioni di motivi di tessuto indipendentemente dall'orientamento. Quindi potresti voler fare delle ricerche su questo.

Prendi un campione circolare. Fai somme lungo i raggi del cerchio per ottenere un profilo di circonferenza. Quindi hanno fatto un DFT su questo (dopo tutto è intrinsecamente circolare). Lancia le informazioni sulla fase (rendila indipendente dall'orientamento) e fai un confronto.

Quindi hanno potuto dire se due tessuti avevano lo stesso modello.

Il tuo problema è simile

Mi sembra, senza prima provarlo, che le caratteristiche del profilo pre DFT rivelino l'orientamento. Fare deviazioni standard lungo i raggi invece delle somme dovrebbe funzionare meglio, forse entrambi.

Ora, se avessi un'immagine di riferimento orientata, potresti usare la loro tecnica.

ced


I tuoi requisiti di precisione sono piuttosto severi.

Gli ho dato una botta. Prendendo la somma dei valori assoluti delle differenze tra due punti successivi lungo il raggio per ciascun colore.

Ecco un grafico attorno alla circonferenza. Il tuo valore viene tracciato con gli indicatori bianchi.

inserisci qui la descrizione dell'immagine

Puoi vederlo, ma non credo che funzionerà per te. Scusate.


Rapporto sullo stato di avanzamento: alcuni

Ho deciso un processo in tre fasi.

1) Trova un punto di valutazione.

2) Misura grossolana

3) Misura fine

Attualmente, il primo passo è l'integrazione dell'utente. Dovrebbe essere automatico, ma non mi preoccupo. Ho una bozza del secondo passo. Ci sono alcune modifiche che voglio provare. Infine, ho alcuni candidati per il terzo passo che verrà sottoposto a test per vedere quale funziona meglio.

La buona notizia è che si sta accendendo velocemente. Se il tuo unico scopo è quello di far sembrare un'immagine a livello di una pagina Web, le tue tolleranze sono troppo rigide e la misurazione approssimativa dovrebbe essere abbastanza accurata.

Questa è la misurazione approssimativa. Ogni pixel è di circa 0,6 gradi. (Modifica, in realtà 0,3)

inserisci qui la descrizione dell'immagine


Rapporto sullo stato di avanzamento: in grado di ottenere buoni risultati

inserisci qui la descrizione dell'immagine

La maggior parte non è così buona, ma sono economici (e abbastanza locali) e trovare posti per ottenere buone letture è facile ..... per un essere umano. La forza bruta dovrebbe funzionare bene per un programma.

I risultati possono essere notevolmente migliorati, questo è un semplice test di base. Non sono ancora pronto a fare alcuna spiegazione, né pubblicare il codice, ma questa schermata non è photoshoppata.


Rapporto di avanzamento: il codice è stato pubblicato, ho finito con questo per un po '.

Questo screenshot è il programma che lavora al tiro di 45 gradi di Marcus.

inserisci qui la descrizione dell'immagine

I canali di colore vengono elaborati in modo indipendente.

Viene selezionato un punto come centro di scansione.

Un diametro viene spazzato di 180 gradi ad angoli discreti

Ad ogni angolo, la "volatilità" sta misurando attraverso il diametro. Viene tracciata una traccia per ciascun canale che raccoglie campioni. Il valore del campione è un'interpolazione lineare dei quattro valori degli angoli di qualsiasi griglia quadrata su cui si trova il punto campione.

Per ogni traccia di canale

I campioni vengono moltiplicati per una funzione della finestra di VonHann

Viene eseguito un passaggio Smooth / Differ sui campioni

L'RMS del Differ viene utilizzato come misura di volatilità

I grafici delle righe inferiori sono:

Il primo è lo sweep da 0 a 180 gradi, ogni pixel è di 0,5 gradi. Il secondo è lo sweep attorno all'angolo selezionato, ogni pixel è di 0,1 gradi. Terzo è lo sweep attorno all'angolo selezionato, ogni pixel è di 0,01 gradi. Il quarto è la curva Differ traccia

La selezione iniziale è la volatilità media minima dei tre canali. Questo sarà vicino, ma di solito non attivo, l'angolazione migliore. La simmetria alla depressione è un indicatore migliore del minimo. Una parabola più adatta in quel quartiere dovrebbe dare un'ottima risposta.

Il codice sorgente (in Gambas, PPA gambas-team / gambas3) è disponibile all'indirizzo:

https://forum.gambas.one/viewtopic.php?f=4&t=707

È un normale file zip, quindi non è necessario installare Gambas per guardare la fonte. I file si trovano nella sottodirectory ".src".

La rimozione della finestra VonHann produce una maggiore precisione perché allunga efficacemente la traccia, ma aggiunge oscillazioni. Forse un doppio VonHann sarebbe migliore in quanto il centro non è importante e verrà rilevato un inizio più rapido di "quando la vacillazione-vacillazione colpisce il terreno" verrà rilevato. La precisione può essere facilmente migliorata aumentando la lunghezza della traccia fino a quando l'immagine lo consente (Sì, è automatico). Una migliore funzione finestra, sinc?

Le misure che ho adottato con le impostazioni correnti confermano il valore 3,19 +/- 03 ish.

Questo è solo lo strumento di misurazione. Ci sono diverse strategie che mi vengono in mente per applicarlo all'immagine. Questo, come si suol dire, è un esercizio per il lettore. O in questo caso, l'OP. Ci proverò più tardi.

C'è spazio per migliorare sia l'algoritmo che il programma, ma già sono davvero utili.

Ecco come funziona l'interpolazione lineare

'---- Intero numero porzione

        x = piano (rx)
        y = piano (ry)

'---- Parti frazionarie

        fx = rx - x
        fy = ry - y

        gx = 1.0 - fx
        gy = 1.0 - fy

'---- Media ponderata

        vtl = ArgValues ​​[x, y] * gx * gy 'In alto a sinistra
        vtr = ArgValues ​​[x + 1, y] * fx * gy 'In alto a destra
        vbl = ArgValues ​​[x, y + 1] * gx * fy 'In basso a sinistra
        vbr = ArgValues ​​[x + 1, y + 1] * fx * fy 'Bottom Rigth

        v = vtl + vtr + vbl + vbr

Qualcuno conosce il nome convenzionale per quello?


1
ehi, non devi essere dispiaciuto per qualcosa che è stato un approccio molto intelligente e potrebbe essere di grande aiuto per qualcuno con un problema simile che verrà qui più tardi! +1
Marcus Müller,

1
@BarsMonster, sto facendo un buon progresso. Dovrai installare Gambas (PPA: gambas-team / gambas3) sul tuo box Linux. (Probabilmente, anche tu Marcus e Olli, se puoi.) Sto lavorando a un programma che non solo affronterà questo problema, ma servirà anche come buona base per altre attività di elaborazione delle immagini.
Cedron Dawg

in attesa!
Marcus Müller,

@CedronDawg che si chiama interpolazione bilineare, ecco perché , indicando anche un'implementazione alternativa.
Olli Niemitalo,

1
@ OlliNiemitalo, grazie Olli. In questa situazione, non penso che il bicubico migliorerebbe i risultati rispetto a quello bilineare, infatti, potrebbe persino essere dannoso. Più tardi, giocherò con diverse metriche di volatilità lungo il diametro e una funzione finestra di diversa forma. A questo punto sto pensando di usare un VonHann alle estremità del diametro come pagaie o "sedili barcollanti che colpiscono il fango". Il fondo piatto nella curva è dove il barcollatore non ha ancora il suo terreno (bordo). A metà strada tra i due angoli è una buona lettura. Le impostazioni attuali sono buone a meno di 0,1 gradi,
Cedron Dawg

4

Piuttosto ad alte prestazioni, ma dovrebbe ottenere la precisione desiderata:

  • Edge rileva l'immagine
  • Trasformati in uno spazio in cui hai abbastanza pixel per la precisione desiderata.
  • Perché ci sono abbastanza linee ortogonali; l'immagine nello spazio hough conterrà i massimi che giacciono su due linee. Questi sono facilmente rilevabili e ti danno l'angolazione desiderata.

Bello, esattamente il mio approccio: sono un po 'triste di non averlo visto prima di andare in treno e quindi non l'ho inserito nella mia risposta. Un chiaro +1!
Marcus Müller,

4

Sono andato avanti e ho sostanzialmente adattato l'esempio di trasformazione di Hough di Opencv al tuo caso d'uso. L'idea è buona, ma poiché la tua immagine ha già molti bordi a causa della sua natura spigolosa, il rilevamento dei bordi non dovrebbe avere molti benefici.

Quindi, quello che ho fatto sopra detto esempio è stato

  • Omettere il rilevamento dei bordi
  • decomporre l'immagine di input in canali di colore ed elaborarli separatamente
  • contare le occorrenze delle linee in un angolo specifico (dopo aver quantizzato gli angoli e averli presi modulo 90 °, poiché si hanno molti angoli retti)
  • combina i contatori dei canali di colore
  • correggere queste rotazioni

Quello che potresti fare per migliorare ulteriormente la qualità della stima (come vedrai di seguito, la supposizione in alto non era corretta - la seconda era) probabilmente equivarrebbe a convertire l'immagine in un'immagine in scala di grigi che rappresenta le effettive differenze tra i diversi materiali migliori - chiaramente, i canali RGB non sono i migliori. Sei l'esperto di semiconduttori, quindi trova un modo per combinare i canali di colore in modo da massimizzare la differenza tra ad esempio metallizzazione e silicio.

Il mio quaderno jupyter è qui . Vedi i risultati qui sotto.

Per aumentare la risoluzione angolare, aumentare la QUANT_STEPvariabile e la precisione angolare nella hough_transformchiamata. Non l'ho fatto, perché volevo che questo codice fosse scritto in <20 min, e quindi non volevo investire un minuto nel calcolo.

import cv2
import numpy
from matplotlib import pyplot
import collections

QUANT_STEPS = 360*2
def quantized_angle(line, quant = QUANT_STEPS):
    theta = line[0][1]
    return numpy.round(theta / numpy.pi / 2 * QUANT_STEPS) / QUANT_STEPS * 360 % 90

def detect_rotation(monochromatic_img):
    # edges = cv2.Canny(monochromatic_img, 50, 150, apertureSize = 3) #play with these parameters
    lines = cv2.HoughLines(monochromatic_img, #input
                           1, # rho resolution [px]
                           numpy.pi/180, # angular resolution [radian]
                           200) # accumulator threshold – higher = fewer candidates
    counter = collections.Counter(quantized_angle(line) for line in lines)
    return counter
img = cv2.imread("/tmp/HIKRe.jpg") #Image directly as grabbed from imgur.com
total_count = collections.Counter()
for channel in range(img.shape[-1]):
    total_count.update(detect_rotation(img[:,:,channel]))

most_common = total_count.most_common(5)
for angle,_ in most_common:
    pyplot.figure(figsize=(8,6), dpi=100)
    pyplot.title(f"{angle:.3f}°")
    rotation = cv2.getRotationMatrix2D((img.shape[0]/2, img.shape[1]/2), -angle, 1)
    pyplot.imshow(cv2.warpAffine(img, rotation, img.shape[:2]))

output_4_0

output_4_1

output_4_2

output_4_3

output_4_4


4

Questo è un tentativo alla prima estensione suggerita della mia risposta precedente.

Filtri limitatori di banda ideali simmetrici circolari

Costruiamo una banca ortogonale di quattro filtri banditi all'interno di un cerchio di raggioωcsul piano delle frequenze. Le risposte all'impulso di questi filtri possono essere combinate linearmente per formare kernel direzionali di rilevamento dei bordi. Un insieme arbitrariamente normalizzata di risposte all'impulso filtra ortogonale si ottengono applicando le prime due coppie di "beach-palla come" operatori differenziali al continuo spazio risposta impulsiva del filtro di banda limitativo simmetrica ideale circolarmente risposta all'impulsoh(X,y):

(1)h(X,y)=ωc2πX2+y2J1(ωcX2+y2)

(2)h0X(X,y)αddXh(X,y),h0y(X,y)αddyh(X,y),h1X(X,y)α((ddX)3-3ddX(ddy)2)h(X,y),h1y(X,y)α((ddy)3-3ddy(ddX)2)h(X,y)

(3)h0X(X,y)={0Se X=y=0,-ωc2XJ2(ωcX2+y2)2π(X2+y2)altrimenti,h0y(X,y)=h0X[y,X],h1X(X,y)={0Se X=y=0,(ωcX(3y2-X2)(J0(ωcX2+y2)ωcX2+y2(ωc2X2+ωc2y2-24)-8J1(ωcX2+y2)(ωc2X2+ωc2y2-6)))2π(X2+y2)7/2altrimenti,h1y(X,y)=h1X[y,X],

dove Jαè una funzione di Bessel del primo tipo di ordineα e αsignifica "è proporzionale a". Ho usato le query Wolfram Alpha ( (ᵈ / dx) ³ ; ᵈ / dx ; ᵈ / dx (ᵈ / dy) ² ) per effettuare la differenziazione e semplificare il risultato.

Gherigli troncati in Python:

import matplotlib.pyplot as plt
import scipy
import scipy.special
import numpy as np

def h0x(x, y, omega_c):
  if x == 0 and y == 0:
    return 0
  return -omega_c**2*x*scipy.special.jv(2, omega_c*np.sqrt(x**2 + y**2))/(2*np.pi*(x**2 + y**2))

def h1x(x, y, omega_c):
  if x == 0 and y == 0:
    return 0
  return omega_c*x*(3*y**2 - x**2)*(scipy.special.j0(omega_c*np.sqrt(x**2 + y**2))*omega_c*np.sqrt(x**2 + y**2)*(omega_c**2*x**2 + omega_c**2*y**2 - 24) - 8*scipy.special.j1(omega_c*np.sqrt(x**2 + y**2))*(omega_c**2*x**2 + omega_c**2*y**2 - 6))/(2*np.pi*(x**2 + y**2)**(7/2))

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 circularLowpassKernel(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 x, y: omega_c*scipy.special.j1(omega_c*np.sqrt((x - (N - 1)/2)**2 + (y - (N - 1)/2)**2))/(2*np.pi*np.sqrt((x - (N - 1)/2)**2 + (y - (N - 1)/2)**2)), [N, N])
  kernel[(N - 1)//2, (N - 1)//2] = omega_c**2/(4*np.pi)
  return kernel

def prototype0x(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.zeros([N, N])
  for y in range(N):
    for x in range(N):
      kernel[y, x] = h0x(x - (N - 1)/2, y - (N - 1)/2, omega_c)
  return kernel

def prototype0y(omega_c, N):  # omega = cutoff frequency in radians (pi is max), N = horizontal size of the kernel, also its vertical size, must be odd.
  return prototype0x(omega_c, N).transpose()

def prototype1x(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.zeros([N, N])
  for y in range(N):
    for x in range(N):
      kernel[y, x] = h1x(x - (N - 1)/2, y - (N - 1)/2, omega_c)
  return kernel

def prototype1y(omega_c, N):  # omega = cutoff frequency in radians (pi is max), N = horizontal size of the kernel, also its vertical size, must be odd.
  return prototype1x(omega_c, N).transpose()

N = 321  # 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/8  # Cutoff frequency in radians <= pi
lowpass = circularLowpassKernel(omega_c, N)
kernel0x = prototype0x(omega_c, N)
kernel0y = prototype0y(omega_c, N)
kernel1x = prototype1x(omega_c, N)
kernel1y = prototype1y(omega_c, N)

# Optional kernel image save
plt.imsave('lowpass.png', plt.cm.bwr(plt.Normalize(vmin=-lowpass.max(), vmax=lowpass.max())(lowpass)))
plt.imsave('kernel0x.png', plt.cm.bwr(plt.Normalize(vmin=-kernel0x.max(), vmax=kernel0x.max())(kernel0x)))
plt.imsave('kernel0y.png', plt.cm.bwr(plt.Normalize(vmin=-kernel0y.max(), vmax=kernel0y.max())(kernel0y)))
plt.imsave('kernel1x.png', plt.cm.bwr(plt.Normalize(vmin=-kernel1x.max(), vmax=kernel1x.max())(kernel1x)))
plt.imsave('kernel1y.png', plt.cm.bwr(plt.Normalize(vmin=-kernel1y.max(), vmax=kernel1y.max())(kernel1y)))
plt.imsave('kernelkey.png', plt.cm.bwr(np.repeat([(np.arange(321)/320)], 16, 0)))

inserisci qui la descrizione dell'immagine
inserisci qui la descrizione dell'immagine
Figura 1. Grafico in scala 1: 1 con mappatura dei colori della risposta all'impulso del filtro limitatore di banda simmetrico circolare, con frequenza di taglio ωc=π/8. Tasto colore: blu: negativo, bianco: zero, rosso: massimo.

inserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagine
inserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagine
inserisci qui la descrizione dell'immagine
Figura 2. Grafici in scala 1: 1 mappati per colore delle risposte all'impulso campionate dei filtri nel banco dei filtri, con frequenza di taglio ωc=π/8, In ordine: h0X, h0y, h1X, h0y. Tasto colore: blu: minimo, bianco: zero, rosso: massimo.

I rilevatori di bordi direzionali possono essere costruiti come somme ponderate di questi. In Python (continua):

composite = kernel0x-4*kernel1x
plt.imsave('composite0.png', plt.cm.bwr(plt.Normalize(vmin=-composite.max(), vmax=composite.max())(composite)))
plt.imshow(composite, vmin=-np.max(composite), vmax=np.max(composite), cmap='bwr')
plt.colorbar()
plt.show()

composite = (kernel0x+kernel0y) + 4*(kernel1x+kernel1y)
plt.imsave('composite45.png', plt.cm.bwr(plt.Normalize(vmin=-composite.max(), vmax=composite.max())(composite)))
plt.imshow(composite, vmin=-np.max(composite), vmax=np.max(composite), cmap='bwr')
plt.colorbar()
plt.show()

inserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagine
inserisci qui la descrizione dell'immagine
Figura 3. Gherigli di rilevamento del bordo direzionale costruiti come somme ponderate di gherigli di Fig. 2. Tasto colore: blu: minimo, bianco: zero, rosso: massimo.

I filtri di Fig. 3 dovrebbero essere ottimizzati per i bordi continui, rispetto ai filtri a gradiente (primi due filtri di Fig. 2).

Filtri gaussiani

I filtri di Fig. 2 hanno molte oscillazioni dovute alla limitazione della banda rigorosa. Forse un punto di partenza migliore sarebbe una funzione gaussiana, come nei filtri derivati ​​gaussiani. Relativamente, sono molto più facili da gestire matematicamente. Proviamo invece. Iniziamo con la definizione della risposta all'impulso di un filtro gaussiano "passa-basso":

(4)h(X,y,σ)=e-X2+y22σ22πσ2.

Applichiamo gli operatori di Eq. 2 ah(X,y,σ) e normalizzare ogni filtro h.. di:

(5)--h..(X,y,σ)2dXdy=1.

(6)h0X(X,y,σ)=22πσ2ddXh(X,y,σ)=-2πσ2Xe-X2+y22σ2,h0y(X,y,σ)=h0X(y,X,σ),h1X(X,y,σ)=23πσ43((ddX)3-3ddX(ddy)2)h(X,y,σ)=-33πσ4(X3-3Xy2)e-X2+y22σ2,h1y(X,y,σ)=h1X(y,X,σ).

Vorremmo costruire da questi, come la loro somma ponderata, la risposta all'impulso di un filtro rilevatore di bordi verticali che massimizza la specificità S che è la sensibilità media a un bordo verticale rispetto ai possibili spostamenti del bordo S rispetto alla sensibilità media sui possibili angoli di rotazione del bordo β e possibili spostamenti dei bordi S:

(7)S=2π-(-(-ShX(X,y,σ)dX-ShX(X,y,σ)dX)dy)2dS(-ππ-(-(-ShX(cos(β)X-peccato(β)y,peccato(β)X+cos(β)y)dX-ShX(cos(β)X-peccato(β)y,peccato(β)X+cos(β)y)dX)dy)2dSdβ).

Abbiamo solo bisogno di una somma ponderata di h0X con varianza σ2 e h1Xcon varianza ottimale. Si scopre cheS è massimizzato da una risposta all'impulso:

(8)hX(X,y,σ)=7625-2440561h0X(X,y,σ)-26105-97661h1X(X,y,5σ)=-(15250-4880561πσ2Xe-X2+y22σ2+18305-29284575πσ4(2X3-6Xy2)e-X2+y210σ2=2πσ215250-4880561ddXh(X,y,σ)-100πσ418305-2928183((ddX)3-3ddX(ddy)2)h(X,y,5σ)3,8275359956049814σ2ddXh(X,y,σ)-33,044650082417731σ4((ddX)3-3ddX(ddy)2)h(X,y,5σ),

anche normalizzato dall'Eq. 5. Ai bordi verticali, questo filtro ha una specificità diS=10×51/49 + 2 3,661,498645 millions, in contrasto con la specificità S=2 di un filtro derivato gaussiano del primo ordine rispetto a X. L'ultima parte dell'Eq. 8 ha la normalizzazione compatibile con i filtri separativi gaussiani 2-d separabili di Python scipy.ndimage.gaussian_filter:

import matplotlib.pyplot as plt
import numpy as np
import scipy.ndimage

sig = 8;
N = 161
x = np.zeros([N, N])
x[N//2, N//2] = 1
ddx = scipy.ndimage.gaussian_filter(x, sigma=[sig, sig], order=[0, 1], truncate=(N//2)/sig)
ddx3 = scipy.ndimage.gaussian_filter(x, sigma=[np.sqrt(5)*sig, np.sqrt(5)*sig], order=[0, 3], truncate=(N//2)/(np.sqrt(5)*sig))
ddxddy2 = scipy.ndimage.gaussian_filter(x, sigma=[np.sqrt(5)*sig, np.sqrt(5)*sig], order=[2, 1], truncate=(N//2)/(np.sqrt(5)*sig))

hx = 3.8275359956049814*sig**2*ddx - 33.044650082417731*sig**4*(ddx3 - 3*ddxddy2)
plt.imsave('hx.png', plt.cm.bwr(plt.Normalize(vmin=-hx.max(), vmax=hx.max())(hx)))

h = scipy.ndimage.gaussian_filter(x, sigma=[sig, sig], order=[0, 0], truncate=(N//2)/sig)
plt.imsave('h.png', plt.cm.bwr(plt.Normalize(vmin=-h.max(), vmax=h.max())(h)))
h1x = scipy.ndimage.gaussian_filter(x, sigma=[sig, sig], order=[0, 3], truncate=(N//2)/sig) - 3*scipy.ndimage.gaussian_filter(x, sigma=[sig, sig], order=[2, 1], truncate=(N//2)/sig)
plt.imsave('ddx.png', plt.cm.bwr(plt.Normalize(vmin=-ddx.max(), vmax=ddx.max())(ddx)))
plt.imsave('h1x.png', plt.cm.bwr(plt.Normalize(vmin=-h1x.max(), vmax=h1x.max())(h1x)))
plt.imsave('gaussiankey.png', plt.cm.bwr(np.repeat([(np.arange(161)/160)], 16, 0)))

enter image description hereenter image description hereenter image description hereenter image description hereenter image description here
Figura 4. Grafici in scala 1: 1 mappati per colore di, in ordine: una funzione gaussiana 2-d, derivata della funzione gaussiana rispetto a X, un operatore differenziale (ddX)3-3ddX(ddy)2 applicato alla funzione gaussiana, il filtro di rilevamento del bordo verticale derivato gaussiano ottimale a due componenti hX(X,y,σ)dell'Eq. 8. La deviazione standard di ciascun gaussiano eraσ=8 ad eccezione del componente esagonale nell'ultimo diagramma che aveva una deviazione standard 5×8. Tasto colore: blu: minimo, bianco: zero, rosso: massimo.

CONTINUA...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.