在 openCV 和 python 中计算二值图像中的曲线、角度和直线

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

我想编写一个工具来查找图像中每个有界对象内的角度、曲线和直线的数量。 所有输入图像都是白底黑字,并且都代表字符。

如图所示,对于每个边界区域,每个形状出现都被记录下来。最好能够有一个阈值来确定曲线必须有多弯曲才能被视为曲线而不是角度等。对于直线和角度也是如此。

我已经使用霍夫线变换来检测其他图像上的直线,它可能与我认为的这里的东西结合使用。

我对 opencv 以外的其他库开放 - 这正是我的一些经验。

提前致谢

编辑: 因此,根据 Markus 的回答,我使用 findContours()CHAIN_APPROX_SIMPLE 制作了一个程序。

输入 'k' 它会产生一个有点奇怪的结果,它正确地识别了角度周围的一些点,但是 'leg' (下对角线部分)上面有很多点。我不确定如何将其分割成直线、角度和曲线。

代码:

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()

图片示例:

您可以将 findContours 与选项 CHAIN_APPROX_SIMPLE 结合使用。

  • 角度小于某个阈值的点是一个角。
  • 角度大于某个阈值的点在一条直线上,应将其删除。
  • 两个相邻点的距离大于某个阈值是一条直线的端点。
  • 被识别为角点的两个相邻点是一条直线的端点。
  • 所有其他点都属于一些弯曲的细节。

更新:

这是您可以开始的一些代码。它展示了如何平滑直线,如何将多个角点合并为一个角点,以及如何计算每个点的距离和角度。要达到要求的结果,您还有一些工作要做,但我希望它能引导正确的方向。

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()