2

I need help on how to detect the current angle of a shape based on another image of the same shape?

For example, lets say I take an image and after the usual image processing:

  • Change to gray
  • Apply threshold

(I am using ideal shapes drawn in paint, the objective is to detect physical objects but for simplicity lets assume this images)

I get this white shape image:

enter image description here

So now my new image is the following (without the red lines) which has moved 225 degrees.

enter image description here

How can I calculate the 225 degrees based on the original image orientation?

I have managed to get the following code working. But the angle wrt the original sample is something I can not figure out

enter image description here

As you can see, this position reports 45º but it should report 225º

enter image description here

The idea is to be able to do the same processing with this kind of shapes also:

enter image description here

This one should report 315º

enter image description here

# ------------------------------- FUNCTIONS -------------------------------------
def drawGrid(image):

    print("Image size: " + str(result.shape))       # (700, 900, 3)

    image_width = result.shape[1]       # 900
    image_height = result.shape[0]      # 700

    # https://www.geeksforgeeks.org/python-opencv-cv2-line-method/
    number_lines = int(image_width / 100)
    for x in range(number_lines):
        # Start coordinate, here (0, 0)
        # represents the top left corner of image
        start_point = (100*x, 0)   # x, y

        # End coordinate, here (250, 250)
        # represents the bottom right corner of image
        end_point = (100*x, image_height - 10)

        # Green color in BGR
        color = (0, 255, 0)

        # Line thickness of 9 px
        thickness = 1

        # Draw a diagonal green line with thickness of 9 px
        image = cv2.line(result, start_point, end_point, color, thickness)

        # Write line number
        cv2.putText(result,str(x), (100*x,image_height - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0))

    number_lines = int(image_height / 100)
    for y in range(number_lines):
        # Start coordinate, here (0, 0)
        # represents the top left corner of image
        start_point = (0, 100*y)   # x, y

        # End coordinate, here (250, 250)
        # represents the bottom right corner of image
        end_point = (image_width - 10, 100*y)

        # Green color in BGR
        color = (0, 255, 0)

        # Line thickness of 9 px
        thickness = 1

        # Draw a diagonal green line with thickness of 9 px
        image = cv2.line(result, start_point, end_point, color, thickness)

        # Write line number
        cv2.putText(result,str(y), (image_width - 10,100*y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0))


# resize image
def resizeImage(img): 
    scale_percent = 100 # percent of original size
    width = int(img.shape[1] * scale_percent / 100)
    height = int(img.shape[0] * scale_percent / 100)
    dim = (width, height)
    # resize image
    resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
    return resized
# ------------------------------- FUNCTIONS -------------------------------------


correct_area = 37621

# load image as HSV and select saturation
img = cv2.imread("images/sample225.png")

# img = resizeImage(img)

hh, ww, cc = img.shape

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

""""
# Show image and wait
cv2.imshow("img", gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
"""

# threshold the grayscale image
ret, thresh = cv2.threshold(gray, 0, 255, 0)


# find outer contour
cntrs = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# select first contour, or if 2 contours are found, select the second one
cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]


# Loop over the contours to count them
number_contours = 0
for c in cntrs:
    number_contours += 1
print("Number of contours found: " + str(number_contours))


# Get rotated rectangle from outer contour
rotrect = cv2.minAreaRect(cntrs[0])
# print coordinates of box
print("Rectangle Box definition: ")
print(rotrect)
# Get external box of the contour points
box = cv2.boxPoints(rotrect)
box = np.int0(box)
print("Rectangle Box points: ")
print(box)


# draw rotated rectangle on copy of img as result
result = img.copy()
cv2.drawContours(result,[box],0,(0,0,255),2)


# get angle from rotated rectangle
angle = rotrect[-1]
print("Angle: " + str(angle))

# from https://www.pyimagesearch.com/2017/02/20/text-skew-correction-opencv-python/
# the `cv2.minAreaRect` function returns values in the
# range [-90, 0); as the rectangle rotates clockwise the
# returned angle trends to 0 -- in this special case we
# need to add 90 degrees to the angle
if angle < -45:
    angle = -(90 + angle)
angle = round(angle,2)
print("Corrected angle: " + str(angle))


# Draw a grid to check coordinates visually
drawGrid(result)

# Get area of the contour
area = cv2.contourArea(box)
print ("Area: " + str(area))


# https://www.pyimagesearch.com/2016/02/01/opencv-center-of-contour/
# compute the center of the contour
# Find center position of the external contour box
M = cv2.moments(box)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
print("Box center x: " + str(cX))
print("Box center y: " + str(cY))
cv2.circle(result, (cX,cY), 3, (0,0,255), thickness=-1, lineType=8, shift=0)
boxX = cX
boxY = cY

# Find center position of the contour [0]
M = cv2.moments(cntrs[0])
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
print("Box center x: " + str(cX))
print("Box center y: " + str(cY))
cv2.circle(result, (cX,cY), 3, (0,255,0), thickness=-1, lineType=8, shift=0)
cntrsX = cX
cntrsY = cY


