Установка многоугольника в MATLAB

Определение проблемы

Я пытаюсь восстановить невыпуклую и несамопересекающуюся ломаную линию (которую я называю целевой линией) из зашумленного набора данных (см. рисунок ниже). Поэтому моя цель — узнать положение вершин целевой линии из набора данных. Я называю предполагаемую ломаную линию ломаной линией.

Целевая линия предполагается нормированной, т.е. заключенной в интервалах [-1,1] (горизонтальное направление) и [0,1] (вертикальное направление). При этом целевая линия начинается из точки [1 0]' и заканчивается в точке, пересекающей горизонтальную ось системы отсчета.

Основными проблемами этой проблемы являются следующие:

  • Набор данных неупорядочен, а это означает, что единственная доступная информация — это положение зашумленных наблюдений. Порядок относится к временной последовательности, в которой мы наблюдали бы наблюдения, если бы путешествовали вдоль целевой линии от ее начальной точки до конечной точки. Важно отметить, что если указан порядок, проблему можно легко решить с помощью стандартных решателей аппроксимации кривой (например, путем минимизации методом наименьших квадратов). Однако это не мой случай...

  • Алгоритм должен быть быстрым даже для больших наборов данных (т. е. время вычислений должно быть порядка секунд, даже если набор данных содержит 1e4 или даже больше наблюдений), потому что мне нужно реализовать его в гораздо более крупной системе, которая работает в режиме реального времени. . Таким образом, я не могу рассматривать причудливые алгоритмы, основанные на задачах оптимизации.

  • Алгоритм должен быть надежным, когда набор данных небольшой (например, если количество наблюдений порядка 10 или меньше) или когда стандартное отклонение гауссовского шума велико (например, больше 1 в текущих нормализованных единицах измерения). . Под устойчивым я подразумеваю, что в этих сложных сценариях предполагаемая форма должна быть чем-то простым и осмысленным, например полуэллипсом, а не случайно переплетающейся линией. Другими словами, алгоритм должен проявлять осторожность: если информация ограничена, ответ должен быть простым и расплывчатым.

  • Количество вершин предполагаемой ломаной должно быть равно заданному значению N

Я могу получить приблизительную оценку с помощью довольно быстрого алгоритма (даже несмотря на то, что он не оптимизирован), поэтому я ищу предложения по его улучшению с учетом некоторых критических проблем. Я использую следующие соглашения:

  • hatV — матрица размера 2xN, содержащая координаты вершин N. формирование ломаной линии;
  • D — матрица размером 2xm, содержащая координаты наблюдений m, формирующих набор данных. Такие точки генерируются путем равномерного выбора точки на целевой линии и последующего добавления гауссовского шума.

Мой алгоритм принимает на вход D, N и возвращает hatV.


Мой алгоритм (обзор)

function hatV = staticShaper(D, N)
    % step 0: unfrozen initialization 
    hatV   = 1.5 * [cos(linspace(0, pi, N)); sin(linspace(0, pi, N))];
    freeze = zeros(1, N);

    for iter = 1 : 3
        % step 1: project the polygonal line over the dataset
        idx = projectLine(hatV, D);
       
        % step 2: update the polygonal line
        hatV = updateLine(hatV, D, idx, freeze);

        % step 3: simplify the polygonal line
        [hatV, Ndeficit] = simplifyLine(hatV);

        % step 4: interpolate and freeze the polygonal line
        [hatV, freeze] = interpolateLine(hatV, Ndeficit);
    end
