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

У меня есть набор точек, таких как следующие:

     <data:polygon>
                            <data:point x="542" y="107"/>
                            <data:point x="562" y="102"/>
                            <data:point x="582" y="110"/>
                            <data:point x="598" y="142"/>
                            <data:point x="600" y="192"/>
                            <data:point x="601" y="225"/>
                            <data:point x="592" y="261"/>
                            <data:point x="572" y="263"/>
                            <data:point x="551" y="245"/>
                            <data:point x="526" y="220"/>
                            <data:point x="520" y="188"/>
                            <data:point x="518" y="152"/>
                            <data:point x="525" y="127"/>
                            <data:point x="542" y="107"/
 </data:polygon>

Я хочу нарисовать многоугольник, определенный этими точками на изображении, а затем извлечь его. Как я могу сделать это, используя OpenCV с Python?

1 ответ

Решение

Использование cv2.fillConvexPoly так что вы можете указать двумерный массив точек и определить маску, которая заполняет форму, которая определяется этими точками, чтобы быть белой в маске. Нужно сделать справедливое предупреждение, если точки, которые определены в вашем многоугольнике, являются выпуклыми (отсюда и название fillConvexPoly).

Затем мы можем преобразовать это в логическую маску и использовать для индексации вашего изображения, чтобы извлечь нужные пиксели. Код ниже производит массив с именем mask и он будет содержать логическую маску пикселей, которые вы хотите сохранить из изображения. Кроме того, массив out будет содержать желаемый извлеченный образ, который был определен многоугольником. Обратите внимание, что изображение инициализируется полностью темным и что единственные пиксели, которые должны быть скопированы, - это пиксели, определенные многоугольником.

Предполагая, что фактическое изображение называется img и предполагая, что ваш x а также y Точки обозначают горизонтальные и вертикальные координаты на изображении, вы можете сделать что-то вроде этого:

import numpy as np
import cv2

pts = np.array([[542, 107], [562, 102], [582, 110], [598, 142], [600, 192], [601, 225], [592, 261], [572, 263], [551, 245], [526, 220], [520, 188], [518, 152], [525, 127], [524, 107]], dtype=np.int32)

mask = np.zeros((img.shape[0], img.shape[1]))

cv2.fillConvexPoly(mask, pts, 1)
mask = mask.astype(np.bool)

out = np.zeros_like(img)
out[mask] = img[mask]

out все должны быть черными, за исключением региона, который должен быть скопирован. Если вы хотите отобразить это изображение, вы можете сделать что-то вроде:

cv2.imshow('Extracted Image', out)
cv2.waitKey(0)
cv2.destroyAllWindows()

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

Если вы хотите сохранить это изображение в файл, сделайте что-то вроде этого:

cv2.imwrite('output.png', out)

Это сохранит изображение в файл с именем output.png, Я указываю формат PNG, потому что он без потерь.


В качестве простого теста давайте определим белое изображение, которое 300 x 700, что намного превышает самые большие координаты в том, что вы определили. Давайте выделим область, определенную этим полигоном, и покажем, как выглядит результат.

img = 255*np.ones((300, 700, 3), dtype=np.uint8)

Используя тестовое изображение выше, мы получаем это изображение:

редактировать

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

Как cv2.remap Работает так, что для каждого пикселя в выходных данных вам нужно указать пространственную координату того, где вы хотите получить доступ к пикселю в исходном изображении. Поскольку вы в конечном итоге перемещаете вывод в центр изображения, вам необходимо добавить смещение для каждого x а также y местоположение в целевом изображении, чтобы получить исходный пиксель.

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

Используя переменные, которые мы определили выше, вы можете найти центроид:

(meanx, meany) = pts.mean(axis=0)

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

(cenx, ceny) = (img.shape[1]/2, img.shape[0]/2)

Также важно, чтобы вы преобразовали координаты в целое число, поскольку координаты пикселя таковы:

(meanx, meany, cenx, ceny) = np.floor([meanx, meany, cenx, ceny]).astype(np.int32)

Теперь, чтобы выяснить смещение, сделайте это так, как мы говорили ранее:

(offsetx, offsety) = (-meanx + cenx, -meany + ceny)

Теперь переведите свое изображение. Вам необходимо определить отображение для каждого пикселя в выходном изображении, где для каждой точки (x,y) в целевом изображении вы должны указать, где взять образец из источника. Смещение, которое мы рассчитали, переводит каждый исходный пиксель в место назначения. Поскольку мы делаем противоположное, где для каждого целевого пикселя мы находим исходный пиксель для выборки, мы должны вычесть смещение, а не добавить. Поэтому сначала определите сетку (x,y) указывает нормально, затем вычесть смещение. Как только вы закончите, переведите изображение:

(mx, my) = np.meshgrid(np.arange(img.shape[1]), np.arange(img.shape[0]))
ox = (mx - offsetx).astype(np.float32)
oy = (my - offsety).astype(np.float32)
out_translate = cv2.remap(out, ox, oy, cv2.INTER_LINEAR)

Если мы отобразили out_translate с приведенным выше примером, это то, что мы получаем:


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

topleft = pts.min(axis=0) + [offsetx, offsety]
bottomright = pts.max(axis=0) + [offsetx, offsety]
cv2.rectangle(out_translate, tuple(topleft), tuple(bottomright), color=(255,0,0))

Если мы покажем это изображение, мы получим:


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

# Import relevant modules
import numpy as np
import cv2

# Define points
pts = np.array([[542, 107], [562, 102], [582, 110], [598, 142], [600, 192], [601, 225], [592, 261], [572, 263], [551, 245], [526, 220], [520, 188], [518, 152], [525, 127], [524, 107]], dtype=np.int32)

### Define image here
img = 255*np.ones((300, 700, 3), dtype=np.uint8)

# Initialize mask
mask = np.zeros((img.shape[0], img.shape[1]))

# Create mask that defines the polygon of points
cv2.fillConvexPoly(mask, pts, 1)
mask = mask.astype(np.bool)

# Create output image (untranslated)
out = np.zeros_like(img)
out[mask] = img[mask]

# Find centroid of polygon
(meanx, meany) = pts.mean(axis=0)

# Find centre of image
(cenx, ceny) = (img.shape[1]/2, img.shape[0]/2)

# Make integer coordinates for each of the above
(meanx, meany, cenx, ceny) = np.floor([meanx, meany, cenx, ceny]).astype(np.int32)

# Calculate final offset to translate source pixels to centre of image
(offsetx, offsety) = (-meanx + cenx, -meany + ceny)

# Define remapping coordinates
(mx, my) = np.meshgrid(np.arange(img.shape[1]), np.arange(img.shape[0]))
ox = (mx - offsetx).astype(np.float32)
oy = (my - offsety).astype(np.float32)

# Translate the image to centre
out_translate = cv2.remap(out, ox, oy, cv2.INTER_LINEAR)

# Determine top left and bottom right of translated image
topleft = pts.min(axis=0) + [offsetx, offsety]
bottomright = pts.max(axis=0) + [offsetx, offsety]

# Draw rectangle
cv2.rectangle(out_translate, tuple(topleft), tuple(bottomright), color=(255,0,0))

# Show image, wait for user input, then save the image
cv2.imshow('Output Image', out_translate)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('output.png', out_translate)
Другие вопросы по тегам