【问题标题】:OpenCV: Lines passing through centroid of contour at given anglesOpenCV:以给定角度穿过轮廓质心的线
【发布时间】:2015-02-25 02:59:41
【问题描述】:

我有一个名为cnt 的轮廓是从下面的图片中获得的:

我能够找到这样的质心:

M = cv2.moments(cnt)
centroid_x = int(M['m10']/M['m00'])
centroid_y = int(M['m01']/M['m00'])

我现在想绘制 N 条线,每条线相隔 360/N 度,从质心开始,并在所有可能的交点处穿过轮廓。 cv2.line() 函数需要起点和终点,但我没有终点。

如果我画了一条穿过质心的线,斜率为 Tan(360/N),我会使用 bitwise_and 找到线与轮廓的交点,但我无法找到方法画出那条线。

任何有关如何绘制此类线条的帮助将不胜感激。

【问题讨论】:

    标签: python opencv image-processing line contour


    【解决方案1】:

    我得到了一些工作。这有点特别,但这基本上是我写的算法。我必须重建图像的轮廓,所以我所做的是手动读取图像,提取对象的最外层轮廓,然后从那里开始。 cv2.line 方法的好处在于,如果您绘制的线超出边界,则该线会被图像边界剪裁。这对我写的算法很有用。

    事不宜迟,以下是步骤:

    1. 读入图像,阈值然后反转图像,使黑色轮廓线变成白点。
    2. 检测最外面的轮廓。
    3. 创建原始输入图像的副本,以便我们绘制线条。打电话给out
    4. 创建一个“参考”图像,用于存储在第 2 步中找到的最外层轮廓。
    5. 检测轮廓点的质心。还可以访问图像的宽度和高度。如果您没有任何宽度或高度值,请选择一个非常大的值......也许像 1000 这样的值。您需要确保该值超出轮廓上任何点的最大值。另外,设置您想要的角度总数,N
    6. 对于我们拥有的每个角度,所以对于i = 0, 1N - 1

      一个。创建临时空白图片

      b.计算合适的角度:i*(360 / N)并转换为弧度

      c。在临时图像上,从轮廓的质心到图像外部的坐标画一条线,以确保我们沿着我们想要的角度向图像边界画一条线。这条线的水平分量是cos(360/N)(参数在这里是度数),而垂直分量是-sin(360/N)(参数也是度数)。负数是因为y 轴在我们的图像坐标空间中是向下的正数,所以负数是还原它,使正数相对于笛卡尔坐标向上。这样做的原因是,当我们计算每条线与中心所成的角度时,在正角度逆时针扫过时,角度将是正确的。从质心开始,我们将移动水平方向的图像宽度和垂直方向的图像高度,保持之前找到的水平和垂直分量。这会让我们画出边界线,但线会被图像边界剪掉。

      另一个复杂之处是在这个临时图像中画一条足够粗的线。如果我们画了一条只有 1 个像素粗的线,由于像素的采样和线的绘制方式,您可能会遇到线不与轮廓相交的情况。我在这里选择了 5 个像素的厚度来确定。

      d。使用此临时图像,查看哪些位置等于参考图像。对于任何相等的位置,我们已经找到了这条线与原始图像的轮廓相交的位置。因此,请选择任何相交的位置,因为粗线很可能会与最外层轮廓产生多个交点。

      e。使用步骤 (d),从out 的质心到我们在步骤 (d) 中找到的位置画一条线。

    7. 对所有角度重复第 6 步。 out 将包含我们在第 6 步完成后的结果。

    废话不多说,下面是我写的代码:

    # Step #1
    img = cv2.imread('contour.png', 0)
    img_bw = img <= 128
    img_bw = 255*img_bw.astype('uint8')
    
    # Step #2
    contours, _ = cv2.findContours(img_bw,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
    
    # Step #3
    out = img.copy()
    
    # Step #4
    ref = np.zeros_like(img_bw)
    cv2.drawContours(ref, contours, 0, 255, 1)
    
    # Step #5
    M = cv2.moments(contours[0])
    centroid_x = int(M['m10']/M['m00'])
    centroid_y = int(M['m01']/M['m00'])
    
    # Get dimensions of the image
    width = img.shape[1]
    height = img.shape[0]
    
    # Define total number of angles we want
    N = 20
    
    # Step #6
    for i in range(N):
      # Step #6a
      tmp = np.zeros_like(img_bw)
    
      # Step #6b
      theta = i*(360/N)
      theta *= np.pi/180.0
    
      # Step #6c
      cv2.line(tmp, (centroid_x, centroid_y),
               (int(centroid_x+np.cos(theta)*width),
                int(centroid_y-np.sin(theta)*height)), 255, 5)
    
      # Step #6d
      (row,col) = np.nonzero(np.logical_and(tmp, ref))
    
      # Step #6e
      cv2.line(out, (centroid_x, centroid_y), (col[0],row[0]), 0, 1)
    
    # Show the image
    # Step #7
    cv2.imshow('Output', out)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    这是我得到的结果。我选择了20个角度:

    【讨论】:

    • 非常感谢您的回答。尽管该线在所有可能的路口都没有与凸曲线相交,但您的方法足以满足我当前的需求。主要帮助是步骤 6a-e。另外,非常感谢您回答问题的方式,因为它非常详细且易于理解。当我将来遇到问题时,我希望通过 codementor 获得您的指导。再次感谢。
    • @samkhan13 - 是的。这是我方法的一个缺陷。我可以建议的一件事是查看np.nonzeros 返回的内容。它是与曲线相交的所有值的列表。因此,您可以扫描每个(x,y) 值并找到最极端的坐标并从那里画一条线。此外,您可以从质心到每个交点绘制多条线,以确保我们正确绘制线。
    • @samkhan13 - 谢谢你的赞美 :) 我当然有空。给我发个信息吧。祝你好运!
    • 你将如何计算这些行的长度?
    【解决方案2】:

    我将采用一种老式的方式,我怀疑 STL 或对编译器的普遍信任可以加速这种方式;)。顺便说一句,C++。

    • 将所需的所有角度放在一个列表中。
    • 遍历轮廓中的所有点,并从上面的列表中收集与某些点匹配的点。
    • 画线。

      findContours( f, contours, heirarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE );
      
      Moments M = moments( contours[0], true );
      Point2f cntr = Point2f( (int)M.m10/M.m00, (int)M.m01/M.m00 );
      circle( frame, cntr, 5, Scalar(0,0,0) );
      
      int N = 20;
      vector<float> slopes;
      for( int i=0; i<N; i++ )
          slopes.push_back( i*360.0/N ); 
      
      
      for( auto s : slopes )
          for( auto p : contours[0] )
              if( std::abs( cv::fastAtan2( p.y-cntr.y, p.x-cntr.x ) - s ) <= 0.5 )    //error margin, of sorts..
                  {   finalpoints.push_back( p ); break; }
      
      cout<<"\nfound points: "<<finalpoints.size()<<endl; 
      for( auto p : finalpoints )
          line( frame, cntr, p, Scalar(0,0,0), 1 );
      

    【讨论】:

    • +1 表示努力和展示另一种方法。我担心的是,当轮廓变大而线条数很少时,您的方法可能会变得更加耗时。您的方法就像绘制大量虚拟线并保留与所需角度匹配的虚拟线,效率不高。
    • 是的。我开始想一个更有效的解决方案来找出所需的点,但后来我有其他事情要处理。我会回到这个。
    猜你喜欢
    • 2018-09-09
    • 2013-08-23
    • 2020-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-22
    相关资源
    最近更新 更多