У меня есть это изображение статуи.

enter image description here

Я пытаюсь найти верхнюю, нижнюю, левую и правую большинство точек на статуе. Есть ли способ измерить край каждой стороны, чтобы определить самую внешнюю точку на статуе? Я хочу получить координату (x,y) каждой стороны. Я пытался использовать cv2.findContours() и cv2.drawContours(), чтобы получить контур статуи.

import cv2

img = cv2.imread('statue.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

contours = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(img, contours, -1, (0, 200, 0), 3)

cv2.imshow('img', img)
cv2.waitKey()
13
coffeewin 27 Июн 2019 в 01:20

4 ответа

Лучший ответ

Вот потенциальный подход:

  • Преобразование изображения в оттенки серого и размытие изображения
  • Порог для получения бинарного изображения
  • Найти контуры
  • Получить внешние координаты

После преобразования в градацию серого и размытое изображение, мы порог, чтобы получить двоичное изображение

Теперь мы находим контуры, используя cv2.findContours(). Поскольку OpenCV использует массивы Numpy для кодирования изображений, контур - это просто массив Numpy с (x,y) координатами. Мы можем нарезать массив Numpy и использовать argmin() или argmax() для определения внешних левой, правой, верхней и нижней координат, как это

left = tuple(c[c[:, :, 0].argmin()][0])
right = tuple(c[c[:, :, 0].argmax()][0])
top = tuple(c[c[:, :, 1].argmin()][0])
bottom = tuple(c[c[:, :, 1].argmax()][0])

Вот результат

слева: (162, 527)

справа: (463, 467)

верх: (250, 8)

внизу: (381, 580)

import cv2
import numpy as np

image = cv2.imread('2.png')

blur = cv2.GaussianBlur(image, (3,3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)

thresh = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV)[1]

cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
c = max(cnts, key=cv2.contourArea)

left = tuple(c[c[:, :, 0].argmin()][0])
right = tuple(c[c[:, :, 0].argmax()][0])
top = tuple(c[c[:, :, 1].argmin()][0])
bottom = tuple(c[c[:, :, 1].argmax()][0])

cv2.drawContours(image, [c], -1, (36, 255, 12), 2)
cv2.circle(image, left, 8, (0, 50, 255), -1)
cv2.circle(image, right, 8, (0, 255, 255), -1)
cv2.circle(image, top, 8, (255, 50, 0), -1)
cv2.circle(image, bottom, 8, (255, 255, 0), -1)

print('left: {}'.format(left))
print('right: {}'.format(right))
print('top: {}'.format(top))
print('bottom: {}'.format(bottom))
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()
10
nathancy 26 Июн 2019 в 22:36

Вам не нужен дорогой код, такой как findContours. Вам просто нужно сканировать изображение построчно с 4-х сторон снаружи, пока не найдете первый не белый пиксель.

Слева, начните сканирование слева направо. Если белый пиксель не найден, переместите 1 пиксель вправо и снова идите сверху вниз. Как только вы найдете не белый пиксель, это ваш left.

Сделайте то же самое для всех сторон.

0
RobAu 28 Июн 2019 в 09:06

Вместо того, чтобы проверять каждый отдельный элемент (и останавливать процессор с помощью оператора if для каждого пикселя), вероятно, быстрее будет суммировать все элементы в каждом столбце. Они должны прийти к 600 * 255 или 153 000, если они все белые. Итак, найдите, где 153 000 минус общее количество столбцов не равно нулю. Первым и последним будет верх и низ статуи.

Затем повторите через ряды, чтобы найти левую и правую экстремумы.

Итак, начиная с изображения в градациях серого, бегите по каждой строке, суммируя пиксели:

import numpy as np

# Total up all the elements in each column
colsums = np.sum(gray, axis=0)

Суммы каждого столбца теперь выглядят так:

array([153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 152991, 153000, 152976, 152920,
       152931, 152885, 151600, 148818, 147448, 146802, 146568, 146367,
       146179, 145888, 145685, 145366, 145224, 145066, 144745, 144627,
       144511, 144698, 144410, 144329, 144162, 143970, 143742, 143381,
       141860, 139357, 135358, 133171, 131138, 129246, 128410, 127866,
       127563, 127223, 126475, 125614, 125137, 124848, 122906, 121653,
       119278, 115548, 114473, 113800, 113486, 112655, 112505, 112670,
       111845, 111124, 110378, 110315, 109996, 109693, 109649, 109411,
       110626, 110628, 112247, 112348, 111865, 111571, 110601, 108308,
       107213, 106768, 105546, 103971, 103209, 101866, 100215,  98964,
        98559,  97008,  94981,  94513,  92490,  91555,  91491,  90072,
        88642,  87210,  86960,  86834,  85759,  84496,  83237,  81911,
        80249,  78942,  77715,  76918,  75746,  75826,  75443,  75087,
        75156,  75432,  75730,  75699,  77028,  77825,  76813,  76718,
        75958,  75207,  74216,  73042,  72527,  72043,  71819,  71384,
        70693,  69922,  69537,  69685,  69688,  69876,  69552,  68937,
        68496,  67942,  67820,  67626,  67627,  68113,  68426,  67894,
        67868,  67365,  66191,  65334,  65752,  66438,  66285,  66565,
        67616,  69090,  69386,  69928,  70470,  70318,  70228,  71028,
        71197,  71827,  71712,  71312,  72013,  72878,  73398,  74038,
        75017,  76270,  76087,  75317,  75210,  75497,  75099,  75620,
        75059,  75008,  74146,  73531,  73556,  73927,  75395,  77235,
        77094,  77229,  77463,  77808,  77538,  77104,  76816,  76500,
        76310,  76331,  76889,  76293,  75626,  74966,  74871,  74950,
        74931,  74852,  74885,  75077,  75576,  76104,  76208,  75387,
        74971,  75878,  76311,  76566,  77014,  77205,  77231,  77456,
        77983,  78379,  78793,  78963,  79154,  79710,  80777,  82547,
        85164,  88944,  91269,  92438,  93646,  94836,  96071,  97918,
       100244, 102011, 103553, 104624, 104961, 105354, 105646, 105866,
       106367, 106361, 106461, 106659, 106933, 107055, 106903, 107028,
       107080, 107404, 107631, 108022, 108194, 108261, 108519, 109023,
       109349, 109873, 110373, 110919, 111796, 112587, 113219, 114143,
       115161, 115733, 116531, 117615, 118338, 119414, 120492, 121332,
       122387, 123824, 124938, 126113, 127465, 128857, 130411, 131869,
       133016, 133585, 134442, 135772, 136440, 136828, 137200, 137418,
       137705, 137976, 138167, 138481, 138788, 138937, 139194, 139357,
       139375, 139583, 139924, 140201, 140716, 140971, 141285, 141680,
       141837, 141975, 142260, 142567, 142774, 143154, 143533, 143853,
       144521, 145182, 145832, 147978, 149006, 150026, 151535, 152753,
       152922, 152960, 152990, 152991, 153000, 152995, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000,
       153000, 153000, 153000, 153000, 153000, 153000, 153000, 153000],
      dtype=uint64)

Теперь найдите, где эти столбцы не суммируют до 153 000:

np.nonzero(153000-colsums)                                                                 

Это выглядит так:

(array([156, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
        170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182,
        183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195,
        196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208,
        209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221,
        222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234,
        235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247,
        248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260,
        261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273,
        274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286,
        287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299,
        300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312,
        313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325,
        326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338,
        339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351,
        352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364,
        365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
        378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390,
        391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403,
        404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416,
        417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429,
        430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442,
        443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455,
        456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 469]),)

Таким образом, верхняя строка, которая не состоит полностью из белых пикселей, - это строка 156 (первая запись), а нижняя строка, которая не полностью состоит из белых пикселей, - это строка 469 (последняя запись).

Теперь суммируйте по другой оси (ось = 1) и сделайте то же самое снова, чтобы получить левую и правую экстремумы.

4
Mark Setchell 28 Июн 2019 в 14:15

Вот возможное улучшение в ответе Натани, откуда берется большая часть кода, а также основная идея использования np.argmax. Пожалуйста, посмотрите на этот ответ раньше!


Поскольку у нас уже есть бинаризованное изображение из / a>, так что (белый) фон входного изображения установлен на ноль, мы можем использовать возможность cv2.boundingRect to " вычислить ограничивающий прямоугольник вверх для набора точек или ненулевых пикселей изображения в оттенках серого " . Метод возвращает кортеж (x, y, w, h) с (x, y) верхней левой точкой, а также шириной w и высотой h ограничительного прямоугольника. Оттуда упомянутые точки left, right и т. Д. Можно легко получить с помощью np.argmax на соответствующем срезе изображения thresh.

Вот полный код:

import cv2
import numpy as np

image = cv2.imread('images/dMXjY.png')

blur = cv2.GaussianBlur(image, (3,3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)

thresh = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV)[1]

x, y, w, h = cv2.boundingRect(thresh)           #  Replaced code
                                                # 
left = (x, np.argmax(thresh[:, x]))             # 
right = (x+w-1, np.argmax(thresh[:, x+w-1]))    # 
top = (np.argmax(thresh[y, :]), y)              # 
bottom = (np.argmax(thresh[y+h-1, :]), y+h-1)   # 

cv2.circle(image, left, 8, (0, 50, 255), -1)
cv2.circle(image, right, 8, (0, 255, 255), -1)
cv2.circle(image, top, 8, (255, 50, 0), -1)
cv2.circle(image, bottom, 8, (255, 255, 0), -1)

print('left: {}'.format(left))
print('right: {}'.format(right))
print('top: {}'.format(top))
print('bottom: {}'.format(bottom))
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()

Выводы изображения выглядят так, как в ответе Натанси.

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

слева: (162, 527)

справа: (463, 461) (вместо (463, 467))

верх: (250, 8)

внизу: (381, 580)

Если мы поближе рассмотрим изображение thresh, то увидим, что для 463 - го столбца все пиксели в диапазоне 461 ... 467 имеют значение {{X3} } . Таким образом, для правого края не существует уникальной экстремальной ценности.

Контур c, найденный в подходе Натанси, содержит две точки (463, 467) и (463, 461) в этом порядке, так что np.argmax сначала найдет (463, 467). В моем подходе 463 -й столбец проверяется от 0 до (height of image), так что np.argmax сначала найдет (463, 461).

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

Использование cv2.boundingRect сохраняет две строки кода, а также работает быстрее, по крайней мере, в соответствии с некоторыми короткими тестами с использованием timeit.


Раскрытие. Опять же, большая часть кода и основная идея взята из ответа Натанси.

4
HansHirse 28 Июн 2019 в 05:19