[OpenCV image implementation: using OpenCV for object contour sorting]

overview

In image processing, it is often necessary to perform operations related to object contours, such as calculating the perimeter and area of ​​the target contour. In order to obtain information about the target contour, OpenCV's findContours function is usually used. However, once the contour information is obtained, you may find that the order of the contours is disordered, as shown on the left side of the figure below:
Insert image description here

In this diagram, each contour is found, but their order is scrambled, which makes accurate measurement and analysis of each target difficult. To solve this problem, we need to sort the contours for easier subsequent processing.
Insert image description here

read image

Start reading the image and generating its edge detection map.

import cv2
import numpy as np

# 读取图像
image = cv2.imread('img_4.png')

# 初始化累积边缘图
accumEdged = np.zeros(image.shape[:2], dtype='uint8')

# 对每个通道进行边缘检测
for chan in cv2.split(image):
    chan = cv2.medianBlur(chan, 11)
    edged = cv2.Canny(chan, 50, 200)
    accumEdged = cv2.bitwise_or(accumEdged, edged)

# 显示边缘检测图
cv2.imshow('Edge Map', accumEdged)
cv2.waitKey(0)
cv2.destroyAllWindows()

Insert image description here

Get outline

The API for finding image contours in opencv-python is: findContours function, which receives a binary image as input and can output the outer contour, inner and outer contours of the object, etc.

import cv2
import numpy as np

# 读取图像
image = cv2.imread('img_4.png')

# 初始化累积边缘图
accumEdged = np.zeros(image.shape[:2], dtype='uint8')

# 对每个通道进行边缘检测
for chan in cv2.split(image):
    chan = cv2.medianBlur(chan, 11)
    edged = cv2.Canny(chan, 50, 200)
    accumEdged = cv2.bitwise_or(accumEdged, edged)

# 显示边缘检测图
cv2.imshow('Edge Map', accumEdged)

# 寻找图像轮廓
cnts, _ = cv2.findContours(accumEdged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5]

# 复制原始图像
orig = image.copy()

# 对未排序的轮廓进行可视化
for (i, c) in enumerate(cnts):
    orig = cv2.drawContours(orig, [c], -1, (0, 255, 0), 2)
    cv2.putText(orig, f'Contour #{i+1}', (10, 30*(i+1)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

# 显示未排序的轮廓可视化结果
cv2.imshow('Unsorted Contours', orig)
cv2.imwrite("./Unsorted_Contours.jpg", orig)

cv2.waitKey(0)
cv2.destroyAllWindows()

Insert image description here
In different versions of OpenCV, the return value form of the cv2.findContours function is different. In OpenCV 2.X version, the function returns two values, while in OpenCV 3+ version, it returns three values. In order to adapt to these two versions, you can implement a function called grab_contours to select the correct contour return position according to different versions. Here is the code for the function:

def grab_contours(cnts):
    # 如果 cv2.findContours 返回的轮廓元组长度为 '2',则表示使用的是 OpenCV v2.4、v4-beta 或 v4-official
    if len(cnts) == 2:
        cnts = cnts[0]
    # 如果轮廓元组长度为 '3',则表示使用的是 OpenCV v3、v4-pre 或 v4-alpha
    elif len(cnts) == 3:
        cnts = cnts[1]

    return cnts

This function simply checks the length of the returned contour tuple and selects the correct contour return position based on the length. By using this function, you can ensure that the code works correctly in different versions of OpenCV.

Contour sorting

Through the above steps, the contours of all objects in the image are obtained. Next, the function sort_contours is defined to sort the contours. This function accepts the method parameter to sort the contours in different orders, such as from left to right. Or from right to left.

import cv2
import numpy as np

def sort_contours(cnts, method='left-to-right'):
    reverse = False
    i = 0
    if method == 'right-to-left' or method == 'bottom-to-top':
        reverse = True
    if method == 'bottom-to-top' or method == 'top-to-bottom':
        i = 1

    boundingBoxes = [cv2.boundingRect(c) for c in cnts]
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))
    return (cnts, boundingBoxes)

