Como eu disse nos comentários, o registro de imagens médicas é um tópico com muitas pesquisas disponíveis, e eu não sou especialista. Pelo que li, a idéia básica comumente usada é definir um mapeamento entre duas imagens (no seu caso, uma imagem e sua imagem espelhada), depois definir termos de energia para suavidade e semelhança de imagem se o mapeamento for aplicado e, finalmente, otimize esse mapeamento usando técnicas de otimização padrão (ou às vezes específicas de aplicativos).
Eu criei um algoritmo rápido no Mathematica para demonstrar isso. Este não é um algoritmo que você deve usar em um aplicativo médico, apenas uma demonstração das idéias básicas.
Primeiro, carrego sua imagem, espelho e divido essas imagens em pequenos blocos:
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, faríamos um registro rígido aproximado (usando, por exemplo, pontos-chave ou momentos de imagem), mas sua imagem está quase centralizada, então eu vou pular isso.
Se olharmos para um bloco e sua contrapartida de imagem espelhada:
{partsS[[6, 10]], partsM[[6, 10]]}
Podemos ver que eles são semelhantes, mas mudaram. A quantidade e a direção da mudança é o que estamos tentando descobrir.
Para quantificar a similaridade da correspondência, posso usar a distância euclidiana ao quadrado:
ListPlot3D[
ImageData[
ImageCorrelate[partsM[[6, 10]], partsS[[6, 10]],
SquaredEuclideanDistance]]]
infelizmente, usar esses dados é a otimização diretamente mais difícil do que eu pensava, então usei uma aproximação de segunda ordem:
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}]
A função não é a mesma que a função de correlação real, mas está próxima o suficiente para um primeiro passo. Vamos calcular isso para cada par de blocos:
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];
Isso nos dá nosso primeiro termo energético para a otimização:
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
contém as compensações para cada bloco e matchEnergyFit
aproxima a diferença euclidiana ao quadrado entre a imagem original e a imagem espelhada com as compensações aplicadas.
A otimização dessa energia por si só daria resultados ruins (se é que convergiam). Também queremos que as compensações sejam suaves, onde a similaridade do bloco não diz nada sobre a compensação (por exemplo, ao longo de uma linha reta ou no fundo branco).
Então, configuramos um segundo termo de energia para suavidade:
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]];
Felizmente, a otimização restrita está integrada no Mathematica:
allVariables = Flatten[{variablesX, variablesY}];
constraints = -blockSize/3. < # < blockSize/3. & /@ allVariables;
initialValues = {#, 0} & /@ allVariables;
solution =
FindMinimum[{matchEnergyFit + 0.1 smoothnessEnergy, constraints},
initialValues];
Vejamos o 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]
}]]
O 0.1
fator anterior smoothnessEnergy
é o peso relativo que a energia da suavidade recebe em relação ao termo de energia da correspondência de imagem. Estes são resultados para pesos diferentes:
Possíveis melhorias:
- Como eu disse, faça um registro rígido primeiro. Com um fundo branco, o registro simples baseado em momentos da imagem deve funcionar bem.
- Este é apenas um passo. Você pode usar os desvios encontrados em uma etapa e melhorá-los em uma segunda etapa, talvez com uma janela de pesquisa menor ou tamanhos de bloco menores
- Eu li artigos em que eles fazem isso sem blocos, mas otimizam um deslocamento por pixel.
- Experimente diferentes funções de suavidade