OpenCV:以给定角度穿过轮廓质心的线
OpenCV: Lines passing through centroid of contour at given angles
我从下面的图像中获得了一个名为 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
找到直线与等高线的交点,但我做不到想办法画那条线。
如能提供有关如何绘制此类线条的任何帮助,我们将不胜感激。
我有一些工作。这有点特别,但这基本上就是我写的算法。我必须重建图像的轮廓,所以我所做的是手动读取图像,提取物体的最外层轮廓,然后从那里开始。 cv2.line
方法的好处在于,如果您绘制一条超出边界的线,则该线会被图像边界裁剪掉。这对我写的算法很有用。
事不宜迟,步骤如下:
- 读入图像,阈值然后反转图像,使黑色轮廓线变成白色点。
- 检测最外面的轮廓。
- 创建原始输入图像的副本,以便我们绘制线条。称之为
out
.
- 创建一个 "reference" 图像来存储在步骤 #2 中找到的最外层轮廓。
- 检测轮廓点的质心。还可以访问图像的宽度和高度。如果您没有任何宽度或高度值,则选择一个非常大的值……可能是 1000。您需要确保该值超出轮廓上任何点的最大值。另外,设置你想要的角度总数,
N
。
对于每个角度,所以 i = 0, 1
到 N - 1
:
一个。创建临时空白图像
b。计算合适的角度:i*(360 / N)
并转换为弧度
c。在临时图像上,从轮廓的质心到图像外部的坐标画一条线,以确保我们沿着我们想要的角度向图像边界画一条线。这条线的水平分量是 cos(360/N)
(这里的参数以度为单位),而垂直分量是 -sin(360/N)
(参数也是以度为单位)。负值是由于 y
轴在我们的图像坐标 space 中向下为正,因此负值是将其恢复为正值相对于笛卡尔坐标向上。这样做的原因是,当我们计算每条线与中心所成的角度时,正角度逆时针方向扫描的角度将是正确的。从质心开始,我们将在水平方向上移动图像的宽度,在垂直方向上移动图像的高度,以保持之前找到的水平和垂直分量。这将使我们在边界外绘制线,但线将被图像边界剪裁。
另一个复杂的问题是在此临时图像中画一条 足够 粗的线。如果我们画一条只有 1 个像素粗的线,由于像素采样和线的绘制方式,您可能会遇到线不与轮廓相交的情况。为了保险起见,我这里选择了5像素的粗细
d。使用此临时图像,查看哪些位置 等于 参考图像。对于任何相等的位置,我们已经找到这条线与原始图像的轮廓相交的位置。因此,选择任何相交的位置,因为粗线很可能会与最外层轮廓产生多个交点。
e。使用步骤 (d),从 out
的质心到我们在步骤 (d) 中找到的位置画一条线。
对所有角度重复步骤#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个角度:
我将采用一种老式的方式,我怀疑这种方式可以通过 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 );
我从下面的图像中获得了一个名为 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
找到直线与等高线的交点,但我做不到想办法画那条线。
如能提供有关如何绘制此类线条的任何帮助,我们将不胜感激。
我有一些工作。这有点特别,但这基本上就是我写的算法。我必须重建图像的轮廓,所以我所做的是手动读取图像,提取物体的最外层轮廓,然后从那里开始。 cv2.line
方法的好处在于,如果您绘制一条超出边界的线,则该线会被图像边界裁剪掉。这对我写的算法很有用。
事不宜迟,步骤如下:
- 读入图像,阈值然后反转图像,使黑色轮廓线变成白色点。
- 检测最外面的轮廓。
- 创建原始输入图像的副本,以便我们绘制线条。称之为
out
. - 创建一个 "reference" 图像来存储在步骤 #2 中找到的最外层轮廓。
- 检测轮廓点的质心。还可以访问图像的宽度和高度。如果您没有任何宽度或高度值,则选择一个非常大的值……可能是 1000。您需要确保该值超出轮廓上任何点的最大值。另外,设置你想要的角度总数,
N
。 对于每个角度,所以
i = 0, 1
到N - 1
:一个。创建临时空白图像
b。计算合适的角度:
i*(360 / N)
并转换为弧度c。在临时图像上,从轮廓的质心到图像外部的坐标画一条线,以确保我们沿着我们想要的角度向图像边界画一条线。这条线的水平分量是
cos(360/N)
(这里的参数以度为单位),而垂直分量是-sin(360/N)
(参数也是以度为单位)。负值是由于y
轴在我们的图像坐标 space 中向下为正,因此负值是将其恢复为正值相对于笛卡尔坐标向上。这样做的原因是,当我们计算每条线与中心所成的角度时,正角度逆时针方向扫描的角度将是正确的。从质心开始,我们将在水平方向上移动图像的宽度,在垂直方向上移动图像的高度,以保持之前找到的水平和垂直分量。这将使我们在边界外绘制线,但线将被图像边界剪裁。另一个复杂的问题是在此临时图像中画一条 足够 粗的线。如果我们画一条只有 1 个像素粗的线,由于像素采样和线的绘制方式,您可能会遇到线不与轮廓相交的情况。为了保险起见,我这里选择了5像素的粗细
d。使用此临时图像,查看哪些位置 等于 参考图像。对于任何相等的位置,我们已经找到这条线与原始图像的轮廓相交的位置。因此,选择任何相交的位置,因为粗线很可能会与最外层轮廓产生多个交点。
e。使用步骤 (d),从
out
的质心到我们在步骤 (d) 中找到的位置画一条线。对所有角度重复步骤#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个角度:
我将采用一种老式的方式,我怀疑这种方式可以通过 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 );