Reputation: 3385
I have a contour named cnt
obtained from the image bellow:
for which I am able to find the centroid like this:
M = cv2.moments(cnt)
centroid_x = int(M['m10']/M['m00'])
centroid_y = int(M['m01']/M['m00'])
I now want to draw N number of lines, each 360/N degrees apart, starting from the centroid and cutting through the contour at all possible points of intersection. The cv2.line() function requires start point and end point but I don't have the end point.
If I had drawn a line passing through the centroid with a slope of Tan(360/N) I would have found the intersection of the line with the contour using bitwise_and
but I'm not able to figure out a way to draw that line.
Any help on how to draw such lines would be much appreciated.
Upvotes: 1
Views: 3987
Reputation: 1212
I'll throw in an old fashioned way, which I suspect could be speeded up by STLs or a general trust in the compiler ;). C++, by the way.
Draw lines.
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 );
Upvotes: 1
Reputation: 104575
I got something working. It's a bit ad-hoc, but this is basically the algorithm I wrote. I had to reconstruct the contour of your image, so what I did was I read in the image manually, extracted the outer most contour of the object, then proceeded from there. What's good about the cv2.line
method is that if you draw a line that is out of bounds, the line is clipped by the image boundaries. This will be useful in the algorithm I wrote up.
Without further ado, these are the steps:
out
.N
.For each angle we have, so for i = 0, 1
up to N - 1
:
a. Create a temporary blank image
b. Calculate appropriate angle: i*(360 / N)
and convert to radians
c. On the temporary image, draw a line from the centroid of the contours to a coordinate outside of the image to ensure we draw a line towards the image boundary that is along the angle we want. The horizontal component of this line is cos(360/N)
(argument is in degrees here) while the vertical component is -sin(360/N)
(argument also in degrees). The negative is due to the fact that the y
axis is positive going downwards in our image coordinate space, so the negative is to revert this so that the positive is going upwards with respect to Cartesian coordinates. The reason for this is so that when we calculate the angles that each line makes with the centre, the angles will be correct in that positive angles sweep counterclockwise. Starting from the centroid, we will move over by the width of the image for the horizontal and the height of the image for vertical maintaining the horizontal and vertical components found previously. This will make us draw the line out of bounds, but the line will be clipped by the image boundaries.
Another intricacy is to draw a line in this temporary image that is sufficiently thick. If we drew a line that was only 1 pixel thick, you may get a case where the line doesn't intersect with the contour due to the sampling of the pixels and the way the line is drawn. I chose a thickness of 5 pixels here to be sure.
d. Using this temporary image, see which locations are equal to the reference image. For any locations that are equal, we have found where this line intersects with the contour of the original image. As such, choose any location that intersects, as the thick line will most likely produce more than one intersection point with the outermost contour.
e. Using step (d), draw a line from the centroid of out
to the location we found in step (d).
Repeat Step #6 for all angles. out
will contain our the result after Step #6 is completed.
Without further ado, here's the code I wrote:
# 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()
Here's the result I get. I chose 20 angles:
Upvotes: 4