Python OpenCV HoughLinesP не может обнаружить линии

Я использую OpenCV HoughlinesP, чтобы найти горизонтальные и вертикальные линии. Большую часть времени он не находит никаких строк. Даже когда он находит линии, он даже не близок к реальному изображению.

import cv2
import numpy as np

img = cv2.imread('image_with_edges.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)


flag,b = cv2.threshold(gray,0,255,cv2.THRESH_OTSU)

element = cv2.getStructuringElement(cv2.MORPH_CROSS,(1,1))
cv2.erode(b,element)

edges = cv2.Canny(b,10,100,apertureSize = 3)

lines = cv2.HoughLinesP(edges,1,np.pi/2,275, minLineLength = 100, maxLineGap = 200)[0].tolist()

for x1,y1,x2,y2 in lines:
   for index, (x3,y3,x4,y4) in enumerate(lines):

    if y1==y2 and y3==y4: # Horizontal Lines
        diff = abs(y1-y3)
    elif x1==x2 and x3==x4: # Vertical Lines
        diff = abs(x1-x3)
    else:
        diff = 0

    if diff < 10 and diff is not 0:
        del lines[index]

    gridsize = (len(lines) - 2) / 2

   cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)
   cv2.imwrite('houghlines3.jpg',img)

Входное изображение: входное изображение

Выходное изображение: (см. Красную линию):

@ljetibo Попробуйте это с: c_6.jpg

1 ответ

Решение

Здесь все немного не так, поэтому я начну с самого начала.

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

В руководстве упоминается, что

cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst

специальное значение THRESH_OTSU может быть объединено с одним из вышеуказанных значений. В этом случае функция определяет оптимальное пороговое значение с помощью алгоритма Оцу и использует его вместо указанного порога.

Я знаю, что это немного сбивает с толку, потому что вы не объединяете THRESH_OTSU с любым другим методом (THRESH_BINARY и т. Д.), К сожалению, это руководство может быть таким. Что этот метод на самом деле делает, так это предполагает, что есть "передний план" и "фон", которые следуют бимодальной гистограмме, а затем применяют THRESH_BINARY, который я считаю.

Представьте, что вы делаете снимок собора или высокого здания в середине дня. В солнечный день небо будет очень ярким и синим, а собор / здание будет немного темнее. Это означает, что группа пикселей, принадлежащих небу, будет иметь высокие значения яркости, то есть будет справа от гистограммы, а пиксели, принадлежащие церкви, будут темнее, то есть к средней и левой стороне гистограмма.

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

Изображение после установки порога ОП

После этого нетрудно догадаться, что идет не так. Но давайте продолжим. Я полагаю, что вы пытаетесь достичь чего-то вроде этого:

flag,b = cv2.threshold(gray,160,255,cv2.THRESH_BINARY)

Изображение с заданным вручную порогом

Затем вы продолжаете и пытаетесь разрушить изображение. Я не уверен, почему вы делаете это, было ли ваше намерение "жирным шрифтом" линии или ваше намерение убрать шум. В любом случае вы никогда не назначали результат размывания чему-либо. Числовые массивы, которые представляют собой изображения, являются изменяемыми, но синтаксис работает не так:

cv2.erode(src, kernel, [optionalOptions] ) → dst

Итак, вы должны написать:

b = cv2.erode(b,element)

Хорошо, теперь для элемента и как работает эрозия. Эрозия затягивает ядро ​​поверх изображения. Ядро - это простая матрица с 1 и 0. Один из элементов этой матрицы, обычно центральный, называется якорем. Якорь - это элемент, который будет заменен в конце операции. Когда вы создали

cv2.getStructuringElement(cv2.MORPH_CROSS, (1, 1))

на самом деле вы создали матрицу 1x1 (1 столбец, 1 строка). Это делает эрозию совершенно бесполезной.

То, что делает эрозия, в первую очередь извлекает все значения яркости пикселей из исходного изображения, где элемент ядра, перекрывающий сегмент изображения, имеет "1". Затем он находит минимальное значение найденных пикселей и заменяет привязку этим значением.

В вашем случае это означает, что вы тащите [1] Матрица поверх изображения, сравните, если яркость пикселя исходного изображения больше, равна или меньше, чем она сама, а затем замените ее на себя.

Если вы намеревались убрать "шум", то, вероятно, лучше использовать прямоугольное ядро ​​над изображением. Думайте об этом так, "шум" - это то, что "не вписывается" в окружение. Так что, если вы сравните свой центральный пиксель с его окружением и обнаружите, что он не подходит, скорее всего, это шум.

Кроме того, я сказал, что он заменяет якорь минимальным значением, полученным ядром. Численно минимальное значение равно 0, что совпадает с тем, как черный цвет представлен на изображении. Это означает, что в вашем случае с преимущественно белым изображением эрозия "раздувает" черные пиксели. Эрозия заменит 255-значные белые пиксели на 0-значные черные пиксели, если они находятся в пределах досягаемости ядра. В любом случае он никогда не должен иметь форму (1,1).

>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
array([[0, 1, 0],
       [1, 1, 1],
       [0, 1, 0]], dtype=uint8)

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

Выветренное изображение

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

Хитрые края

Хорошо, теперь мы ищем ТОЛЬКО вертикальные и ТОЛЬКО горизонтальные линии. Конечно, нет таких линий, кроме меридиана в левой части изображения (это то, что он называется?), И конечное изображение, которое вы получите после того, как вы все сделали правильно, будет следующим:

