OpenCV:以给定角度通过轮廓质心的线

2024-06-02 12:11:14 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个名为cnt的轮廓,它来自下面的图像:

image description

我可以找到像这样的质心:

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

我现在要画N条线,每一条线间隔360/N度,从质心开始,在所有可能的交点处切割轮廓。line()函数需要起点和终点,但我没有终点。在

如果我用Tan(360/N)的斜率画一条穿过质心的线,我就可以用bitwise_and找到这条线与轮廓的交点,但我无法找到绘制这条线的方法。在

如果您能帮助我们画出这样的界线,我们将不胜感激。在


Tags: 图像间隔linecv2int轮廓m01质心
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 );
    

我有点事要做。这有点特别,但这基本上就是我写的算法。我必须重建你图像的轮廓,所以我所做的就是手动读取图像,提取出物体最外层的轮廓,然后从那里开始。cv2.line方法的优点是,如果您绘制一条超出边界的线,该线将被图像边界剪裁。这对我写的算法很有用。在

无需赘言,这些步骤如下:

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

    a.创建临时空白图像

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

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

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

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

    使用步骤(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个角度:

{1美元^

相关问题 更多 >