Comme je l'ai dit dans les commentaires, l'enregistrement d'images médicales est un sujet avec beaucoup de recherches disponibles, et je ne suis pas un expert. D'après ce que j'ai lu, l'idée de base couramment utilisée est de définir un mappage entre deux images (dans votre cas, une image et son image miroir), puis de définir des termes énergétiques pour la fluidité et la similitude de l'image si le mappage est appliqué, et enfin optimiser ce mappage à l'aide de techniques d'optimisation standard (ou parfois spécifiques à l'application).
J'ai piraté un algorithme rapide dans Mathematica pour le démontrer. Ce n'est pas un algorithme que vous devez utiliser dans une application médicale, seulement une démonstration des idées de base.
Tout d'abord, je charge votre image, la reflète et divise ces images en petits blocs:
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]
Normalement, nous ferions un enregistrement rigide approximatif (en utilisant par exemple des points clés ou des moments d'image), mais votre image est presque centrée, donc je vais sauter ceci.
Si nous regardons un bloc et son homologue d'image miroir:
{partsS[[6, 10]], partsM[[6, 10]]}
Nous pouvons voir qu'ils sont similaires, mais décalés. La quantité et la direction du changement sont ce que nous essayons de découvrir.
Pour quantifier la similitude de correspondance, je peux utiliser la distance euclidienne au carré:
ListPlot3D[
ImageData[
ImageCorrelate[partsM[[6, 10]], partsS[[6, 10]],
SquaredEuclideanDistance]]]
malheureusement, l'utilisation de ces données est l'optimisation directement plus difficile que je ne le pensais, j'ai donc utilisé une approximation de second ordre à la place:
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 fonction n'est pas la même que la fonction de corrélation réelle, mais elle est suffisamment proche pour une première étape. Calculons ceci pour chaque paire de blocs:
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];
Cela nous donne notre premier terme énergétique pour l'optimisation:
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
contient les décalages pour chaque bloc et donne une matchEnergyFit
approximation de la différence euclidienne au carré entre l'image d'origine et l'image miroir avec les décalages appliqués.
L'optimisation de cette énergie seule donnerait de mauvais résultats (si elle convergeait du tout). Nous voulons également que les décalages soient lisses, où la similitude des blocs ne dit rien sur le décalage (par exemple le long d'une ligne droite ou sur le fond blanc).
Nous avons donc mis en place un deuxième terme énergétique pour la douceur:
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]];
Heureusement, l'optimisation contrainte est intégrée dans Mathematica:
allVariables = Flatten[{variablesX, variablesY}];
constraints = -blockSize/3. < # < blockSize/3. & /@ allVariables;
initialValues = {#, 0} & /@ allVariables;
solution =
FindMinimum[{matchEnergyFit + 0.1 smoothnessEnergy, constraints},
initialValues];
Regardons le résultat:
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]
}]]
Le 0.1
facteur avant smoothnessEnergy
est le poids relatif que l'énergie de lissage obtient par rapport au terme d'énergie de correspondance d'image. Ce sont des résultats pour différents poids:
Améliorations possibles:
- Comme je l'ai dit, effectuez d'abord un enregistrement rigide. Avec un fond blanc, un enregistrement basé sur des moments d'image simples devrait fonctionner correctement.
- Ce n'est qu'une étape. Vous pouvez utiliser les décalages que vous avez trouvés dans une étape et les améliorer dans une deuxième étape, peut-être avec une fenêtre de recherche plus petite ou des tailles de bloc plus petites
- J'ai lu des articles où ils le font sans blocs du tout, mais optimisent un décalage par pixel.
- Essayez différentes fonctions de douceur