Counting curves, angles and straights in a binary image in openCV and python

Issue

I want to write a tool for finding the number of angles, curves and straight lines within each bounded object in an image.
All input images will be black on white background and all will represent characters.

As illustrated in the image, for each bounded region, each shape occurrence is noted. It would be preferable to be able to have a threshold for how curvy a curve must be to be considered a curve and not an angle etc. And the same for straight lines and angles.

I have used Hough Line Transform for detecting straight lines on other images and it might work in combination with something here I thought.

Am open to other libraries than opencv – this is just what I have some experience with.

EDIT:
So based on the answer from Markus, I made a program using the `findContours()` with `CHAIN_APPROX_SIMPLE`.

It produces a somewhat wierd result inputting a ‘k’ where it correctly identifies some points around the angles but then the ‘leg’ (the lower diagonal part) has many many points on it. I am unsure how to go about segmenting this to group into straights, angles and curves.

Code:

``````import numpy as np

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
edges = cv2.Canny(blurred, 50, 150, apertureSize=3)
ret, thresh = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY_INV)

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#cv2.drawContours(img, contours, 0, (0,255,0), 1)

#Coordinates of each contour
for i in range(len(contours[0])):
print(contours[0][i][0][0])
print(contours[0][i][0][1])
cv2.circle(img, (contours[0][i][0][0], contours[0][i][0][1]), 2, (0,0,255), -1)

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
``````

Img example:

Solution

You can use findContours with option `CHAIN_APPROX_SIMPLE`.

• A point with an angle less than some threshold is a corner.
• A point with an angle more than some threshold is on a straight line and should be removed.
• Two adjacent points with a distance of more than some threshold are the ends of a straight line.
• Two adjacent points that are identified to be corners are the ends of a straight line.
• All other points belong to some curvy detail.

Update:

Here is some code you can start with. It shows how to smoothen the straight lines, how you can merge several corner points into one, and how to calculate distances and angles at each point. There is still some work to be done for you to achieve the required result but I hope it leads in the right direction.

``````import numpy as np
import numpy.linalg as la
import cv2

def get_angle(p1, p2, p3):
v1 = np.subtract(p2, p1)
v2 = np.subtract(p2, p3)
cos = np.inner(v1, v2) / la.norm(v1) / la.norm(v2)

def get_angles(p, d):
n = len(p)
return [(p[i], get_angle(p[(i-d) % n], p[i], p[(i+d) % n])) for i in range(n)]

def remove_straight(p):
angles = get_angles(p, 2)                     # approximate angles at points (two steps in path)
return [p for (p, a) in angles if a < 170]    # remove points with almost straight angles

def max_corner(p):
angles = get_angles(p, 1)                     # get angles at points
j = 0

while j < len(angles):                        # for each point
k = (j + 1) % len(angles)                 # and its successor
(pj, aj) = angles[j]
(pk, ak) = angles[k]

if la.norm(np.subtract(pj, pk)) <= 4:     # if points are close
if aj > ak:                           # remove point with greater angle
angles.pop(j)
else:
angles.pop(k)
else:
j += 1

return [p for (p, a) in angles]

def main():
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY_INV)

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

for c in contours:                  # for each contour
pts = [v[0] for v in c]         # get pts from contour
pts = remove_straight(pts)      # remove almost straight angles
pts = max_corner(pts)           # remove nearby points with greater angle
angles = get_angles(pts, 1)     # get angles at points

# draw result
for (p, a) in angles:
if a < 120:
cv2.circle(img, p, 3, (0, 0, 255), -1)
else:
cv2.circle(img, p, 3, (0, 255, 0), -1)

cv2.imwrite('out.png', img)
cv2.destroyAllWindows()

main()
``````