def draw_contour(image, c, i):
    M = cv2.moments(c)
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])
    cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
    cv2.putText(image, f'#{i+1}', (cX - 20, cY), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
    return image

# 读取图像
image = cv2.imread('img_4.png')

# 初始化累积边缘图
accumEdged = np.zeros(image.shape[:2], dtype='uint8')

# 对每个通道进行边缘检测
for chan in cv2.split(image):
    chan = cv2.medianBlur(chan, 11)
    edged = cv2.Canny(chan, 50, 200)
    accumEdged = cv2.bitwise_or(accumEdged, edged)

# 显示边缘检测图
cv2.imshow('Edge Map', accumEdged)

# 寻找图像轮廓
cnts, _ = cv2.findContours(accumEdged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5]

# 复制原始图像
orig = image.copy()

# 对未排序的轮廓进行可视化
for (i, c) in enumerate(cnts):
    orig = draw_contour(orig, c, i)
cv2.imshow('Unsorted Contours', orig)

# 轮廓排序
(cnts, boundingboxes) = sort_contours(cnts, method='left-to-right')

# 对排序后的轮廓进行可视化
for (i, c) in enumerate(cnts):
    image = draw_contour(image, c, i)
cv2.imshow('Sorted Contours', image)

cv2.waitKey(0)
cv2.destroyAllWindows()

Insert image description here
The following is an explanation of the function sort_contours:

def sort_contours(cnts, method='left-to-right'):
    # 初始化反转标志和排序索引
    reverse = False
    i = 0
    # 处理反向排序
    if method == 'right-to-left' or method == 'bottom-to-top':
        reverse = True
    # 如果按照 y 而不是 x 对外接框进行排序
    if method == 'bottom-to-top' or method == 'top-to-bottom':
        i = 1

    # 获取轮廓的外接矩形框
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]
    # 使用 lambda 函数按照指定的坐标轴对外接框进行排序
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))
    return (cnts, boundingBoxes)

The function accepts a list of contours cnts and a sorting method method as arguments. The inversion flag and sort index are initialized first, setting these flags according to the specified sort method. Then, use a list comprehension and the cv2.boundingRect function to get the bounding rectangle of each contour. Finally, sort based on the x or y coordinate of the bounding box by using the sorted function and the zip function.

In the sorted results, cnts stores the outlines sorted according to the specified method, and boundingBoxes stores the corresponding bounding rectangular boxes. These two results are returned to the calling function as a tuple.

In the main calling part, the code is as follows:

# 使用 sort_contours 函数对轮廓进行排序
(cnts, boundingboxes) = sort_contours(cnts, method=args['method'])
# 遍历排序后的轮廓并绘制
for (i, c) in enumerate(cnts):
    image = draw_contour(image, c, i)
# 显示排序后的结果
cv2.imshow('Sorted', image)
cv2.waitKey(0)

Here, the sort_contours function is called and the returned sorted contours are stored in cnts. Then, draw by looping through these sorted contours and calling the draw_contour function, and finally use cv2.imshow to display the sorted results.

summary

边缘检测:
    通过中值模糊和Canny边缘检测对图像进行处理,生成累积边缘图。

轮廓查找:
    使用 cv2.findContours 函数找到累积边缘图中的轮廓,并按面积降序排序。

未排序轮廓可视化:
    将未排序的轮廓绘制在原始图像上,并标注轮廓编号。

轮廓排序函数 sort_contours:
    实现根据指定方法对轮廓进行排序,可选择从左到右、从右到左、从上到下或从下到上排序。

轮廓排序和可视化:
    使用排序函数对轮廓进行排序,然后将排序后的轮廓绘制在原始图像上,同时标注轮廓编号。

Guess you like

Origin blog.csdn.net/weixin_47869094/article/details/134539658