end
  • Шаг 0: По предположению, целевая линия, которую я хочу оценить (и соответствующий набор данных D), полностью содержится в интервалах [-1, 1] (горизонтальное направление) и [0, 1] (вертикальное направление). Соответственно, я инициализирую ломаную линию hatV, размещая вершины N вдоль полукруга радиуса 1,5, гарантируя, что hatV охватывает целевую линию. Кроме того, я использую вектор freeze, содержащий флаг для каждой вершины. Если freeze(i) == 1, то i-я вершина заморожена, то есть ее положение нельзя изменить. Если freeze(i) == 0, то i-я вершина не заморожена, что позволяет изменить ее положение.
  • Для простоты я рассматриваю цикл с заранее заданным номером итерации. Помимо этого упрощения, я думаю, что смогу доказать, что мой алгоритм всегда сходится за конечное число итераций, потому что в конце концов все вершины замораживаются.
  • Шаг 1: Для каждой отдельной вершины текущей оцененной ломаной линии я ищу в наборе данных ближайшее наблюдение. Здесь idx(i) указывает, что ближайшее к вершине i наблюдение является idx(i)-м наблюдением в наборе данных D.
  • Шаг 2. Обновите ломаную линию, разместив каждую незамороженную вершину. над его ближайшим наблюдением.
  • Шаг 3: Предыдущая операция может привести к размещению нескольких вершин над одним и тем же наблюдением, что приведет к избыточности. На этом этапе ломаная линия упрощается за счет объединения вершин, расположенных близко друг к другу, что устраняет избыточность. В ходе этого процесса количество вершин может уменьшиться с N до N - Ndeficit.
  • Шаг 4: Чтобы конечный результат содержал N вершин, я вставляю Ndeficit новых вершин в упрощенную ломаную линию. Вновь вставленные вершины представляют собой степень свободы, а старые вершины считаются фиксированными. Поэтому я замораживаю старые вершины и обозначаю вновь вставленные вершины как незамороженные, позволяя обновить их позиции в следующем цикле алгоритма.

Вопросы

На этом рисунке изображены четыре разных шага моего алгоритма за три итерации с учетом N = 50. Точки обозначают незамороженные вершины, а точки в кружках обозначают замороженные вершины. Черные линии соединяют незамороженные вершины с ближайшими к ним наблюдениями в наборе данных.

Меня больше всего беспокоит вопрос о том, как повысить точность результата. В моем решении есть две основные проблемы.

  • проблема 1: мой алгоритм не может проникать внутрь «жестких» вогнутых областей.
  • проблема 2: мой алгоритм не может соответствовать набору данных, а скорее очерчивает «внешнюю» границу набора данных.

Итак, мой первый вопрос: как мне решить эти две проблемы? Помимо этого вопроса у меня возникает следующий: как мне доказать (или опровергнуть), что мой алгоритм генерирует ломаную, не пересекающуюся сам с собой?

Мой алгоритм (подробнее)

function idx =  projectLine(hatV, D)
    idx = NaN(1, size(hatV, 2));
    for i = 1 : size(hatV, 2)
        [~, idx(i)] = min(vecnorm(hatV(:, i) - D));
    end
end

Для каждой вершины hatV(:, i) я ищу ближайшее наблюдение в D.


function hatV = updateLine(hatV, D, idx, freeze)
    for i = 1 : size(hatV, 2)
        if not(freeze(i))
            hatV(:, i) = D(:, idx(i));
        end
    end

    hatV(:, 1)   = [1 0]';
    hatV(2, end) = 0;
end

Я помещаю каждую незамороженную вершину hatV(:, i) над ближайшим к ней наблюдением D(:, idx(i)). После этого я проверяю, что первая вершина hatV(:, 1) находится в точке [1 0]', и подтверждаю, что конечная вершина hatV(:, end) не имеет вертикального компонента. Это потому, что я знаю, что начальной точкой целевой линии является [1 0]' и что целевая линия заканчивается на горизонтальной оси системы отсчета.


function [hatV, Ndeficit] = simplifyLine(hatV)
    oldHatV    = hatV; % backup 
    k          = 1;
    hatV       = [];
    hatV(:, 1) = oldHatV(:, 1);
    for i = 1 : size(oldHatV, 2)
        if norm(oldHatV(:, i) - hatV(:, k)) < 0.01
            hatV(:, k) = 0.5 * (hatV(:, k) + oldHatV(:, i));
        else
            k          = k + 1;
            hatV(:, k) = oldHatV(:, i);
        end
    end
    Ndeficit = size(oldHatV, 2) - size(hatV, 2);
end

Должен сказать, что эта, казалось бы, простая задача отнимает у меня некоторое время, и я даже не уверен, что это хорошая реализация. Однако здесь я стремлюсь исключить одинаковые или похожие вершины. Для этого я исследую расстояния между вершинами. Если расстояние достаточно мало, я выполняю операцию слияния, взяв среднее значение. В конце я подсчитываю, сколько вершин потеряно при этих операциях.


