0
votes

I'm trying to filter out short lines from my canny edge detection. Here's what I'm currently using as well as a brief explanation:

I start out by taking a single channel of the image and running CV2's Canny edge detection. Following that, I scan through each pixel and detect if there are any around it that are white (True, 255). If it is, I add it to a group of true pixels and then check every pixel around it (and keep looping until there are no white/True pixels left. I then replace all the pixels with black/False if the group count is less than a designated threshold (In this case, 100 pixels).

While this works (as shown below) it's awfully slow. I'm wondering if there's a faster, easier way to do this.

import cv2

img = cv2.imread("edtest.jpg")
img_r = img.copy()
img_r[:, :, 0] = 0
img_r[:, :, 1] = 0
img_r = cv2.GaussianBlur(img_r, (3, 3), 0)
basic_edge = cv2.Canny(img_r, 240, 250)

culled_edge = basic_edge.copy()
min_threshold = 100

for x in range(len(culled_edge)):
    print(x)
    for y in range(len(culled_edge[x])):
        test_pixels = [(x, y)]
        true_pixels = [(x, y)]

        while len(test_pixels) != 0:

            xorigin = test_pixels[0][0]
            yorigin = test_pixels[0][1]
            if 0 < xorigin < len(culled_edge) - 1 and 0 < yorigin < len(culled_edge[0]) - 1:
                for testx in range(3):
                    for testy in range(3):
                        if culled_edge[xorigin-1+testx][yorigin - 1 + testy] == 255 and (xorigin-1+testx, yorigin-1+testy) not in true_pixels:
                            test_pixels.append((xorigin-1+testx, yorigin-1+testy))
                            true_pixels.append((xorigin-1+testx, yorigin-1+testy))
            test_pixels.pop(0)

        if 1 < len(true_pixels) < min_threshold:
            for i in range(len(true_pixels)):
                culled_edge[true_pixels[i][0]][true_pixels[i][1]] = 0

cv2.imshow("basic_edge", basic_edge)
cv2.imshow("culled_edge", culled_edge)
cv2.waitKey(0)

Source Image:

Source Image

Canny Detection and Filtered (Ideal) Results: enter image description here

1
You can get contours of your edges and then filter on contour area or perimeter. You can also use connectedComponentsWithStats() and filter on area.fmw42

1 Answers

2
votes

The operation you are applying is called an area opening. I don't think there is an implementation in OpenCV, but you can find one in either scikit-image (skimage.morphology.area_opening) or DIPlib (dip.BinaryAreaOpening).

For example with DIPlib (disclosure: I'm an author) you'd amend your code as follows:

import diplib as dip

# ...
basic_edge = cv2.Canny(img_r, 240, 250)
min_threshold = 100
culled_edge = dip.BinaryAreaOpening(basic_edge > 0, min_threshold)

The output, culled_edge, is now a dip.Image object, which is compatible with NumPy arrays and you should be able to use it as such in many situations. If there's an issue, then you can cast it back to a NumPy array with culled_edge = np.array(culled_edge).