Найдите линии и раскрасьте их одним щелчком мыши

@ALL Это редактирование исходного вопроса, чтобы пролить немного света на эту тему.

Постановка задачи

  • Предположим, что есть промышленный участок P&ID.
  • Стремление раскрасить только некоторые линии, важные для процесса.
  • Пользователь должен только щелкнуть (щелчок левой кнопкой мыши) на отрезке линии, чтобы получить его цветной.

Проблемный подход

Я новичок в программировании -> с помощью Python (3.5), чтобы попробовать это. Как я понимаю, алгоритм выглядит так:

  • Сюжет будет в формате.pdf. Поэтому я мог бы использовать PIL ImageGrab или конвертировать.pdf в.png, как показано в этом примере.
  • Алгоритм будет искать пиксели вокруг щелчка мыши, затем сравнивать их с другой частью идентичного размера (скажем, полоса размером 6x3 пикселя), но на один шаг влево / вправо (будь то 1-5 пикселей)
  • Проверка среднего значения их различий покажет нам, идентичны ли эти две полосы
  • Таким образом, алгоритм должен найти оба конца строки, стрелки, углы или другие элементы
  • Как только это будет найдено, записаны позиции и нарисована линия разметки, пользователь должен выбрать другую линию

Подвела

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

Другие мысли

  • В приложении вы можете найти две фотографии образца изображения и то, что я пытаюсь достичь.
  • Пытался найти "дыры" в срезах, используя подход, найденный здесь: OpenCV, чтобы найти окончания строк
  • Не существует строгого правила придерживаться рутины ImageGrab или чего-либо подобного
  • Если вы знаете другую тактику, которую я мог бы использовать, пожалуйста, не стесняйтесь комментировать
  • Любой совет приветствуется и искренне ценится

Образец изображения:

Образец изображения

Желаемый результат (изменено в Paint):