function [hatV, freeze] = interpolateLine(hatV, Ndeficit)
    % step 1: normalized edge lengths computation
    ell = NaN(1, size(hatV, 2) - 1);
    for i = 1 : size(hatV, 2) - 1
        ell(i) = norm(hatV(:, i + 1) - hatV(:, i));
    end
    ell = ell / sum(ell);
    
    % step 2: new vertices generation
    newVertContainer = cell(1, size(hatV, 2) - 1);
    for i = 1 : size(hatV, 2) - 1
        nrNewVertices       = round(Ndeficit * ell(i)); 
        alpha               = 0;
        newVertContainer{i} = [];
        for j = 1 : nrNewVertices
            newVertex           = (1 - alpha) * hatV(:, i) + alpha * hatV(:, i + 1);
            newVertContainer{i} = cat(2, newVertContainer{i}, newVertex);
            alpha               = alpha + 1 / nrNewVertices;
        end
    end
    
    % step 3: output definition
    oldHatV = hatV;
    hatV    = oldHatV(:, 1);
    freeze  = 1;
    for i = 1 : size(oldHatV, 2) - 1
        hatV   = cat(2,   hatV,   newVertContainer{i}, oldHatV(:, i + 1) );
        freeze = cat(2, freeze, zeros(1, size(newVertContainer{i}, 2)), 1);
    end
end

Идея состоит в том, чтобы вставлять новые вершины «равномерно» вдоль текущей ломаной линии. Чтобы добиться этого, я добавляю к каждому краю ломаной линии несколько новых вершин, пропорциональное длине ребра.

Первый шаг — вычислить длину каждого края предполагаемой ломаной линии и нормализовать результаты. Если ребро i имеет нормализованную длину ell(i), то количество новых вершин, сгенерированных между двумя старыми вершинами, определяющими ребро, равно round(Ndeficit * ell(i)).

Понятно, что эта стратегия не гарантирует, что ломаная линия в итоге будет иметь ровно N вершин, но на данный момент эта проблема не является большой проблемой (исправлю позже).

На шаге 2 я генерирую новые вершины, которые временно сохраняются в newVertContainer. Наконец, на шаге 3 я определяю обновленную ломаную линию, в которой старые вершины замораживаются, а новые вершины размораживаются.

Не могли бы вы добавить к своему вопросу набор входных данных, чтобы у вас был Минимальный воспроизводимый пример?

BillBokeey 19.04.2024 10:38

Я бы рассматривал такого рода проблемы как типичную работу для генетического алгоритма. Каждый элемент популяции представляет собой набор из N+1 упорядоченных точек, так что вершина k переходит из точки k в точку k+1. Ключевая идея фитнес-функции состоит в том, чтобы на самом деле попытаться минимизировать расстояние между каждой исходной точкой данных и ближайшей вершиной, а не наоборот, а также добавить большой штраф за все, что вам не нужно. Если вы предоставите набор данных, я попробую.

BillBokeey 19.04.2024 10:43

Вы также можете попытаться упорядочить свой набор данных (возможно, ближайшего соседа?), а затем использовать алгоритмы, которые, как вы знаете, работают для упорядоченных наборов данных.

BillBokeey 19.04.2024 11:17

Спасибо за помощь, BillBokeey, вы можете найти данные здесь drive.google.com/file/d/1JrKIauk9mAYPFxR7QhfKy-rvOC_o1D2I/vi‌​ew Я уже пробовал ближайших соседей, но безуспешно.

matteogost 19.04.2024 11:20

Есть ли у вас какие-либо ссылки на генетические алгоритмы, используемые при подборе кривой?

matteogost 19.04.2024 14:57

ваша «проблема 2» заключается в том, что алгоритм возвращает внешнюю границу. Пробовали ли вы запустить алгоритм еще раз изнутри? Это даст вам оценку «внутренней» границы. Если определены внутренние и внешние границы, прямые сегменты (где внутренние и внешние границы псевдопараллельны) занимают среднюю точку линии результата, а сегменты, где границы «расходятся», будут обозначать/очерчивать зону с жестко вогнутыми областями. Как только эти зоны будут идентифицированы, вы можете упаковать их (как вы это сделали с исходным полным доменом) и снова запустить алгоритм на этих упакованных регионах подмножества.

Hoki 19.04.2024 15:13

Нет, извините, я предлагал сделать все с нуля. Однако я нашел функцию, которая может вас заинтересовать. k = boundary(D(1,:).',D(2,:).',1); bound = [D(1,k).',D(2,k).']; не слишком далеко от того, что вы ищете

