【问题标题】:How to find extreme outer points in an image with Python OpenCV使用 Python OpenCV 查找图像中的极端外部点
【发布时间】:2019-06-26 22:20:37
【问题描述】:

我有这个雕像的图像。

我正在尝试找到雕像的顶部、底部、左侧和最右侧的点。有没有办法测量每边的边缘以确定雕像上的最外点?我想得到每一边的(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()

【问题讨论】:

    标签: python image opencv image-processing computer-vision


    【解决方案1】:

    这是一种可能的方法:


    转换为灰度和模糊图像后,我们阈值得到二值图像

    现在我们使用cv2.findContours() 找到轮廓。由于 OpenCV 使用 Numpy 数组对图像进行编码,因此轮廓只是 (x,y) 坐标的 Numpy 数组。我们可以对 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
    
    # Load image, grayscale, Gaussian blur, threshold
    image = cv2.imread('1.png')
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (3,3), 0)
    thresh = cv2.threshold(blur, 220, 255, cv2.THRESH_BINARY_INV)[1]
    
    # Find contours
    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)
    
    # Obtain outer coordinates
    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])
    
    # Draw dots onto image
    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()
    

    【讨论】:

    • 由于boundingRect 也适用于灰度图像,并且您已经有一个二值化(阈值)图像,您也可以使用x, y, w, h = cv2.boundingRect(thresh),并计算leftright 等用你的方法使用np.argmax。我相应地修改了您的代码并得到了相同的结果。您节省了两个 LOC,我可以想象,这将比计算整个轮廓更快(实际上还没有计时)。无论如何,干得好,有我的赞成票。 :-)
    • @HansHirse,不错的优化!随意编辑该解决方案:)
    • @nathancy 毕竟,我写了一个单独的答案,因为我观察到我的结果有一点不同,我想正确解释一下。并且将所有这些作为编辑在您的答案中不知何故感觉不合适。当然,我提到了你的回答,并透露了代码的来源。
    【解决方案2】:

    这是对nathancy's answer 的可能改进,大部分代码都来自这里,也是使用np.argmax 的主要思想。所以,请先看看那个答案!


    由于我们已经有来自cv2.threshold 的二值化图像,因此输入图像的(白色)背景设置为零,我们可以使用cv2.boundingRect 的能力来“计算右上角点集的边界矩形或灰度图像的非零像素"。该方法返回一个元组(x, y, w, h),其中(x, y)左上角以及边界矩形的宽度w和高度h。从那里,可以在thresh 图像的相应切片上使用np.argmax 轻松获得提到的点leftright 等。

    完整代码如下:

    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()
    

    图像输出看起来像 nathancy 的答案。

    尽管如此,其中一个结果有点不同:

    左:(162, 527)

    右:(463, 461)(而不是 (463, 467))

    顶部:(250, 8)

    底部:(381, 580)

    如果我们仔细查看thresh 图像,我们会发现463-th 列中461 ... 467 范围内的所有像素的值都是255。因此,对于右边缘,没有唯一的极值。

    在 nathancy 的方法中找到的轮廓 c 按此顺序包含两个点 (463, 467)(463, 461),这样 np.argmax 将首先找到 (463, 467)。在我的方法中,463-th 列从0(height of image) 进行检查,这样np.argmax 将首先找到(463, 461)

    在我看来,两者(甚至介于两者之间的所有其他点)都是合适的结果,因为对处理多个极值点没有额外的限制。

    使用cv2.boundingRect 节省了两行代码,并且执行速度也更快,至少根据使用timeit 的一些简短测试。


    披露:同样,大部分代码和主要思想来自nathancy's answer

    【讨论】:

      【解决方案3】:

      与其检查每个元素(并使用if 语句为每个像素停止CPU),不如将所有元素汇总到每列中可能更快。它们应该达到 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 行(最后一个条目)。

      现在在另一个轴 (axis=1) 上求和,然后再次执行相同的操作以获得左右极值。

      【讨论】:

        【解决方案4】:

        您不需要像findContours 这样的昂贵代码。您只需要从外到内的 4 个面逐行扫描图像,直到找到第一个非白色像素。

        从左上角开始扫描到左下角。如果没有找到白色像素,则向右移动 1 个像素,然后再次从上到下。一旦你找到一个非白色像素,这就是你的left

        对所有面都做同样的事情。

        【讨论】:

          【解决方案5】:
          import cv2
          import numpy as np
          img = cv2.imread('statue.png')
          gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
          thresh = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV)[1]
          sz=thresh.shape
          top=divmod(np.flatnonzero(thresh)[0], sz[0])[::-1]
          botton=divmod(np.flatnonzero(thresh)[-1], sz[0])[::-1]
          thresh=thresh.T
          left=divmod(np.flatnonzero(thresh)[0], sz[1])
          right=divmod(np.flatnonzero(thresh)[-1], sz[1])
          print(left, right, top, botton, sep='\n')
          

          【讨论】:

            【解决方案6】:

            倾斜 x、y、z 点的轮廓。假设使用梯形规则计算左角。它适用于分布的顶部、底部和右尾。如果 z 的值是高斯的。我们把 y 作为 langrange 分布形式。插值 x,y,z 以获得 x,y 点。 当我们说插值时,我们的意思是从 y 倾斜 N-1 个点的矩阵。 这样可以更清楚地看到雕像。

            【讨论】:

              猜你喜欢
              • 2019-01-21
              • 2016-08-04
              • 2020-02-12
              • 2013-06-08
              • 2017-06-02
              • 1970-01-01
              • 2015-07-31
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多