Желаемый результат (изменено в Paint

Добавление обновления к посту с работой, которую я опробовал до сих пор

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

import win32gui as w
from PIL import ImageStat, ImageChops, Image, ImageDraw
import win32api as wa

img=Image.open("Trials.jpg")
img_width=img.size[0]
img_height=img.size[1]
#Using 1920 x 1080 resolution
#Hide the taskbar to center the Photo Viewer
#Defining a way to make sure the mouse click is inside the image
#Substract the width from total and divide by 2 to get base point of the crop
width_lim = (1920 - img_width)/2
height_lim = (1080 - img_height)/2-7
#After several tests, the math in calculating the height is off by 7 pixels, hence the correction
#Use these values when doing the crop

#Check if left mouse button was pressed and record its position
left_p = wa.GetKeyState(0x01)
#print(left_p)
while True :
    a=wa.GetKeyState(0x01)
    if a != left_p:
        left_p = a
        if a<0 :
            pos = w.GetCursorPos()
            pos_x=pos[0]-width_lim
            pos_y=pos[1]-height_lim
#            print(pos_x,pos_y)
        else:
            break


#img.show()
#print(img.size)

#Define the crop height; size is doubled
height_size = 10
#Define max length limit
#Getting a horizontal strip
im_hor = img.crop(box=[0, pos_y-height_size, img_width, pos_y+height_size])
#im_hor.show()



#failed in trying crop a small square of 3x3 size using the pos_x
#sq_size = 3
#st_sq = im_hor.crop(box=[pos_x,0,pos_x+sq_size,height_size*2])
#st_sq.show()

#going back to the code it works
#crop a standard strip and compare with a new test one
#if the mean of difference is zero, the strips are identical
#still looking for a way to find the position of the central pixel (that would be the one with maximum value - black)
strip_len = 3
step = 3
i = pos_x
st_sq = im_hor.crop(box=[i,0,i+strip_len,height_size*2])
test_sq = im_hor.crop(box=[i+step,0,i+strip_len+step,height_size*2])
diff = ImageChops.difference(st_sq,test_sq)
stat=ImageStat.Stat(diff)
mean = stat.mean
mean1 = stat.mean
#print(mean)

#iterate to the right until finding a different strip, record position
while mean==[0,0,0]:
    i = i+1
    st_sq = im_hor.crop(box=[i,0,i+strip_len,height_size*2])
    #st_sq.show()
    test_sq = im_hor.crop(box=[i+step,0,i+strip_len+step,height_size*2])
    #test_sq.show()
    diff = ImageChops.difference(st_sq,test_sq)
    #diff.show()
    stat=ImageStat.Stat(diff)
    mean = stat.mean
#    print(mean)
print(i-1)

r = i-1
#print("STOP")
#print(r)
#record the right end as r = i-1

#iterate to the left until finding a different strip. record the position
while mean1==[0,0,0]:
    i = i-1
    st_sq = im_hor.crop(box=[i,0,i+strip_len,height_size*2])
    #st_sq.show()
    test_sq = im_hor.crop(box=[i+step,0,i+strip_len+step,height_size*2])
    #test_sq.show()
    diff = ImageChops.difference(st_sq,test_sq)
    #diff.show()
    stat=ImageStat.Stat(diff)
    mean1 = stat.mean
#    print(mean)
#print("STOP")
print(i+1)

l = i+1
#record the left end as l=i+1
test_draw = ImageDraw.Draw(img)
test_draw.line([l,pos_y,r,pos_y], fill=128)
img.show()

#find another approach or die trying!!! 

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

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

1 ответ

Таким образом, это решение не является полным решением вашей конкретной проблемы, но я думаю, что это может быть хорошим подходом, который поможет вам хотя бы частично пройти путь. Проблемы, с которыми я сталкиваюсь в целом при подходах к обнаружению линий, заключается в том, что они обычно сильно зависят от нескольких гиперпараметров. Более досадно, что они медленные, так как они ищут широкий спектр углов; Ваши линии строго горизонтальны или вертикальны. Поэтому я бы рекомендовал использовать морфологию. Вы можете найти общий обзор морфологии на сайте OpenCV и увидеть, как он применяется для удаления музыкальных полос в партитуре в этом руководстве на сайте OpenCV.

Основная идея, которую я думал, была:

  1. Обнаружение горизонтальных и вертикальных линий
  2. Бежать connectedComponents() по обнаруженным линиям идентифицировать каждую строку отдельно
  3. Получить пользовательскую позицию мыши и определить окно вокруг нее
  4. Если метка из подключенных компонентов находится в этом окне, то захватите этот компонент
  5. Нарисуйте этот компонент на изображении

Это очень базовая идея, которая игнорирует некоторые связанные с этим проблемы. Тем не менее, то, что это будет делать наверняка, так это то, что если вы щелкнете где-нибудь, и в окне этого клика есть линия на вашем изображении, вы получите его. Здесь нет пропущенных строк. Еще одна хорошая новость: он не игнорирует более толстые границы и такие на изображении, где вы, естественно, хотели бы, чтобы это прекратилось (обратите внимание, что эта проблема существует для схем обнаружения линий). Это будет обнаруживать только линии, которые имеют определенную ширину, и если линия становится толще (превращается в стрелку или попадает на линию, идущую в другом направлении), она обрезает ее. Плохая новость заключается в том, что для ваших линий используется заранее заданная ширина. Вы можете немного обойти это, используя преобразование типа "попал или не попал", но учтите, что в настоящее время реализация не работает для версий OpenCV старше 3.3-rc; смотрите здесь больше (вы можете легко обойти сломанную реализацию). В любом случае, преобразование типа "попал или не попал" позволяет вам сказать: "Я хочу горизонтальную линию, но она может быть шириной в несколько пикселей или шириной всего в один пиксель". Конечно, чем шире вы делаете это, тем больше вещей, которые не являются линиями, могут превратиться в одну. Вы можете отфильтровать их позже, хотя на основе размера (бросить все линии, которые меньше, чем некоторый размер с эрозией или расширением).


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

Сначала мы создадим изображение и нарисуем несколько линий:

import cv2
import numpy as np 

img = 255*np.ones((500, 500), dtype=np.uint8)
cv2.line(img, (10, 350), (200, 350), color=0, thickness=1)
cv2.line(img, (100, 150), (400, 150), color=0, thickness=1)
cv2.line(img, (300, 250), (300, 500), color=0, thickness=1)
cv2.line(img, (100, 50), (100, 350), color=0, thickness=1)
bin_img = cv2.bitwise_not(img)

Простой пример строки

И обратите внимание, я также создал противоположное изображение, потому что я предпочитаю оставить то, что я пытаюсь обнаружить, белым, а черный - фоном.

Теперь мы возьмем эти горизонтальные и вертикальные линии с морфологией (в данном случае эрозия):

h_kernel = np.array([[0, 0, 0],
                     [1, 1, 1],
                     [0, 0, 0]], dtype=np.uint8)
v_kernel = np.array([[0, 1, 0],
                     [0, 1, 0],
                     [0, 1, 0]], dtype=np.uint8)

h_lines = cv2.morphologyEx(bin_img, cv2.MORPH_ERODE, h_kernel)
v_lines = cv2.morphologyEx(bin_img, cv2.MORPH_ERODE, v_kernel)

Горизонтальные линии

Вертикальные линии

А теперь мы будем помечать каждую строку:

h_n, h_labels = cv2.connectedComponents(h_lines)
v_n, v_labels = cv2.connectedComponents(v_lines)

Эти изображения h_labels а также v_labels будет идентичным h_lines а также v_lines но вместо того, чтобы цвет / значение было белым в каждом пикселе, значение вместо этого является целым числом для каждого отдельного компонента в изображении. Таким образом, фоновые пиксели будут иметь значение 0, одна строка будет помечена 1 с, а другая - 2 с. И так далее для изображений с большим количеством строк.

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

mouse_click = [101, 148]  # x, y
click_radius = 3  # pixel width around mouse click
window = [[mouse_click[0] - i, mouse_click[1] - j]
          for i in range(-click_radius, click_radius+1)
          for j in range(-click_radius, click_radius+1)]

Последнее, что нужно сделать, это перебрать все места внутри window и проверьте, является ли ярлык положительным (т.е. это не фон). Если это так, то мы попали в линию. Итак, теперь мы можем просто посмотреть на все пиксели, которые имеют эту метку, и это будет полная строка. Тогда мы можем использовать любое количество методов, чтобы нарисовать линию на оригинале img,

label = 0
for pixel in window:
    if h_labels[pixel[1], pixel[0]] > 0:
        label = h_labels[pixel[1], pixel[0]]
        bin_labeled = 255*(h_labels == label).astype(np.uint8)
    elif v_labels[pixel[1], pixel[0]] > 0:
        label = v_labels[pixel[1], pixel[0]]
        bin_labeled = 255*(v_labels == label).astype(np.uint8)
    if label > 0:
        rgb_labeled = cv2.merge([img, img+bin_labeled, img])
        break

Помеченная линия

IMO, этот код, приведенный выше, действительно неаккуратный, есть лучшие способы нарисовать это, но я не хотел тратить время на что-то не очень важное для вопроса.


Одним из простых способов улучшить это было бы соединение вблизи линий - вы могли бы сделать это еще с морфологией, прежде чем найдете компоненты. Возможно, лучшим способом рисования было бы просто найти минимальное / максимальное положения этой метки внутри изображения и использовать их в качестве координат конечной точки для OpenCV. line() функция рисования, которая позволит вам легко выбирать цвета и толщину линий. Единственное, что я бы порекомендовал сделать, если это возможно, это показать эти линии при наведении мыши до того, как пользователь щелкнет (чтобы они знали, что щелкают в правой области). Таким образом, если пользователь находится рядом с двумя строками, он знает, какую из них он выбирает.

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