How to use Opencv contours to describe line points in a unidirectional way

▼魔方 西西 提交于 2021-02-18 06:40:22

问题


I am using opencvs findContour to find the points to describe an image made up of lines (not polygons) as such: cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);.


回答1:


If I understand correctly, the "cv2.connectedComponents" method gives what you are looking for. It assigns a label for each point in your image, the label is the same if points are connected. By doing this assignment there is no duplication happening. So, if your lines are one pixel wide (e.g output of an edge detector or a thinning operator) you get one point per location.

Edit:

As per the OP request, lines should be 1-pixel wide. To achieve this a thinning operation is applied before finding connected components. Steps images have been added too.

Please note that each connected component points are sorted in ascending order of y cords.

img_path = "D:/_temp/fig.png"
output_dir = 'D:/_temp/'

img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)

_, img = cv2.threshold(img, 128, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)

total_white_pixels = cv2.countNonZero(img)
print ("Total White Pixels Before Thinning = ", total_white_pixels)

cv2.imwrite(output_dir + '1-thresholded.png', img)

#apply thinning -> each line is one-pixel wide
img = cv2.ximgproc.thinning(img)
cv2.imwrite(output_dir + '2-thinned.png', img)

total_white_pixels = cv2.countNonZero(img)
print ("Total White Pixels After Thinning = ", total_white_pixels)

no_ccs, labels = cv2.connectedComponents(img)

label_pnts_dic = {}

colored = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

i = 1 # skip label 0 as it corresponds to the backgground points
sum_of_cc_points = 0 
while i < no_ccs:
    label_pnts_dic[i] = np.where(labels == i) #where return tuple(list of x cords, list of y cords)
    colored[label_pnts_dic[i]] = (random.randint(100, 255), random.randint(100, 255), random.randint(100, 255))
    i +=1

cv2.imwrite(output_dir + '3-colored.png', colored)    


print ("First ten points of label-1 cc: ")
for i in range(10):
    print ("x: ", label_pnts_dic[1][1][i], "y: ", label_pnts_dic[1][0][i])

Output:

Total White Pixels Before Thinning =  6814
Total White Pixels After Thinning =  2065
First ten points of label-1 cc: 
x:  312 y:  104
x:  313 y:  104
x:  314 y:  104
x:  315 y:  104
x:  316 y:  104
x:  317 y:  104
x:  318 y:  104
x:  319 y:  104
x:  320 y:  104
x:  321 y:  104

Images:

1.Thresholded

  1. Thinned

  1. Colored Components

Edit2:

After a discussion with OP, I understood that having a list of (scattered) points is not enough. Points should be ordered so that they could be traced. To achieve that new logic should be introduced after applying thinning to the image.

  1. Find extreme points (points with a single 8-connectivity neighbor)
  2. Find connector points (points with 3-ways connectivity)
  3. Find simple points (all other points)
  4. Start tracing from an extreme point until reaching another extreme point or a connector one.
  5. Extract the traveled path.
  6. Check whether a connector point has turned into a simple point and update its status.
  7. Repeat
  8. Check if there are any closed-loops of simple points that have not been reached from any extreme point, extract each closed-loop as an additional waypoint.

Code for extreme/connector/simple point classification

def filter_neighbors(ns):    
    i = 0
    while i < len(ns):
        j = i + 1
        while j < len(ns):
            if (ns[i][0] == ns[j][0] and abs(ns[i][1] - ns[j][1]) <= 1) or (ns[i][1] == ns[j][1] and abs(ns[i][0] - ns[j][0]) <= 1):
                del ns[j]
                break                                    
            j += 1
        i += 1    

def sort_points_types(pnts):
    extremes = []
    connections = []
    simple = []

    for i in range(pnts.shape[0]):
        neighbors = []
        for j in range (pnts.shape[0]):
            if i == j: continue
            if abs(pnts[i, 0] - pnts[j, 0]) <= 1 and abs(pnts[i, 1] - pnts[j, 1]) <= 1:#8-connectivity check
                neighbors.append(pnts[j])
        filter_neighbors(neighbors)
        if len(neighbors) == 1:
            extremes.append(pnts[i])
        elif len(neighbors) == 2:
            simple.append(pnts[i])
        elif len(neighbors) > 2:
            connections.append(pnts[i])
    return extremes, connections, simple


img_path = "D:/_temp/fig.png"
output_dir = 'D:/_temp/'

img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)

_, img = cv2.threshold(img, 128, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
img = cv2.ximgproc.thinning(img)

pnts = cv2.findNonZero(img)
pnts = np.squeeze(pnts)


ext, conn, simple = sort_points_types(pnts)

for p in conn:
    cv2.circle(img, (p[0], p[1]), 5, 128)

for p in ext:
    cv2.circle(img, (p[0], p[1]), 5, 128)

cv2.imwrite(output_dir + "6-both.png", img)

print (len(ext), len(conn), len(simple))

Edit3:

A much more efficient implementation for classifying the points in a single pass by checking neighbors in a kernel-like way, thanks to eldesgraciado!

Note: Before calling this method the image should be padded with one pixel to avoid border checks or equivalently blackout pixels at the border.

def sort_points_types(pnts, img):
    extremes = []
    connections = []
    simple = []

    for p in pnts:
        x = p[0]
        y = p[1]
        n = []
        if img[y - 1,x] > 0: n.append((y-1, x))
        if img[y - 1,x - 1] > 0: n.append((y-1, x - 1))
        if img[y - 1,x + 1] > 0: n.append((y-1, x + 1))
        if img[y,x - 1] > 0: n.append((y, x - 1))
        if img[y,x + 1] > 0: n.append((y, x + 1))
        if img[y + 1,x] > 0: n.append((y+1, x))
        if img[y + 1,x - 1] > 0: n.append((y+1, x - 1))
        if img[y + 1,x + 1] > 0: n.append((y+1, x + 1))
        filter_neighbors(n)
        if len(n) == 1:
            extremes.append(p)
        elif len(n) == 2:
            simple.append(p)
        elif len(n) > 2:
            connections.append(p)
    return extremes, connections, simple

An image visualizing extreme and connector points:



来源:https://stackoverflow.com/questions/60856650/how-to-use-opencv-contours-to-describe-line-points-in-a-unidirectional-way

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!