Как я уже говорил в комментариях, регистрация медицинских изображений - это тема с большим количеством исследований, и я не эксперт. Из того, что я прочитал, основная идея, которую обычно используют, состоит в том, чтобы определить отображение между двумя изображениями (в вашем случае это изображение и его зеркальное отображение), затем определить энергетические термины для гладкости и сходства изображений, если применяется сопоставление, и, наконец, оптимизируйте это отображение, используя стандартные (или иногда зависящие от приложения) методы оптимизации.
Я взломал быстрый алгоритм в Mathematica, чтобы продемонстрировать это. Это не алгоритм, который вы должны использовать в медицинском приложении, а только демонстрация основных идей.
Сначала я загружаю ваше изображение, отражаю его и делю эти изображения на маленькие блоки:
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]
Обычно мы выполняем приблизительную жесткую регистрацию (используя, например, ключевые точки или моменты изображения), но ваше изображение почти центрировано, поэтому я пропущу это.
Если мы посмотрим на один блок и его зеркальное отражение:
{partsS[[6, 10]], partsM[[6, 10]]}
Мы видим, что они похожи, но смещены. Количество и направление сдвига - это то, что мы пытаемся выяснить.
Чтобы оценить сходство совпадений, я могу использовать квадрат евклидова расстояния:
ListPlot3D[
ImageData[
ImageCorrelate[partsM[[6, 10]], partsS[[6, 10]],
SquaredEuclideanDistance]]]
к сожалению, использование этих данных напрямую оказалось сложнее, чем я думал, поэтому я использовал вместо этого приближение 2-го порядка:
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}]
Функция не совпадает с фактической функцией корреляции, но она достаточно близка для первого шага. Давайте посчитаем это для каждой пары блоков:
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];
Это дает нам наш первый энергетический термин для оптимизации:
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
содержит смещения для каждого блока и matchEnergyFit
приближает квадрат евклидовой разницы между исходным изображением и зеркальным отражением с примененными смещениями.
Оптимизация этой энергии сама по себе дала бы плохие результаты (если бы она вообще сходилась). Мы также хотим, чтобы смещения были плавными, когда сходство блоков ничего не говорит о смещении (например, по прямой или на белом фоне).
Итак, мы установили второй энергетический член для гладкости:
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]];
К счастью, в Mathematica встроена ограниченная оптимизация:
allVariables = Flatten[{variablesX, variablesY}];
constraints = -blockSize/3. < # < blockSize/3. & /@ allVariables;
initialValues = {#, 0} & /@ allVariables;
solution =
FindMinimum[{matchEnergyFit + 0.1 smoothnessEnergy, constraints},
initialValues];
Давайте посмотрим на результат:
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
Множитель перед smoothnessEnergy
относительным весом энергия гладкости получает по отношению к термину энергии совпадения изображения. Это результаты для разных весов:
Возможные улучшения:
- Как я уже сказал, сначала выполните жесткую регистрацию. На белом фоне простая регистрация на основе моментов изображения должна работать нормально.
- Это только один шаг. Вы можете использовать смещения, которые вы нашли за один шаг, и улучшить их на втором шаге, возможно, с меньшим окном поиска или меньшим размером блока
- Я читал статьи, где они делают это вообще без блоков, но оптимизируют смещение на пиксель.
- Попробуйте разные функции сглаживания