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

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

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

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

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

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

enter image description here

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

enter image description here

но я хочу больше:

enter image description here

Это плохо, потому что 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 соседних узла), и это возможно, потому что я уже работаю над довольно тонкими контурами. Затем я могу запустить алгоритм минимального связующего дерева. И, наконец, я могу аппроксимировать каждую ветвь дерева с помощью openCV approxPolyDP()

сегментация http://img197.imageshack.us/img197/4488/segmentation.png

Вот фантастические снимки (спасибо Paint!) исходного списка и связанный граф. Затем, когда я добавляю ребра между соседями. И, наконец, когда я удаляю ребра и создаю минимальное остовное дерево (здесь не полезно)

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


Изменить: Чтобы уточнить, как только у меня есть дерево, я могу извлечь "ветки" (ветки начинаются у листьев или узлов, связанных с 3 или более другими узлами). Тогда алгоритм в openCV approxPolyDP() является алгоритм Рамера-Дугласа-Пьюкера, и вот фотография Википедии о том, что она делает:

enter image description here

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


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

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

фундаментально отличается от

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

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

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


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

enter image description here

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

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

enter image description here

4b9b3361

Ответ 1

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

enter image description here

enter image description here

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

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"]]

enter image description here

И тогда вершина связана после анализа их связности.

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

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

enter image description here

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

Ответ 2

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

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

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

enter image description here

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

enter image description here

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

Другая идея.

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

enter image description here

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

enter image description here

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

Ответ 3

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

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 imageDetected segments

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