Wie ich in den Kommentaren gesagt habe, ist die Registrierung von medizinischen Bildern ein Thema, zu dem es viele Nachforschungen gibt, und ich bin kein Experte. Nach dem, was ich gelesen habe, besteht die häufig verwendete Grundidee darin, eine Zuordnung zwischen zwei Bildern (in Ihrem Fall ein Bild und sein Spiegelbild) zu definieren, dann Energiebegriffe für die Glätte und für die Bildähnlichkeit zu definieren, wenn die Zuordnung angewendet wird, und schließlich Optimieren Sie dieses Mapping mithilfe standardmäßiger (oder manchmal anwendungsspezifischer) Optimierungstechniken.
Ich habe in Mathematica einen schnellen Algorithmus gehackt, um dies zu demonstrieren. Dies ist kein Algorithmus, den Sie in einer medizinischen Anwendung verwenden sollten, sondern nur eine Demonstration der Grundideen.
Zuerst lade ich dein Bild, spiegele es und teile diese Bilder in kleine Blöcke auf:
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]
Normalerweise würden wir eine ungefähre starre Registrierung durchführen (z. B. unter Verwendung von Schlüsselpunkten oder Bildmomenten), aber Ihr Bild ist fast zentriert, sodass ich dies überspringe.
Wenn wir uns einen Block und sein spiegelbildliches Gegenstück ansehen:
{partsS[[6, 10]], partsM[[6, 10]]}
Wir können sehen, dass sie ähnlich, aber verschoben sind. Das Ausmaß und die Richtung der Verschiebung versuchen wir herauszufinden.
Um die Übereinstimmungsähnlichkeit zu quantifizieren, kann ich den quadratischen euklidischen Abstand verwenden:
ListPlot3D[
ImageData[
ImageCorrelate[partsM[[6, 10]], partsS[[6, 10]],
SquaredEuclideanDistance]]]
Leider war die direkte Optimierung unter Verwendung dieser Daten schwieriger als ich dachte, daher habe ich stattdessen eine Approximation 2. Ordnung verwendet:
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}]
Die Funktion ist nicht mit der eigentlichen Korrelationsfunktion identisch, reicht jedoch für einen ersten Schritt aus. Berechnen wir dies für jedes Blockpaar:
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];
Dies gibt uns unseren ersten Energiebegriff für die Optimierung:
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
enthält die Offsets für jeden Block und matchEnergyFit
approximiert die quadratische euklidische Differenz zwischen dem Originalbild und dem gespiegelten Bild, wobei die Offsets angewendet werden.
Die Optimierung dieser Energie allein würde schlechte Ergebnisse liefern (wenn sie überhaupt konvergiert). Wir möchten auch, dass die Offsets glatt sind, wobei die Blockähnlichkeit nichts über den Offset aussagt (z. B. entlang einer geraden Linie oder im weißen Hintergrund).
Also haben wir einen zweiten Energiebegriff für die Glätte aufgestellt:
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]];
Glücklicherweise ist in Mathematica eine eingeschränkte Optimierung integriert:
allVariables = Flatten[{variablesX, variablesY}];
constraints = -blockSize/3. < # < blockSize/3. & /@ allVariables;
initialValues = {#, 0} & /@ allVariables;
solution =
FindMinimum[{matchEnergyFit + 0.1 smoothnessEnergy, constraints},
initialValues];
Schauen wir uns das Ergebnis an:
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]
}]]
Der 0.1
vorhergehende Faktor smoothnessEnergy
ist das relative Gewicht, das die Glättungsenergie in Bezug auf den Bildanpassungsenergieterm erhält. Dies sind Ergebnisse für verschiedene Gewichte:
Mögliche Verbesserungen:
- Führen Sie, wie gesagt, zuerst eine starre Registrierung durch. Bei einem weißen Hintergrund sollte eine einfache Registrierung auf der Basis von Bildmomenten problemlos funktionieren.
- Dies ist nur ein Schritt. Sie können die gefundenen Offsets in einem Schritt verwenden und in einem zweiten Schritt verbessern, z. B. mit einem kleineren Suchfenster oder kleineren Blockgrößen
- Ich habe Artikel gelesen, in denen sie dies ohne Blöcke tun, aber einen Versatz pro Pixel optimieren.
- Probieren Sie verschiedene Glättungsfunktionen aus