Jak powiedziałem w komentarzach, rejestracja obrazów medycznych jest tematem z wieloma dostępnymi badaniami i nie jestem ekspertem. Z tego, co przeczytałem, podstawową powszechnie stosowaną ideą jest zdefiniowanie odwzorowania między dwoma obrazami (w twoim przypadku obrazem i jego odbiciem lustrzanym), następnie zdefiniowanie terminów energii dla gładkości i podobieństwa obrazu, jeśli zastosowane zostanie odwzorowanie, i na koniec zoptymalizuj to mapowanie przy użyciu standardowych (lub czasem specyficznych dla aplikacji) technik optymalizacji.
Zhakowałem szybki algorytm w Mathematica, aby to zademonstrować. To nie jest algorytm, którego powinieneś używać w aplikacji medycznej, a jedynie demonstracja podstawowych pomysłów.
Najpierw ładuję twój obraz, odbij go i dzielę te obrazy na małe bloki:
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]
Normalnie robilibyśmy przybliżoną sztywną rejestrację (używając np. Punktów kluczowych lub momentów obrazu), ale twój obraz jest prawie wyśrodkowany, więc pominę to.
Jeśli spojrzymy na jeden blok i jego odpowiednik odbicia lustrzanego:
{partsS[[6, 10]], partsM[[6, 10]]}
Widzimy, że są podobne, ale przesunięte. Ilość i kierunek zmiany jest tym, co próbujemy ustalić.
Aby zmierzyć podobieństwo dopasowania, mogę użyć kwadratowej odległości euklidesowej:
ListPlot3D[
ImageData[
ImageCorrelate[partsM[[6, 10]], partsS[[6, 10]],
SquaredEuclideanDistance]]]
niestety, użycie tych danych jest tym, że optymalizacja bezpośrednio była trudniejsza niż myślałem, więc zamiast tego zastosowałem przybliżenie drugiego rzędu:
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}]
Funkcja nie jest taka sama jak faktyczna funkcja korelacji, ale jest wystarczająco blisko, aby wykonać pierwszy krok. Obliczmy to dla każdej pary bloków:
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];
To daje nam nasz pierwszy termin energetyczny do optymalizacji:
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
zawiera przesunięcia dla każdego bloku i matchEnergyFit
przybliża kwadratową różnicę euklidesową między obrazem oryginalnym a obrazem lustrzanym z zastosowanymi przesunięciami.
Sama optymalizacja tej energii dałaby słabe wyniki (gdyby w ogóle się zbiegła). Chcemy również, aby przesunięcia były gładkie, a podobieństwo bloku nie mówi nic o przesunięciu (np. Wzdłuż linii prostej lub na białym tle).
Dlatego ustanowiliśmy drugi termin energetyczny dla gładkości:
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]];
Na szczęście w Mathematica jest wbudowana ograniczona optymalizacja:
allVariables = Flatten[{variablesX, variablesY}];
constraints = -blockSize/3. < # < blockSize/3. & /@ allVariables;
initialValues = {#, 0} & /@ allVariables;
solution =
FindMinimum[{matchEnergyFit + 0.1 smoothnessEnergy, constraints},
initialValues];
Spójrzmy na wynik:
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]
}]]
0.1
Czynnikiem zanim smoothnessEnergy
to masa względna energia gładkość dostaje w stosunku do terminu obraz mecz energii. Oto wyniki dla różnych wag:
Możliwe ulepszenia:
- Jak powiedziałem, najpierw wykonaj sztywną rejestrację. Na białym tle prosta rejestracja oparta na momentach obrazu powinna działać poprawnie.
- To tylko jeden krok. Możesz użyć przesunięć znalezionych w jednym kroku i poprawić je w drugim kroku, być może z mniejszym oknem wyszukiwania lub mniejszymi rozmiarami bloku
- Czytałem artykuły, w których robią to w ogóle bez bloków, ale optymalizuję przesunięcie na piksel.
- Wypróbuj różne funkcje wygładzania