Question

Break the curve wherever there is a significant change in the curve

Wherever there's a significant change in the curve, I need to break the curve with a break thickness of just 1 pixel (I just want to break the curves into multiple parts). I have attached an image for reference. So after i read the image, i am thinning the curve and wherever there are red dots, i need to split it around that area. Red dots indicate the places where I want the image to be cut

This is the output that I am currently getting

Unaltered Image

The first image is the input image and red dots indicate where I want the cut (the image will not actually have the red dot)/ The second image is the current output that I am getting. The third image is the unaltered image for reference.

I have tried implementing the following codes:

import cv2
import numpy as np
from matplotlib import pyplot as plt

image_path = rf'C:\Users\User\Desktop\output.png'
img = cv2.imread(image_path, 0)

ret, binary = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)


binary = cv2.ximgproc.thinning(binary, thinningType=cv2.ximgproc.THINNING_GUOHALL)

coords = np.column_stack(np.where(binary > 0))
def calculateAngle(p1, p2, p3):
    v1 = np.array(p2) - np.array(p1)
    v2 = np.array(p3) - np.array(p2)
    angle = np.arctan2(v2[1], v2[0]) - np.arctan2(v1[1], v1[0])
    angle = np.degrees(angle)
    if angle < 0:
        angle += 360
    return angle

startBlackImg = np.zeros((binary.shape[0], binary.shape[1], 1), np.uint8)
i = 1
while i < (len(coords) - 1):
    p1 = coords[i - 1]
    p2 = coords[i]
    p3 = coords[i + 1]
    i += 1
    angle = calculateAngle(p1, p2, p3)
    
    if angle < 45 or angle > 315:
        startBlackImg[p2[0], p2[1]] = 255
    else:
        startBlackImg[p2[0], p2[1]] = 0

cv2.namedWindow('Check', 0)
cv2.imshow('Check', startBlackImg)
cv2.waitKey(0)
cv2.destroyAllWindows()

and the other logic is

while kk < len(cutContour) - 10:
            xCdte = cutContour[kk][0][0]
            yCdte = cutContour[kk][0][1]
            xNextCdte = cutContour[kk + 10][0][0]
            yNextCdte = cutContour[kk + 10][0][1]
            kk += 1
            if totalDistance <= 0.3048:
                startBlackImg[yCdte, xCdte] = np.array([255, 255, 255])
                startBlackImg[yNextCdte, xNextCdte] = np.array([255, 255, 255])
            else:
                if (abs(xCdte - xNextCdte) < 10 and abs(yCdte - yNextCdte) >= 10) or (abs(xCdte - xNextCdte) >= 10 and abs(yCdte - yNextCdte) < 10):
                    startBlackImg[yCdte, xCdte] = np.array([255, 255, 255])
                    startBlackImg[yNextCdte, xNextCdte] = np.array([255, 255, 255])
                else:
                    startBlackImg[yCdte, xCdte] = np.array([0, 0, 0])
                    kk += 10

So far I am not getting what I want. Its breaking at multiple points and not just where I intend it to. Is there any library or and code to do this?

 3  101  3
1 Jan 1970

Solution

 2
  1. Hough Transform https://en.wikipedia.org/wiki/Hough_transform

enter image description here

  1. K-means iterations with different K's - find how many line groups

enter image description here

  1. K-means again with the right K

enter image description here

enter image description here

  1. Pixel clustering by min distance to lines

enter image description here

import math

import cv2 as cv
import numpy as np

import matplotlib.pyplot as plt

orig_im = cv.imread("/home/ophir/temp/stackoverflow2.png",cv.IMREAD_GRAYSCALE)

# use Hough Transform to get lots of straight lines
lines = cv.HoughLines(orig_im, 1, np.pi/180, 30);

im = cv.cvtColor(orig_im, cv.COLOR_GRAY2BGR)

im2 = im.copy()
im3 = im.copy()

plt.figure()
plt.imshow(im)

# draw all straight lines on image
rho_vals = []
theta_vals = []
for line in lines:
    for rho,theta in line:
        rho_vals.append(rho)
        theta_vals.append(theta)
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))

        cv.line(im2,(x1,y1),(x2,y2),(0,0,255),2)

rho_vals = np.array(rho_vals)
rho_vals = np.expand_dims(rho_vals, axis=0)

theta_vals = np.array(theta_vals)
theta_vals = np.expand_dims(theta_vals, axis=0)

Z = np.vstack((rho_vals,theta_vals)).T

Z = np.float32(Z)

# use K-means to cluster all straight lines to groups
# I don't know how many groups, so I check several K's
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
compactness = []
for i in range(2, 9):
    ret,label,center=cv.kmeans(Z,i + 1,None,criteria,10,cv.KMEANS_RANDOM_CENTERS)
    compactness.append(ret)

compactness = np.array(compactness)

# choose the right k, where the compactness isn't getting any better
derivative = compactness[1:] - compactness[:-1]
amax = np.argmax(derivative > -800) + 2

# do K-means again, this time with the right K
ret,label,center=cv.kmeans(Z, amax,None,criteria,10,cv.KMEANS_RANDOM_CENTERS)

# draw the centers of thr clusters on the lines parameters graph
lines = []
for rho, theta in center:
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho
    x1 = int(x0 + 1000*(-b))
    y1 = int(y0 + 1000*(a))
    x2 = int(x0 - 1000*(-b))
    y2 = int(y0 - 1000*(a))

    lines.append((x1,y1,x2,y2))
    cv.line(im3,(x1,y1),(x2,y2),(0,0,255),2)

# cluster the pixels in the original image by the minimum distance to a line
pixels = cv.findNonZero(orig_im)
labels = []
for pixel in pixels:
    x0, y0 = pixel[0]
    distances = []
    for line in lines:
        x1, y1, x2, y2 = line
        # https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
        dist = abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) / math.sqrt((y2 - y1)**2 + (x2 - x1)**2)
        distances.append(dist)
    labels.append(np.argmin(np.array(distances)))

im4 = np.zeros(im2.shape)

colors = [[0, 0, 255], [0, 255, 0], [255, 0, 0], [255, 255, 0], [255, 0, 255], [0, 255, 255]]

# assign different color to each pixel by the label of the clustering
for pixel, label in zip(pixels, labels):
    x, y = pixel[0]
    color = colors[label]
    im4[y,x, 0] = color[0]
    im4[y,x, 1] = color[1]
    im4[y,x, 2] = color[2]

plt.figure()
plt.plot(list(range(2,9)), compactness)
plt.scatter(amax, compactness[amax - 2], c = 'r')
plt.ylabel("compactness")
plt.xlabel("K")
plt.title("K-means compactness")

plt.figure()
plt.imshow(im2)

plt.figure()
plt.imshow(im3)

plt.figure()
plt.imshow(im4)

plt.figure()
plt.scatter(rho_vals, theta_vals)
plt.scatter(center[:,0],center[:,1],s = 80,c = 'y', marker = 's')
plt.xlabel("rho")
plt.ylabel("theta")
plt.title("lines parameters")
plt.show()

It's not perfect, there are still some issues, but you get the idea.

2024-07-08
Ophir Carmi