# Draw -centers- data on image
cv2.putText(result, "Center Box", (cX+30, cY-30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255))
cv2.putText(result, "Center Cnt", (cX+30, cY+30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0))


# draw axis
red_color = (0, 0, 255)
blue_color = (255, 0, 0)
center = cX, cY
print("center: " + str(center))
end_point = (cX+150, cY)
end_point2 = (cX, cY+150)
print("start_point:"  + str(center))
print("end_point:"    + str(end_point))
cv2.line(result, center, end_point, blue_color)
cv2.line(result, center, end_point2, blue_color)


# Draw information on top left
cv2.putText(result,"Angle: " + str(angle), (5,50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255))
cv2.putText(result,"Pos: " + str(box), (5,100), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255))
cv2.putText(result,"Number of contours: " + str(number_contours), (5,150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255))
cv2.putText(result,"Area: " + str(area), (5,200), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255))
"""
if(area > correct_area*0.95 and area < correct_area*1.05):
    cv2.putText(result, "OK", (1320, 100), cv2.FONT_HERSHEY_SIMPLEX, 4, (0, 255, 0), 10)
else:
    cv2.putText(result, "NOK", (1230, 100), cv2.FONT_HERSHEY_SIMPLEX, 4, (0, 0, 255), 10)
"""

cv2.putText(result,"Center M box: " + str((boxX,boxY)), (5,250), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255))
cv2.putText(result,"Center M contour[0]: " + str((cntrsX,cntrsY)), (5,300), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0))


RestaX = boxX-cntrsX
RestaY = cntrsY-boxY

cv2.putText(result, "X Diff: " + str(RestaX), (5, 350), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255))
cv2.putText(result, "Y Diff: " + str(RestaY), (5, 400), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255))


# Calculate angle w.r.t original shape
cv2.putText(result, "Angle w.r.t original: " + str("???"), (5, 450), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255))


""""
if(RestaX > 0  and RestaX != 0):
    analysis_result = "Redondo en Derecha"
elif(RestaX < 0  and RestaX != 0):
    analysis_result = "Redondo en Izquierda"
else:
    analysis_result = "Redondo indeterminado"
cv2.putText(result, analysis_result, (5, 450), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255))
"""

# Show image
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

If you run this code against this image, it should give you the result above.

enter image description here

Thanks for your help.

Jeru Luke
  • 20,118
  • 13
  • 80
  • 87
Serge
  • 312
  • 5
  • 20
  • Your second example (for 315 deg) is easy. Just use minAreaRect() to get the rotated bounding rectangle's angle. – fmw42 Jan 08 '20 at 19:01
  • I am looking for a code solution which applies to any shape in any angle. Hoping this to be possible. – Serge Jan 08 '20 at 19:22
  • A simple white square shape will have multiple angle solutions at 90 deg increments. So it is not possible to have a universal solution that includes shapes that have symmetry. You could try template matching at multiple angles, if the shapes are non-symmetric. If you have enough corners, you might try ORB feature matching. – fmw42 Jan 08 '20 at 19:50
  • You are right. My shapes are never a square. They all have at least one asymetrical axis. Any better solution than having to shapematch iterating angles ? That would make it loose a lot of performance. Thanks – Serge Jan 08 '20 at 20:39
  • Have you tried using ORB to get matching feature (corners). Then get the affine transformation and then get the rotation angle. You can also try a log polar transformation and then do template matching. Offsets in the polar domain corresponds to angles. Search Google or this forum for such. See https://stackoverflow.com/questions/17827811/angle-and-scale-invariant-template-matching-using-opencv and https://github.com/Smorodov/LogPolarFFTTemplateMatcher and http://www.cb.uu.se/~aht/articles/0027-0021.pdf – fmw42 Jan 08 '20 at 20:52
  • Thank you I will investigate about ORB and report back. I was thinking... Could we use the hu moments center point of the object contour Vs the one of the minrectangle around the object? The delta between them could point towards which direction the object is in? – Serge Jan 08 '20 at 21:45
  • You can get the angle of the equivalent ellipse from the Hu moments. See http://www.fmwconcepts.com/imagemagick/moments/index.php. This is also now built into `identify -verbose -moments some_image` in ImageMagick, so should be available in Python Wand. You can compute that also in OpenCV using the formulae from http://www.via.cornell.edu/ece547/text/survey.pdf Alternately, fit an ellipse to your objects and get the angle of the major axis. OpenCV has several ellipse fitting routines. See https://docs.opencv.org/4.1.1/d3/dc0/group__imgproc__shape.html#gaf259efaad93098103d6c27b9e4900ffa – fmw42 Jan 09 '20 at 00:42
  • I have been doing many tests without success. I think one solution would be to calculate the center point of the contour as well as the center point of the minimum rectangle around it. Then by joining this two points get an angle. This is done to the sample image and then to the image under analysis. By comparing angles be able to calculate the angle of the image under analysis wrt the original image. Any suggestions? Cause I can't manage to get it to work – Serge Jan 21 '20 at 19:34

0 Answers0