Извлечение сегментов из списка 8-связанных пикселей

Текущая ситуация: я пытаюсь извлечь сегменты из изображения. Благодаря методу openCV findContours() теперь у меня есть список 8-связных точек для каждого контура. Однако эти списки нельзя использовать напрямую, поскольку они содержат много дубликатов.

Проблема: дан список из 8 связанных точек, которые могут содержать дубликаты, извлечь из него сегменты.

Возможные решения:

  • Сначала я использовал метод openCV approxPolyDP(). Однако результаты довольно плохие... Вот увеличенные контуры:

введите здесь описание изображения

Вот результат approxPolyDP(): (9 сегментов! Некоторые перекрываются)

введите здесь описание изображения

но то, что я хочу, больше похоже на:

введите здесь описание изображения

Это плохо, потому что approxPolyDP() может преобразовать то, что "выглядит как несколько сегментов" в "несколько сегментов". Однако у меня есть список точек, которые имеют тенденцию повторяться несколько раз.

Например, если мои точки:

0 1 2 3 4 5 6 7 8 
  9   

Тогда список точек будет 0 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 9... И если количество точек станет большим (>100), то сегменты, извлеченные approxPolyDP(), к сожалению, не дублируются (т.е.: они перекрывают друг друга, но не строго равны, поэтому я нельзя просто сказать "удалить дубликаты", в отличие, например, от пикселей)

  • Возможно, у меня есть решение, но оно довольно длинное (хотя и интересное). Прежде всего, для всех 8-связных списков я создаю разреженную матрицу (для эффективности) и устанавливаю значения матрицы равными 1, если пиксель принадлежит списку. Затем я создаю график с узлами, соответствующими пикселям, и ребрами между соседними пикселями. Это также означает, что я добавляю все недостающие ребра между пикселями (сложность небольшая, возможно из-за разреженной матрицы). Затем я удаляю все возможные "квадраты" (4 соседних узла), и это возможно, потому что я уже работаю с довольно тонкими контурами. Затем я могу запустить алгоритм минимального связующего дерева. И, наконец, я могу аппроксимировать каждую ветвь дерева с помощью approxPolyDP() openCV.

Подводя итог: у меня есть утомительный метод, который я еще не реализовал, так как он кажется подверженным ошибкам. Тем не менее, я спрашиваю вас, людей из Stack Overflow: существуют ли другие существующие методы, возможно, с хорошей реализацией?


Изменить: чтобы уточнить, когда у меня есть дерево, я могу извлечь «ветви» (ветки начинаются с листьев или узлов, связанных с 3 или более другими узлами). Затем алгоритм в approxPolyDP() openCV - это алгоритм Ramer-Douglas-Peucker, а вот изображение из Википедии, что он делает:

введите здесь описание изображения

С этой картинкой легко понять, почему она терпит неудачу, когда точки могут быть дубликатами друг друга.


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

X-X-X-X
|
X-X-X-X

принципиально сильно отличается от

X-X-X-X
| | | |
X X X X

но оба являются минимальными остовными деревьями

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


Ответ на комментарий Томалака:

введите здесь описание изображения

Если бы алгоритм DP возвращал 4 сегмента (отрезок от точки 2 до центра был там дважды), я был бы счастлив! Конечно, при хороших параметрах я могу дойти до состояния, когда «случайно» у меня есть одинаковые сегменты, и я могу удалить дубликаты. Однако очевидно, что алгоритм не предназначен для этого.

Вот реальный пример со слишком большим количеством сегментов:

введите здесь описание изображения


