2

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.

Thanks in advance

IMAGE: Occurences of shape in the letters

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

img = cv2.imread('Helvetica-K.png')
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: K-helvitica

YorkHvede
  • 33
  • 4
  • Hough is the wrong approach IMHO. this should operate purely on contours (lists of points). a segmentation of a contour into straight and curved pieces is *not* included in OpenCV, but other software packages (like the commercial "halcon") offer this. -- feel free to write a feature suggestion to OpenCV. open an issue on its github – Christoph Rackwitz May 03 '22 at 16:11
  • Thank you, that makes sense. Unfortunately paid services are beyond the scope of what i'm doing right now. – YorkHvede May 04 '22 at 10:10

1 Answers1

3

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)
    rad = np.arccos(np.clip(cos, -1.0, 1.0))
    return np.rad2deg(rad)


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():
    img = cv2.imread('abc.png')
    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()

enter image description here

Markus
  • 5,976
  • 5
  • 6
  • 21
  • Thanks. I can see findContours might be useful. However I cannot reliably get findContours to detect only the points of the contours. I.e. applying it to a 'k', it will detect points at the angles of the stem and the 'arm' correctly. However the 'leg' (the bottom diagonal line will have many many points running along its direction. How do I distinguish between these as they are basically just running along a straight, diagonal line? Also I'm not exactly sure about your suggestion about how to segment the contours. I don't think I quite understand what you mean. I'm from a non math background. – YorkHvede May 04 '22 at 10:09
  • The option `CHAIN_APPROX_SIMPLE` removes extra points on straight lines. – Markus May 04 '22 at 10:40
  • Thanks. I have edited the original post to illustrate how it stille has many points on the straight lines even though CHAIN_APPROX_SIMPLE. – YorkHvede May 04 '22 at 10:56
  • I have added some code how to work with OpenCV contours. – Markus May 04 '22 at 15:59
  • Thanks a lot! It definately seems useful. I will try and work around with it to see if I can achieve something along the lines of what i want it to. – YorkHvede May 04 '22 at 16:35