John
John

Reputation: 1828

How to measure the central angle with Python cv2 package

Our team set up a vision system with a camera, a microscope and a tunable lens to look at the internal surface of a cone.

Visually speaking, the camera takes 12 image for one cone with each image covering 30 degrees.

Now we've collected many sample images and want to make sure each "fan"(as shown below) is at least 30 degree.

Is there any way in Python, with cv2 or other packages, to measure this central angle. Thanks.

enter image description here

Upvotes: 0

Views: 1115

Answers (2)

Joe
Joe

Reputation: 7121

That sounds possible. You need to do some preprocessing and filtering to figure out what works and there is probably some tweaking involved.

There are three approaches that could work.

1.)

The basic idea is to somehow get two lines and measure the angle between them.

  • Define a threshold to define the outer black region (out of the central angle) and set all values below it to zero.

  • This will also set some of the blurry stripes inside the central angle to zero so we have to try to "heal" them away. This is done by using Morphological Transformations. You can read about them here and here.

  • You could try the operation Closing, but I don't know if it fixes stripes. Usually it fixes dots or scratches. This answer seems to indicate that it should work on lines.

  • Maybe at that point apply some Gaussian blurring and to the threshold thing again. Then try to use some edge or line detection.

It's basically try and error, you have to see what works.

2.)

  • Another thing that could work is to try to use the arc-enter code herelike scratches, maybe even strengthen them and use the Hough Circle Transform. I think it detects arcs as well.

  • Just try it and see what the function returns. In the best case there are several circles / arcs that you can use to estimate the central angle.

  • There are several approaches on arc detection here on StackOverflow or here.

  • I am not sure if that's the same with all your image, but the one above looks like there are some thin, green and pink arcs that seem to stretch all along the central angle. You could use that to filter for that color, then make it grey scale.

  • This question might be helpful.

3.)

  • Apply an edge filter, e.g Canny skimage.feature.canny

  • Try several sigmas and post the images in your question, then we can try to think on how to continue.

  • What could work is to calculate the convex hull around all points that are part of an edge. Then get the two lines that form the central angle from the convex hull.

Upvotes: 1

fmw42
fmw42

Reputation: 53089

Here is one way to do that in Python/OpenCV.

  • Read the image
  • Convert to gray
  • Threshold
  • Use morphology open and close to smooth and fill out the boundary
  • Apply Canny edge extraction
  • Separate the image into top edge and bottom edge by blackening the opposite side to each edge
  • Fit lines to the top and bottom edges
  • Compute the angle of each edge
  • Compute the difference between the two angles
  • Draw the lines on the input
  • Save the results

Input:

enter image description here

import cv2
import numpy as np
import math

# read image
img = cv2.imread('cone_shape.jpg')

# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# threshold
thresh = cv2.threshold(gray,11,255,cv2.THRESH_BINARY)[1]

# apply open then close to smooth boundary
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13,13))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = np.ones((33,33), np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)

# apply canny edge detection
edges = cv2.Canny(morph, 150, 200)
hh, ww = edges.shape
hh2 = hh // 2

# split edge image in half vertically and blacken opposite half
top_edge = edges.copy()
top_edge[hh2:hh, 0:ww] = 0
bottom_edge = edges.copy()
bottom_edge[0:hh2, 0:ww] = 0

# get coordinates of white pixels in top and bottom
# note: need to transpose y,x in numpy to x,y for opencv
top_white_pts = np.argwhere(top_edge.transpose()==255)
bottom_white_pts = np.argwhere(bottom_edge.transpose()==255)

# fit lines to white pixels
# (x,y) is point on line, (vx,vy) is unit vector along line
(vx1,vy1,x1,y1) = cv2.fitLine(top_white_pts, cv2.DIST_L2, 0, 0.01, 0.01)
(vx2,vy2,x2,y2) = cv2.fitLine(bottom_white_pts, cv2.DIST_L2, 0, 0.01, 0.01)

# compute angle for vectors vx,vy
top_angle = (180/math.pi)*math.atan(vy1/vx1)
bottom_angle = (180/math.pi)*math.atan(vy2/vx2)
print(top_angle, bottom_angle)

# cone angle is the difference
cone_angle = math.fabs(top_angle - bottom_angle)
print(cone_angle)

# draw lines on input
lines = img.copy()
p1x1 = int(x1-1000*vx1)
p1y1 = int(y1-1000*vy1)
p1x2 = int(x1+1000*vx1)
p1y2 = int(y1+1000*vy1)
cv2.line(lines, (p1x1,p1y1), (p1x2,p1y2), (0, 0, 255), 1)
p2x1 = int(x2-1000*vx2)
p2y1 = int(y2-1000*vy2)
p2x2 = int(x2+1000*vx2)
p2y2 = int(y2+1000*vy2)
cv2.line(lines, (p2x1,p2y1), (p2x2,p2y2), (0, 0, 255), 1)

# save resulting images
cv2.imwrite('cone_shape_thresh.jpg',thresh)
cv2.imwrite('cone_shape_morph.jpg',morph)
cv2.imwrite('cone_shape_edges.jpg',edges)
cv2.imwrite('cone_shape_lines.jpg',lines)

# show thresh and result    
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("edges", edges)
cv2.imshow("top edge", top_edge)
cv2.imshow("bottom edge", bottom_edge)
cv2.imshow("lines", lines)
cv2.waitKey(0)
cv2.destroyAllWindows()


Thresholded image:

enter image description here

Morphology processed image:

enter image description here

Edge Image:

enter image description here

Lines on input:

enter image description here

Cone Angle (in degrees):

42.03975696357633

Upvotes: 2

Related Questions