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

#python #numpy #opencv #roi

#python #изображение #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 , чтобы вы могли указать 2D-массив точек и определить маску, которая заполняет форму, определенную этими точками, белым цветом в маске. Следует сделать справедливое предупреждение, если точки, определенные в вашем полигоне, являются выпуклыми (отсюда и название 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)
 

Комментарии:

1. Отличный ответ! Спасибо.

2. Никаких проблем вообще! Удачи!

3. Ах да, это не должно быть проблемой. Я отвечу через некоторое время. В настоящее время я возвращаюсь домой с работы.

4. Извините за это. Я изменю это через пару часов. Вчера вечером был занят.

5. Идеально! Этот ответ похож на краткое руководство. Большое вам спасибо, Рэй.