Como he dicho en los comentarios, el registro de imágenes médicas es un tema con mucha investigación disponible, y no soy un experto. Por lo que he leído, la idea básica comúnmente utilizada es definir un mapeo entre dos imágenes (en su caso, una imagen y su imagen especular), luego definir términos de energía para suavidad y similitud de imagen si se aplica el mapeo, y finalmente optimice esta asignación utilizando técnicas de optimización estándar (o, a veces, específicas de la aplicación).
He hackeado un algoritmo rápido en Mathematica para demostrar esto. Este no es un algoritmo que debe usar en una aplicación médica, solo una demostración de las ideas básicas.
Primero, cargo su imagen, la reflejo y divido estas imágenes en pequeños bloques:
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, haríamos un registro rígido aproximado (usando, por ejemplo, puntos clave o momentos de imagen), pero su imagen está casi centrada, por lo que me saltearé esto.
Si miramos un bloque y su contraparte de imagen especular:
{partsS[[6, 10]], partsM[[6, 10]]}
Podemos ver que son similares, pero cambiaron. La cantidad y la dirección del cambio es lo que estamos tratando de averiguar.
Para cuantificar la similitud de coincidencia, puedo usar la distancia euclidiana al cuadrado:
ListPlot3D[
ImageData[
ImageCorrelate[partsM[[6, 10]], partsS[[6, 10]],
SquaredEuclideanDistance]]]
Lamentablemente, el uso de estos datos es que la optimización directamente fue más difícil de lo que pensaba, así que en su lugar utilicé una aproximación de segundo orden:
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 función no es la misma que la función de correlación real, pero está lo suficientemente cerca para un primer paso. Calculemos esto para cada par de bloques:
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];
Esto nos da nuestro primer término energético para la optimización:
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 los desplazamientos para cada bloque y matchEnergyFit
aproxima la diferencia euclidiana al cuadrado entre la imagen original y la imagen reflejada con los desplazamientos aplicados.
La optimización de esta energía por sí sola daría malos resultados (si convergiera en absoluto). También queremos que los desplazamientos sean suaves, donde la similitud del bloque no dice nada sobre el desplazamiento (por ejemplo, a lo largo de una línea recta o en el fondo blanco).
Entonces establecemos un segundo término de energía para suavidad:
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]];
Afortunadamente, la optimización restringida está incorporada en Mathematica:
allVariables = Flatten[{variablesX, variablesY}];
constraints = -blockSize/3. < # < blockSize/3. & /@ allVariables;
initialValues = {#, 0} & /@ allVariables;
solution =
FindMinimum[{matchEnergyFit + 0.1 smoothnessEnergy, constraints},
initialValues];
Veamos el resultado:
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]
}]]
El 0.1
factor anterior smoothnessEnergy
es el peso relativo que obtiene la energía de suavidad en relación con el término de energía de coincidencia de imagen. Estos son resultados para diferentes pesos:
Posibles mejoras:
- Como dije, primero realice un registro rígido. Con un fondo blanco, el registro simple basado en momentos de la imagen debería funcionar bien.
- Este es solo un paso. Puede usar las compensaciones que encontró en un paso y mejorarlas en un segundo paso, tal vez con una ventana de búsqueda más pequeña o tamaños de bloque más pequeños
- He leído artículos donde hacen esto sin bloques en absoluto, pero optimizo un desplazamiento por píxel.
- Pruebe diferentes funciones de suavidad