После прочтения статьи про поиск изображения в изображении[1], осталось множество вопросов к формулам, да и к самому коду где преобразование массивов мне показалось не прозрачным из-за использования множества сторонних библиотечных функций.
Потому занялся дополнительным поиском готовых реализаций, но к сожалению не смотря на обилие упоминаний идеи 1974 года[2], реализаций алгоритма, даже на законодателе моды в вычислительной математике Фортране я не обнаружил. В семинарах и лекциях да и в диссертациях описание не блистало целостностью, потому собрав с десяток статей и обсуждений в кучу появилось желание написать статью для тех кто простейшую реализацию поиска подстроки хочет "подержать в руках".
И так, написание алгоритмических программ обычно произвожу сначала в математических пакетах, и только после выяснения численной устойчивости и корректности работы алгоритма перевожу в код на компилируемые или байт ориентированные языки. Таков уж мой опыт, - или считать медленно но точно, или быстро но то что уже практически известно. Потому для отладочного иллюстративного кода использовал Wolfram Language и набор функций пакета Mathematica V 12.
Собственно в чем ценность идеи: использование дискретного Фурье преобразования (DFT) сокращает сложность вычисления от "наивного" O(n*m) до O(n Log(n)), где n - размер текста а m - размер искомого образца. Более того расширения метода позволяют производить поиск с "Джокером", - символом обозначающим любой другой символ в алфавите, в то время как суффиксные реализации это сделать не позволяют.
Описание "наивного" подхода:
Для массива T размером n и образца P размером m, вычислим функцию квадратов разницы значений элементов. Нумерация в массиве начинается с нуля.
Очевидно что в точке соответствия искомая сумма показывает минимум, если точнее обнуляется. После раскрытия квадрата под суммой получаются три слагаемых, последнее из которых имеет постоянное значение. Соответственно для поиска минимума необходимо вычислить только первые две суммы. Можно увидеть что прямое вычисление всех элементов S требует O((n-m+1)*m) операций, или оценочно O(n*m).
В качестве массива используем строчку из картинки на половине ее высоты, а образец который будем искать пусть будет отрезком в первой половине этой строчки:
Произведем прямое вычисление искомой функции:
Img = ColorConvert[Import["Test.png"], "Grayscale"];
{W, H} = ImageDimensions[Img];
y = IntegerPart[H/2]; (*Pattern width and coordinates*)
x = IntegerPart[W/4];
w = IntegerPart[W/8];
L = Part[ImageData[ImageTake[Img, {y, y}]],1]; (*Stripe Array*)
T[i_] := Table[Part[L[[i + j - 1]], 1], {j, 1, w}] ; (*Sample data*)
P[i_] := Table[Part[L[[j + x - 1]], 1], {j, 1, w}] ; (*Pattern data*)
TT = Table[Sum[(T[i][[j]]* T[i][[j]]), {j, 1, w}], {i, 1, W - w + 1}];
PT = Table[Sum[(P[i][[j]]* T[i][[j]]), {j, 1, w}], {i, 1, W - w + 1}];
ListPlot[TT - 2 PT, Joined -> True, PlotRange -> All]
Как видно в точке (x=175), где был взят образец, функция показала минимальное значение и повторила его значение ведь изображение почти дублируется.
Свойства дискретного Фурье преобразования.
Вместо обычного описания определений и свойств приведу всего две вспомогательных программы, которые мне помогли почувствовать действие скользящих индексов и увидеть точные значения смещений для использования в дальнейших вычислениях.
Вычисление PT
PolyT = {1, 2, 3, 4, 5}; LT = Length[PolyT];
PolyP = {1, 2, 3}; LP = Length[PolyP];
PolyR = {}; LR = LT + LP - 1;
eT = Table[If[i > LT, 0, PolyT[[i]]], {i, 1, LR}]
eP = Table[If[i > LP, 0, PolyP[[i]]], {i, 1, LR}]
PolyR = InverseFourier[
Fourier[eT, FourierParameters -> {1, 1}]*
Fourier[eP, FourierParameters -> {1, 1}]
, FourierParameters -> {1, 1}]
PolyR[[LP ;; LT]]
результат действия такого кода:
{1, 2, 3, 4, 5, 0, 0} (* PolyT *)
{1, 2, 3, 0, 0, 0, 0} (* PolyP *)
{1., 4., 10., 16., 22., 22., 15.} (* PolyR = PolyT * PolyP *)
{10., 16., 22.} (* PolyR starting at LP to LT*)
Итак, если массив значений представить полиномами, то получаемое значение тоже полином размером n+m-1.
Более того, начиная с позиции m (если нумерация начинается с единицы) мы получаем сумму перекрестного произведения элементов для окна длинны m:
10 = 1*3+2*2+3*1
16 = 2*3+3*2+4*1
...
Потому для вычисления элементов PT искомый образец P разворачивается. Всего получается n-m+1 значений.
Вычисление TT
PolyT = {1, 2, 3, 4, 5}; LT = Length[PolyT];
PolyP = {1, 1, 1}; LP = Length[PolyP];
PolyR = {}; LR = LT + LP - 1;
eT = Table[If[i > LT, 0, PolyT[[i]]], {i, 1, LR}]
eP = Table[If[i > LP, 0, PolyP[[i]]], {i, 1, LR}]
PolyR = InverseFourier[
Fourier[eT, FourierParameters -> {1, 1}]*
Fourier[eP, FourierParameters -> {1, 1}]
, FourierParameters -> {1, 1}]
PolyR[[LP ;; LT]]
результат действия кода:
{1, 2, 3, 4, 5, 0, 0} (* PolyT *)
{1, 1, 1, 0, 0, 0, 0} (* PolyP *)
{1., 3., 6., 9., 12., 9., 5.} (* PolyR = PolyT * PolyP *)
{6., 9., 12.} (* PolyR starting at LP to LT*)
6 = 1*1+2*1+3*1
9 = 2*1+3*1+4*1
...
Учитывая предыдущий пример, в массив текста заносятся квадраты значений элементов, а в массив искомого образца единицы, длинна последовательности единиц - m.
Сборка и сравнение
Вычисление PP и TT с использованием DFT:
Tt = Table[If[1 <= i <= W, Part[L[[i]], 1], 0], {i, 1, Wt}] ; (*Sample data*)
Ft = Fourier[Tt, FourierParameters -> {1, 1}];
Ts = Table[If[1 <= i <= W, (Part[L[[i]], 1])^2, 0], {i, 1, Wt}]; (*Sample squared data*)
Fs = Fourier[Ts, FourierParameters -> {1, 1}];
Es = Table[If[1 <= i <= w, 1, 0], {i, 1, Wt}] ; (*m size unit array*)
Fe = Fourier[Es, FourierParameters -> {1, 1}];
Pa = Table[If[1 <= i <= w, Part[L[[x + w - i]], 1], 0], {i, 1, Wt}]; Fp = Fourier[Pa, FourierParameters -> {1, 1}]; (*Inverse pattern data*)
TTf = InverseFourier[Fs*Fe, FourierParameters -> {1, 1}][[w ;; W]];
PTf = InverseFourier[Ft*Fp, FourierParameters -> {1, 1}][[w ;; W]];
Сравниваем полученные значения:
ListPlot[{TT - 2 PT, TTf - 2 PTf, TT - 2 PT - TTf + 2 PTf}, Joined -> True, PlotRange -> All]
Выводы
Не смотря на то что метод приближенный, его точности более чем достаточно для работы с текстом и большинством обычных изображений где значения яркости отличаются в разы.
Приведенный код не претендует на оптимальность быстродействия а предназначен больше для удобства понимания и оценки точности алгоритма.
MichaelBorisov
Верно ли, что ваш алгоритм вычисляет взаимнокорреляционную последовательность? И поэтому, по теореме о свёртке, такую взаимную корреляцию можно вычислять с помощью преобразования Фурье?
Desireless Автор
Да алгоритм как промежуточный результат для вычисления PT это использует, вычисление TT тоже имеет свое математическое название хоть и является частным случаем первого.
Но вместо известных математических определений подразумевающих выводы из индукции, и последующего дедуктивного применения я остановился в изложении только на первом, для того чтоб показать значения смещений и длин искомых массивов.
Desireless Автор
Если вы обратите внимание, то в определении свертки и взаимной корреляционной последовательности есть различие, в интегральной форме смещение незаметно так как пределы интегрирования бесконечны, но для понимания алгоритма нахождения искомой суммы определение свертки менее подходящее. Потому я тоже избегал упоминания теоремы о свертке так как это требует пусть не значительной но замены переменных и дополнительных преобразований.
Refridgerator
Это очень спорное утверждение, сможете его подтвердить реальным примером, или я что-то не так понял? Потому что смещение во времени — это просто линейный наклон фаз вне зависимости от того, дискретное или непрерывное преобразование используется.
Desireless Автор
разумеется:
— использование инверсии и взаимнокорреляционной функции приводят началу последовательности PT с позиции m
— использование свертки и сопряжения, приводят началу последовательности PT с позиции 1
Такое смещение в точности соответствует преобразованию для изменения «знака функции времени второго аргумента».
Refridgerator
В комментарии, на который вы сослались, я не увидел ответа на свой вопрос. Бесконечные пределы интегрирования предполагают аналитическое решение с привлечением аналитических же функций типа такой.
Desireless Автор
В DFT интегрирования нет вообще, наиболее простое его определение связано со значением полинома в точке, приводящему при подстановке к подобию части ряда Фурье. Соответственно и имеет все свойства этого преобразования, доказательство обычно проводят индукцией, но все же отличие DFT и ряда есть, первое это точное преобразование.
Теперь про интегрирование(приведу без комплексного варианта),
— в сверточном определении f(x)g(y-x)dx
— для взаимнокорреляционной функции f(x+y)g(x)dx
переход от одного к другому это замена переменных
x=x+y — вот откуда смещение.
Если вы будете искать t[i+j]p[j] в произведении полиномов
A?B=InvDFT(DFT(A)?DFT(B))
то получите начальную позицию m.
Я использую именно свойство DFT так как оно определено аналитически точно, но в пределах машинной точности вычислений.
Тогда как для использования теоремы о свертке мне придется сделать логический переход от бесконечных сумм в преобразованиях Фурье к конечным, сделать замену переменных, и обосновать исходя из редукции размеры используемых массивов.
Дополнительные действия есть как при использовании DFT, так и при FT, но для первого их меньше. В названии статьи приведено DFT и именно его свойствами я и пользовался в создании алгоритма, хотя можно использовать и свойства FT
MichaelBorisov
Что-то я не пойму, о чём спор. Есть же дискретный вариант теоремы о свёртке, там всё прекрасно выражается через ДПФ. А знак времени для одной из функций (то, чем различается свёртка от автокорреляции) — это уже мелочи.
Desireless Автор
Спор о выборе теории для иллюстрации действия алгоритма. Автокорреляция это еще один термин который от темы уводит дальше чем свертка.
Desireless Автор
Дело в разных размерах массивов, на практике можно получить нужную последовательность несколькими способами, но они отличаются разными способами подготовки массивов и позициями в них. Что говорить на остаток и саму позицию здесь и разбираем.
В лекциях и статьях уделяют внимание больше самой последовательности а остальное или чем то обозначают или просто оставляют как несущественное, иногда наговаривают и наукообразие.
К той же близкой по виду взаимнокорреляционной функции уже есть вопросы, инверсия порядка и отрицательное время похожи вот только для ряда нет отрицательных индексов, или значения нужно дополнительно определять, или иметь константу обеспечивающую положительность разницы, или и то и другое. На все эти вопросы более просто отвечает DTF.