Теперь, поскольку вы никогда не описывали свою точную идею, и я думаю, что вам нужны параллели и меридианы, вам больше повезет на картах меньшего масштаба, потому что это не линии, а кривые. Кроме того, есть ли конкретная причина, чтобы сделать вероятностный тест? "Обычного" Хаф не хватает?

Извините за слишком длинный пост, надеюсь, это немного поможет.


Текст здесь был добавлен как просьба о разъяснении от ФП 24 ноября. потому что нет никакого способа вписать ответ в комментарий, ограниченный символом.

Я бы посоветовал OP задать новый вопрос, более конкретный для определения кривых, потому что вы имеете дело с кривыми op, а не с горизонтальными и вертикальными линиями.

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

  1. Используйте алгоритм RANSAC. Разработайте формулу, описывающую природу длинного. и лат. линии в зависимости от карты. Т.е. кривые широты будут почти идеальными прямыми линиями на карте, когда вы находитесь рядом с экватором, при этом экватор является идеально прямой линией, но будет очень изогнутым, напоминая отрезки круга, когда вы находитесь в высоких широтах (около полюса). В SciPy RANSAC уже реализован как класс, и все, что вам нужно сделать, это найти и программно определить модель, которую вы хотите попытаться подогнать под кривые. Конечно, здесь всегда есть полезный текст 4-х манекенов. Это самое простое, потому что все, что вам нужно сделать, это математика.
  2. Немного сложнее было бы создать прямоугольную сетку, а затем попытаться использовать cv findHomography для деформации сетки на место на изображении. Для различных геометрических преобразований, которые вы можете сделать с сеткой, вы можете обратиться к руководству по OpenCv. Это своего рода хакерский подход, который может работать хуже, чем 1., потому что это зависит от того, что вы можете воссоздать сетку с достаточным количеством деталей и объектов на ней, чтобы cv мог идентифицировать структуры на изображении, которое вы пытаетесь чтобы исказить это. Для этого требуется, чтобы вы делали математику, аналогичную 1., и просто немного кода, чтобы составить конечное решение из нескольких различных функций.
  3. На самом деле сделать это. Есть математически аккуратные способы описания кривых как списка касательных линий на кривой. Вы можете попытаться подогнать несколько более коротких линий HoughLines к своему изображению или сегменту изображения, а затем попытаться сгруппировать все найденные линии и определить, предполагая, что они касаются кривой, действительно ли они следуют кривой желаемой формы или являются они случайные. Смотрите эту статью по этому вопросу. Из всех подходов этот является самым сложным, потому что требует немало самостоятельного кодирования и некоторой математики в отношении метода.

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

Чтобы показать вам, что вы можете сделать с помощью преобразования Hough, посмотрите ниже:

import cv2
import numpy as np

def draw_lines(hough, image, nlines):
   n_x, n_y=image.shape
   #convert to color image so that you can see the lines
   draw_im = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)

   for (rho, theta) in hough[0][:nlines]:
      try:
         x0 = np.cos(theta)*rho
         y0 = np.sin(theta)*rho
         pt1 = ( int(x0 + (n_x+n_y)*(-np.sin(theta))),
                 int(y0 + (n_x+n_y)*np.cos(theta)) )
         pt2 = ( int(x0 - (n_x+n_y)*(-np.sin(theta))),
                 int(y0 - (n_x+n_y)*np.cos(theta)) )
         alph = np.arctan( (pt2[1]-pt1[1])/( pt2[0]-pt1[0]) )
         alphdeg = alph*180/np.pi
         #OpenCv uses weird angle system, see: http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_houghlines/py_houghlines.html
         if abs( np.cos( alph - 180 )) > 0.8: #0.995:
            cv2.line(draw_im, pt1, pt2, (255,0,0), 2)
         if rho>0 and abs( np.cos( alphdeg - 90)) > 0.7:
            cv2.line(draw_im, pt1, pt2, (0,0,255), 2)    
      except:
         pass
   cv2.imwrite("/home/dino/Desktop/3HoughLines.png", draw_im,
             [cv2.IMWRITE_PNG_COMPRESSION, 12])   

img = cv2.imread('a.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

flag,b = cv2.threshold(gray,160,255,cv2.THRESH_BINARY)
cv2.imwrite("1tresh.jpg", b)

element = np.ones((3,3))
b = cv2.erode(b,element)
cv2.imwrite("2erodedtresh.jpg", b)

edges = cv2.Canny(b,10,100,apertureSize = 3)
cv2.imwrite("3Canny.jpg", edges)

hough = cv2.HoughLines(edges, 1, np.pi/180, 200)   
draw_lines(hough, b, 100)

Как видно из изображения ниже, прямые линии - это только долготы. Широты не такие прямые, поэтому для каждой широты у вас есть несколько обнаруженных линий, которые ведут себя как касательные на линии. Синие нарисованные линии нарисованы if abs( np.cos( alph - 180 )) > 0.8: в то время как красные линии нарисованы rho>0 and abs( np.cos( alphdeg - 90)) > 0.7 состояние. Обратите особое внимание при сравнении исходного изображения с изображением с нарисованными на нем линиями. Сходство странное (хех, понимаешь?), Но поскольку они не строчки, большая часть их выглядит как мусор. (особенно эта линия с наибольшей обнаруженной широтой, которая кажется слишком "наклонной", но в действительности эти линии образуют точную касательную к линии широты в самой толстой точке, как этого требует алгоритм hough). Признать, что существуют ограничения на обнаружение кривых с помощью алгоритма обнаружения линий

Наилучшие возможные обнаруженные линии

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