person B. Decoster    schedule 20.06.2011    source источник
comment
are unfortunately not duplicates (i.e : they overlap each other, but are not stricly equal, so I can't just say "remove duplicates", as opposed to pixels for example) Я этого не понимаю. Почему нет?   -  person Lightness Races in Orbit    schedule 20.06.2011
comment
Я ответил, отредактировав свой пост еще одной великолепной картинкой в ​​Paint =)   -  person B. Decoster    schedule 20.06.2011
comment
Я смотрю на картинку, которую вы добавили в ответ Томалаку, и у меня есть сомнение: результат алгоритма DP на самом деле является допустимым решением, если вы допускаете, что наблюдаемые точки могут быть настолько далеки от истины! отрезок как центральная точка изображения. Есть ли у вас определенный порог для расстояния точка-линия, который должен соблюдать алгоритм?   -  person Coffee on Mars    schedule 20.06.2011
comment
Нет, у меня нет определенного порога, но проблема в том, что я имею дело с тысячами изображений с тысячами контуров. Если я установлю определенное значение для порога, то для большинства контуров у меня будет ужасный беспорядок из перекрывающихся сегментов. Я понимаю, что то, что возвращает алгоритм DP, действительно. Но вы только посмотрите на синие сегменты на первой картинке моего поста. Вы можете видеть, что есть много сегментов, где было бы необходимо только два или три. И они перекрываются из-за 8-связного характера списка.   -  person B. Decoster    schedule 20.06.2011


Ответы (4)


Используя Mathematica 8, я создал морфологический граф из списка белых пикселей на изображении. Он отлично работает на вашем первом изображении:

введите здесь описание изображения

введите здесь описание изображения

Создайте морфологический граф:

graph = MorphologicalGraph[binaryimage];

Затем вы можете запросить интересующие вас свойства графика.

Это дает имена вершин в графе:

vertex = VertexList[graph]

Список ребер:

EdgeList[graph]

И это дает позиции вершины:

pos = PropertyValue[{graph, #}, VertexCoordinates] & /@ vertex

Вот как выглядят результаты для первого изображения:

In[21]:= vertex = VertexList[graph]

Out[21]= {1, 3, 2, 4, 5, 6, 7, 9, 8, 10}

In[22]:= EdgeList[graph]

Out[22]= {1 \[UndirectedEdge] 3, 2 \[UndirectedEdge] 4,  3 \[UndirectedEdge] 4, 
          3 \[UndirectedEdge] 5, 4 \[UndirectedEdge] 6,  6 \[UndirectedEdge] 7, 
          6 \[UndirectedEdge] 9, 8 \[UndirectedEdge] 9,  9 \[UndirectedEdge] 10}

In[26]:= pos = PropertyValue[{graph, #}, VertexCoordinates] & /@ vertex

Out[26]= {{54.5, 191.5}, {98.5, 149.5},  {42.5, 185.5}, 
          {91.5, 138.5}, {132.5, 119.5}, {157.5, 72.5},
          {168.5, 65.5}, {125.5, 52.5},  {114.5, 53.5}, 
          {120.5, 29.5}}

Учитывая документацию, http://reference.wolfram.com/mathematica/ref/MorphologicalGraph.html команда MorphologicalGraph сначала вычисляет скелет путем морфологического прореживания:

skeleton = Thinning[binaryimage, Method -> "Morphological"]

Затем обнаруживаются вершины; это точки ветвления и конечные точки:

verteximage = ImageAdd[
                  MorphologicalTransform[skeleton, "SkeletonEndPoints"],   
                  MorphologicalTransform[skeleton, "SkeletonBranchPoints"]]

введите здесь описание изображения

А затем вершины связываются после анализа их связности.

Например, можно начать с разрушения структуры вокруг вершины, а затем искать связанные компоненты, открывая ребра графа:

comp = MorphologicalComponents[
           ImageSubtract[
               skeleton, 
               Dilation[vertices, CrossMatrix[1]]]];
Colorize[comp] 

введите здесь описание изображения

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

person Matthias Odisio    schedule 27.06.2011
comment
@karlphillip: Спасибо, еще не знал об этом - person Matthias Odisio; 27.06.2011
comment
+1 Но поскольку ОП не просит решения в Mathematica, вы должны объяснить алгоритмы, лежащие в основе MorphologicalGraph[] - person Dr. belisarius; 27.06.2011
comment
Отредактировано соответствующим образом, спасибо за это и разрешение фотографий. - person Matthias Odisio; 27.06.2011
comment
О, большое спасибо! Ну, я не использую Mathematica и не могу ее внедрить. Я попробую посмотреть на MorphologicalGraph[], но если вы представляете, как это работает, я буду бесконечно благодарен =). И, если никто не придумает что-то удивительное, награда за вас! - person B. Decoster; 28.06.2011
comment
@Fezvez Спасибо за поддержку. Я не знаю, как работает MorphologicalGraph, но я предложил еще один шаг вперед. Алгоритм все равно требует набора морфологических инструментов, которые, как сказал Андрей, могут отсутствовать в OpenCV. Если вы не можете использовать Mathematica, вы можете поискать другую библиотеку, которая бы предоставляла морфологические функции. - person Matthias Odisio; 28.06.2011

Попробуйте математическую морфологию. Сначала вам нужно dilate или close ваше изображение, чтобы заполнить пробелы.

cvDilate(pimg, pimg, NULL, 3);
cvErode(pimg, pimg, NULL);

я получил это изображение

введите здесь описание изображения

Следующим шагом должно быть применение алгоритма утончения. К сожалению, это не реализовано в OpenCV (у MATLAB есть bwmorph с аргументом thin). Например, с MATLAB я улучшил изображение до этого:

введите здесь описание изображения

Однако OpenCV имеет все необходимые базовые морфологические операции для реализации прореживания (cvMorphologyEx, cvCreateStructuringElementEx и т. д.).

Еще одна идея.

Говорят, что преобразование расстояния кажется очень полезным в таких задачах. Может быть, так. Рассмотрим функцию cvDistTransform. Он создает такое изображение:

введите здесь описание изображения

Затем используйте что-то вроде cvAdaptiveThreshold:

введите здесь описание изображения

Это скелет. Я думаю, вы можете перебрать все связанные белые пиксели, найти кривые и отфильтровать небольшие сегменты.

person Andrey Sboev    schedule 20.06.2011
comment
Спасибо за отзыв! Это интересный материал, но я боюсь, что произошло недоразумение: изображение было увеличенной версией того, что было у меня. Я очистил это в своем посте. Я хочу сделать ваше второе изображение на входе и вывести 3 сегмента. - person B. Decoster; 20.06.2011

Я реализовал аналогичный алгоритм раньше, и я сделал это в виде постепенного метода наименьших квадратов. Это сработало довольно хорошо. Псевдокод примерно такой:

L = empty set of line segments
for each white pixel p
  line = new line containing only p
  C = empty set of points
  P = set of all neighboring pixels of p
  while P is not empty
    n = first point in P
    add n to C
    remove n from P
    line' = line with n added to it
    perform a least squares fit of line'
    if MSE(line) < max_mse and d(line, n) < max_distance
      line = line'
      add all neighbors of n that are not in C to P
  if size(line) > min_num_points
    add line to L

где MSE (линия) — среднеквадратическая ошибка линии (сумма по всем точкам на линии квадрата расстояния до наиболее подходящей линии), а d (линия, n) — расстояние от точки n до линии. Хорошие значения для max_distance кажутся пикселями или около того, а max_mse кажется намного меньше и будет зависеть от среднего размера линейных сегментов на вашем изображении. 0,1 или 0,2 пикселя работали на довольно больших изображениях для меня.

Я использовал это на реальных изображениях, предварительно обработанных с помощью оператора Canny, поэтому у меня есть только это. Вот результат вышеописанного алгоритма для изображения: Raw imageОбнаруженные сегменты

Алгоритм тоже можно сделать быстрым. Имеющаяся у меня реализация C++ (закрытый исходный код, вызванный моей работой, извините, иначе я бы дал его вам) обработала приведенное выше изображение примерно за 20 миллисекунд. Это включает в себя применение оператора Canny для обнаружения границ, поэтому в вашем случае это должно быть еще быстрее.

person Sean    schedule 30.06.2011

Вы можете начать с извлечения прямых линий из изображения контуров, используя HoughLinesP, который предоставляется с openCV:

HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength = 0, double maxLineGap = 0)  

Если вы выберете маленькие threshold = 1 и minLineLenght, вы даже можете получить все отдельные элементы. Однако будьте осторожны, так как это дает много результатов, если у вас много краевых пикселей.

person Sebastian-CV-ML    schedule 13.06.2018