Straighten contours OpenCV

Viewed 264

Hello guys I would like to "straighten" some contours using opencv/python. Is there anyway to accomplish this?
I have attached two images:

  1. in the current stage 1
  2. and how I would like to see it 2.

Bounding boxes resolve the majority of the problem, but there are some exceptions that do not produce the desired outcome (see the top right contour in the image).
Thank you very much!

Current Contours

Squared Contours

1 Answers
def approximate_contours(image: np.ndarray, eps: float, color=(255, 255, 255)):
    contours, _ = cv.findContours(image, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    image = cv.cvtColor(image, cv.COLOR_GRAY2BGR)

    approx_contours = []
    for cnt in contours:
        epsilon = eps * cv.arcLength(cnt, True)
        approx = cv.approxPolyDP(cnt, epsilon, True)
        cv.drawContours(image, [approx], -1, color=color)

        approx_contours.append(approx)

    return image, approx_contours


def get_angle(pts: np.ndarray):
    a = np.array([pts[0][0][0], pts[0][0][1]])
    b = np.array([pts[1][0][0], pts[1][0][1]])
    c = np.array([pts[2][0][0], pts[2][0][1]])
    ba = a - b
    bc = c - b
    unit_vector_ba = ba / np.linalg.norm(ba)
    unit_vector_bc = bc / np.linalg.norm(bc)
    dot_product = np.dot(unit_vector_ba, unit_vector_bc)
    angle_rad = np.arccos(dot_product)
    angle_deg = degrees(angle_rad)

    try:
        int(angle_deg)
    except Exception as e:
        raise Exception("nan value detected")

    return int(angle_deg)


def move_points(contour:np.ndarray, pts: np.ndarray, angle: int, ext: list, weight=1):
    (ext_left, ext_right, ext_bot, ext_top) = ext
    a = np.array([pts[0][0][0], pts[0][0][1]])
    b = np.array([pts[1][0][0], pts[1][0][1]])
    c = np.array([pts[2][0][0], pts[2][0][1]])

    right_angle = False
    if 45 < angle < 135:
        right_angle = True
        diff_x_ba = abs(b[0] - a[0])
        diff_y_ba = abs(b[1] - a[1])
        diff_x_bc = abs(b[0] - c[0])
        diff_y_bc = abs(b[1] - c[1])
        rap_ba = diff_x_ba / max(diff_y_ba, 1)
        rap_bc = diff_x_bc / max(diff_y_bc, 1)

        if rap_ba < rap_bc:
            a[0] = int((a[0] * weight + b[0]) / (2 + weight - 1))
            b[0] = a[0]
            c[1] = int((c[1] + b[1]) / 2)
            b[1] = c[1]
        else:
            c[0] = int((c[0] + b[0]) / 2)
            b[0] = c[0]
            a[1] = int((a[1] * weight + b[1]) / (2 + weight - 1))
            b[1] = a[1]
    else:
        diff_x_ba = abs(b[0] - a[0])
        diff_y_ba = abs(b[1] - a[1])
        diff_x_bc = abs(b[0] - c[0])
        diff_y_bc = abs(b[1] - c[1])
        if (diff_x_ba + diff_x_bc) > (diff_y_ba + diff_y_bc):
            a[1] = int((a[1] * weight + b[1] + c[1]) / (3 + weight - 1))

            b[1] = a[1]
            c[1] = a[1]
        else:
            a[0] = int((a[0] * weight + b[0] + c[0]) / (3 + weight - 1))

            b[0] = a[0]
            c[0] = a[0]

    return a, b, c, right_angle


def straighten_contours(contours: list, image: np.ndarray, color=(255, 255, 255)):
    image = cv.cvtColor(image, cv.COLOR_GRAY2BGR)

    for cnt in contours:
        idx = 0
        ext_left = cnt[cnt[:, :, 0].argmin()][0]
        ext_right = cnt[cnt[:, :, 0].argmax()][0]
        ext_top = cnt[cnt[:, :, 1].argmin()][0]
        ext_bot = cnt[cnt[:, :, 1].argmax()][0]

        while idx != int(cnt.size / 2):
            try:
                angle = get_angle(cnt[idx:idx + 3])
            except Exception:
                idx += 1
                continue
            (a, b, c, right_angle) = move_points(cnt, cnt[idx:idx + 3], angle, [ext_left, ext_right, ext_bot, ext_top])
            cnt[idx][0] = a
            cnt[idx + 1][0] = b
            cnt[idx + 2][0] = c
            idx += 1
            if not right_angle:
                idx -= 1
                cnt = np.delete(cnt, (idx + 1), 0)

            if idx == 1:
                cnt = np.append(cnt, cnt[:2], axis=0)

        cnt = np.delete(cnt, [0, 1], 0)
        cv.drawContours(image, [cnt], -1, color=color)

    return image

I managed to do some workarounds. The straighten contours function is applied onto the approximate_contours result (the first image in the question). Is not as good as I would have wanted it to be but it works.

Result

Related