samkhan13
samkhan13

Reputation: 3385

OpenCV: Lines passing through centroid of contour at given angles

I have a contour named cnt obtained from the image bellow:

image description

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

Answers (2)

a-Jays
a-Jays

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.

  • Put all the angles you want in a list.
  • Iterate through all the points in the contour, and collect the ones that match something from the above list.
  • 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

rayryeng
rayryeng

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:

  1. Read in the image, threshold then invert the image so that the black contour lines becomes white points.
  2. Detect the outer most contour.
  3. Create a copy of the original input image so we can draw our lines. Call this out.
  4. Create a "reference" image that stores this outermost contour found in Step #2.
  5. Detect the centroid of the contour points. Also access the width and height of the image. If you don't have any width or height values, then choose a value that is very large... perhaps something like 1000. You need to make sure that this value is beyond the maximum value of any points along your contour. Also, set the total number of angles you want, N.
  6. 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).

  7. 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:

enter image description here

Upvotes: 4

Related Questions