BillBokeey 19.04.2024 15:13

«Поэтому я не могу рассматривать причудливые алгоритмы, основанные на задачах оптимизации». Как вы думаете, почему на решение проблем оптимизации уходит много времени? Алгоритмы оптимизации часто являются очень эффективными и действенными решениями реальных проблем.

Cris Luengo 20.04.2024 04:59

Это только мое мнение, я могу ошибаться. Я хотел бы использовать продвинутые алгоритмы для решения своей задачи, но мне нужно быть быстрым: мне нужно реализовать этот «формирователь» в фильтре Калмана с целью отслеживания маневрирующих самолетов. Каждые T секунд мне приходится обрабатывать совершенно новый набор данных, где T может составлять порядка некоторой доли секунды.

matteogost 20.04.2024 05:20

@Hoki: твоя идея звучит хорошо, и я попытался начать с интерьера. Однако мой алгоритм работает плохо изнутри. Возможно, мне нужно где-то что-то подкорректировать.

matteogost 20.04.2024 05:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
10
137
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
k = boundary(D(1,:).',D(2,:).',1);
bound = [D(1,k).',D(2,k).'];

(Третий входной аргумент в этом вызове функции MATLAB border является необязательным аргументом, который используется для «ужежения» границы вокруг набора данных, если она близка к 1)

Код выполняется на моей машине примерно за 20 мс и довольно хорошо показывает как верхнюю, так и нижнюю границы набора данных:

scatter(D(1,:).',D(2,:).');hold on
plot(bound(:,1),bound(:,2),'--k','linewidth',2)


Обновлено: Вот еще несколько шагов к рабочему решению:

% Start from the point closest to [1,0]
[~,idx]=min(sqrt((bound(:,1)-1).^2+bound(:,2).^2));
if idx>1

    bound = [bound(idx:end,:);bound(1:(idx-1),:)];

end

% Get the index of the next intersection with the horizontal axis
% (exclude a few points before and after the initial point)
npts_exc = 6;

[~,idx2]=min(bound((npts_exc):(end-npts_exc),2));

bound1 = bound(1:(npts_exc+idx2-1),:);
bound2 = bound((npts_exc+idx2-1):end,:);

% Follow curve 1. for each point on curve 1 get the closest one on
% curve 2 and then construct the mid point
bound_avg = zeros(size(bound1));

for ii=1:size(bound1,1)
    
    [~,idx_min]=min(sqrt((bound2(:,1)-bound1(ii,1)).^2+(bound2(:,2)-bound1(ii,2)).^2));

    bound_avg(ii,:) = 0.5*(bound1(ii,:)+bound2(idx_min,:));

end

Это по-прежнему занимает около 30 мс. Это дает следующие результаты:

scatter(D(1,:).',D(2,:).');hold on
plot(bound1(:,1),bound1(:,2),'--k','linewidth',1)
plot(bound2(:,1),bound2(:,2),'--r','linewidth',1)
plot(bound_avg(:,1),bound_avg(:,2),'-m','linewidth',2)

Последнее, что нужно сделать, — это выполнить повторную выборку кривой, чтобы получить нужную длину в N точек.

EDIT2: Окончательное решение

% Resampling so that the N points on the curve are uniformally positionned
% on the curve length
N = 50;
bound_res = zeros(N,2);
% Keep endpoints
bound_res(1,:) = bound_avg(1,:);
bound_res(end,:) = bound_avg(end,:);

% Cumulative curve length
c_len = cumsum(sqrt((bound_avg(1:(end-1),1)-bound_avg(2:end,1)).^2+(bound_avg(1:(end-1),2)-bound_avg(2:end,2)).^2));
% Curve length will have to be unique for interp1 to work
[C,ia,~] = unique(c_len);

% remove non unique points from bound_avg as well
bound_avg_un = bound_avg(ia+1,:);

% Target cumulative curve length for uniform distribution
step_size = c_len(end)/(N-1);
target_lengths = step_size*(1:(N-1));

% Interpolate on the target lengths
bound_res(2:(end-1),1) = interp1(C,bound_avg_un(:,1),target_lengths(1:(end-1)));
bound_res(2:(end-1),2) = interp1(C,bound_avg_un(:,2),target_lengths(1:(end-1)));

Другие вопросы по теме