Come ho detto nei commenti, la registrazione di immagini mediche è un argomento con molte ricerche disponibili e non sono un esperto. Da quello che ho letto, l'idea di base comunemente usata è quella di definire una mappatura tra due immagini (nel tuo caso un'immagine e la sua immagine speculare), quindi definire i termini energetici per uniformità e somiglianza dell'immagine se viene applicata la mappatura, e infine ottimizzare questa mappatura usando tecniche di ottimizzazione standard (o talvolta specifiche dell'applicazione).
Ho dimostrato insieme un rapido algoritmo in Mathematica per dimostrarlo. Questo non è un algoritmo che dovresti usare in un'applicazione medica, solo una dimostrazione delle idee di base.
Innanzitutto, carico la tua immagine, la specchio e divido queste immagini in piccoli blocchi:
src = ColorConvert[Import["http://i.stack.imgur.com/jf709.jpg"],
"Grayscale"];
mirror = ImageReflect[src, Left -> Right];
blockSize = 30;
partsS = ImagePartition[src, {blockSize, blockSize}];
partsM = ImagePartition[mirror, {blockSize, blockSize}];
GraphicsGrid[partsS]
Normalmente, faremmo una registrazione rigida approssimativa (usando ad esempio punti chiave o momenti dell'immagine), ma la tua immagine è quasi centrata, quindi la salterò.
Se osserviamo un blocco ed è la sua controparte speculare:
{partsS[[6, 10]], partsM[[6, 10]]}
Possiamo vedere che sono simili, ma spostati. La quantità e la direzione del turno è ciò che stiamo cercando di scoprire.
Per quantificare la somiglianza della partita, posso usare la distanza euclidea quadrata:
ListPlot3D[
ImageData[
ImageCorrelate[partsM[[6, 10]], partsS[[6, 10]],
SquaredEuclideanDistance]]]
purtroppo, usando questi dati è l'ottimizzazione direttamente più difficile di quanto pensassi, quindi ho usato invece un'approssimazione del 2 ° ordine:
fitTerms = {1, x, x^2, y, y^2, x*y};
fit = Fit[
Flatten[MapIndexed[{#2[[1]] - blockSize/2, #2[[2]] -
blockSize/2, #1} &,
ImageData[
ImageCorrelate[partsM[[6, 10]], partsS[[6, 10]],
SquaredEuclideanDistance]], {2}], 1], fitTerms, {x, y}];
Plot3D[fit, {x, -25, 25}, {y, -25, 25}]
La funzione non è la stessa della funzione di correlazione effettiva, ma è abbastanza vicina per un primo passo. Calcoliamo questo per ogni coppia di blocchi:
distancesFit = MapThread[
Function[{part, template},
Fit[Flatten[
MapIndexed[{#2[[2]] - blockSize/2, #2[[1]] - blockSize/2, #1} &,
ImageData[
ImageCorrelate[part, template,
SquaredEuclideanDistance]], {2}], 1],
fitTerms, {x, y}]], {partsM, partsS}, 2];
Questo ci dà il nostro primo termine energetico per l'ottimizzazione:
variablesX = Array[dx, Dimensions[partsS]];
variablesY = Array[dy, Dimensions[partsS]];
matchEnergyFit =
Total[MapThread[#1 /. {x -> #2, y -> #3} &, {distancesFit,
variablesX, variablesY}, 2], 3];
variablesX/Y
contiene gli offset per ciascun blocco e matchEnergyFit
approssima la differenza euclidea quadrata tra l'immagine originale e l'immagine speculare con gli offset applicati.
L'ottimizzazione di questa sola energia darebbe scarsi risultati (se convergesse del tutto). Vogliamo anche che gli offset siano uniformi, in cui la somiglianza del blocco non dice nulla sull'offset (ad esempio lungo una linea retta o sullo sfondo bianco).
Quindi impostiamo un secondo termine energetico per fluidità:
smoothnessEnergy = Total[Flatten[
{
Table[
variablesX[[i, j - 1]] - 2 variablesX[[i, j]] +
variablesX[[i, j + 1]], {i, 1, Length[partsS]}, {j, 2,
Length[partsS[[1]]] - 1}],
Table[
variablesX[[i - 1, j]] - 2 variablesX[[i, j]] +
variablesX[[i + 1, j]], {i, 2, Length[partsS] - 1}, {j, 1,
Length[partsS[[1]]]}],
Table[
variablesY[[i, j - 1]] - 2 variablesY[[i, j]] +
variablesY[[i, j + 1]], {i, 1, Length[partsS]}, {j, 2,
Length[partsS[[1]]] - 1}],
Table[
variablesY[[i - 1, j]] - 2 variablesY[[i, j]] +
variablesY[[i + 1, j]], {i, 2, Length[partsS] - 1}, {j, 1,
Length[partsS[[1]]]}]
}^2]];
Fortunatamente, l'ottimizzazione vincolata è integrata in Mathematica:
allVariables = Flatten[{variablesX, variablesY}];
constraints = -blockSize/3. < # < blockSize/3. & /@ allVariables;
initialValues = {#, 0} & /@ allVariables;
solution =
FindMinimum[{matchEnergyFit + 0.1 smoothnessEnergy, constraints},
initialValues];
Diamo un'occhiata al risultato:
grid = Table[{(j - 0.5)*blockSize - dx[i, j], (i - 0.5)*blockSize -
dy[i, j]}, {i, Length[partsS]}, {j, Length[partsS[[1]]]}] /.
solution[[2]];
Show[src, Graphics[
{Red,
Line /@ grid,
Line /@ Transpose[grid]
}]]
Il 0.1
fattore precedente smoothnessEnergy
è il peso relativo che l'energia di levigatezza ottiene in relazione al termine energetico corrispondente all'immagine. Questi sono risultati per pesi diversi:
Possibili miglioramenti:
- Come ho detto, esegui prima una registrazione rigida. Con uno sfondo bianco, la semplice registrazione basata sui momenti dell'immagine dovrebbe funzionare correttamente.
- Questo è solo un passo. Puoi utilizzare gli offset che hai trovato in un passaggio e migliorarli in un secondo passaggio, magari con una finestra di ricerca più piccola o blocchi di dimensioni inferiori
- Ho letto articoli in cui lo fanno senza blocchi, ma ottimizzano un offset per pixel.
- Prova diverse funzioni di scorrevolezza