opencv 轮廓合并
本文将指导你将多个轮廓合并为一个轮廓,主要操作逻辑为:寻找各轮廓之间的最短距离,并按最短距离对应的点进行轮廓拼接,以达到轮廓合并的效果。
以下为运算处理的结果
实现步骤
opencv的导入
pip install opencv-python
或者
pip install opencv-python-headless
加载图像与轮廓
加载包括多个轮廓的图像
check_piece_image = cv2.imread("check_piece_image.jpg")
cv2.imshow("check_piece_image", check_piece_image)
做灰度图处理
check_piece_gray_image = cv2.cvtColor(check_piece_image, cv2.COLOR_BGR2GRAY)
cv2.imshow("check_piece_gray_image", check_piece_gray_image)
因为图像是白底黑字,则需要按位取反,如果你们不是,那么这步可以忽略
check_piece_bitwise_not_image = cv2.bitwise_not(check_piece_gray_image)
cv2.imshow("check_piece_bitwise_not_image", check_piece_bitwise_not_image)
阙值化处理
# _, check_piece_thresh_image = cv2.threshold(check_piece_gray_image, 100, 255, cv2.THRESH_OTSU)
_, check_piece_thresh_image = cv2.threshold(check_piece_bitwise_not_image, 100, 255, cv2.THRESH_OTSU)
cv2.imshow("check_piece_thresh_image", check_piece_thresh_image)
寻找所有的轮廓
check_piece_contours, _ = cv2.findContours(check_piece_thresh_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
check_piece_contours_image = check_piece_image.copy()
cv2.drawContours(check_piece_contours_image, check_piece_contours, -1, (0, 255, 0), 2)
cv2.imshow("check_piece_contours_image", check_piece_contours_image)
对轮廓进行合并
合并轮廓 寻找最小距离并合并为一个轮廓
new_merged_check_piece_contours = merge_contours(check_piece_contours)
check_piece_contours_image_by_merged = check_piece_image.copy()
cv2.drawContours(check_piece_contours_image_by_merged, new_merged_check_piece_contours, -1, (0, 255, 0), 2)
cv2.imshow("check_piece_contours_image_by_merged", check_piece_contours_image_by_merged)
核心方法
def merge_contours(contours: typing.Sequence[cv2.typing.MatLike]) -> typing.Sequence[cv2.typing.MatLike]:
"""合并轮廓 寻找最小距离并合并为一个轮廓
Args:
contours (typing.Sequence[cv2.typing.MatLike]): 轮廓
Returns:
typing.Sequence[cv2.typing.MatLike]: 合并后的轮廓
"""
contours = list(contours).copy()
if len(contours) <= 1:
return contours
min_distance = float('inf')
closest_i = None
closest_j = None
new_contour = None
for i in range(len(contours)):
for j in range(i + 1, len(contours)):
candidate_contour, distance = merge_two_contour_to_one(contours[i], contours[j])
if distance < min_distance:
new_contour, min_distance, closest_i, closest_j = candidate_contour, distance, i, j
contours[closest_i] = new_contour
del contours[closest_j]
return merge_contours(contours)
def merge_two_contour_to_one(contour1: cv2.typing.MatLike, contour2: cv2.typing.MatLike) -> tuple[cv2.typing.MatLike, float]:
min_distance, closest_i, closest_j = float('inf'), None, None
for i, point1 in enumerate(contour1):
for j, point2 in enumerate(contour2):
distance = np.linalg.norm(point1[0] - point2[0], 2, None, False)
if distance < min_distance:
min_distance, closest_i, closest_j = distance, i, j
rearranged_contour1 = np.vstack((contour1[closest_i:], contour1[:closest_i]))
rearranged_contour2 = np.vstack((contour2[closest_j:], contour2[:closest_j]))
return np.vstack((rearranged_contour1, rearranged_contour2)), min_distance
全部代码
所有代码以及所有过程截图
import cv2
from cv2.typing import MatLike
import typing
import numpy as np
def merge_contours(contours: typing.Sequence[cv2.typing.MatLike]) -> typing.Sequence[cv2.typing.MatLike]:
"""合并轮廓 寻找最小距离并合并为一个轮廓
Args:
contours (typing.Sequence[cv2.typing.MatLike]): 轮廓
Returns:
typing.Sequence[cv2.typing.MatLike]: 合并后的轮廓
"""
contours = list(contours).copy()
if len(contours) <= 1:
return contours
min_distance = float('inf')
closest_i = None
closest_j = None
new_contour = None
for i in range(len(contours)):
for j in range(i + 1, len(contours)):
candidate_contour, distance = merge_two_contour_to_one(contours[i], contours[j])
if distance < min_distance:
new_contour, min_distance, closest_i, closest_j = candidate_contour, distance, i, j
contours[closest_i] = new_contour
del contours[closest_j]
return merge_contours(contours)
def merge_two_contour_to_one(contour1: cv2.typing.MatLike, contour2: cv2.typing.MatLike) -> tuple[cv2.typing.MatLike, float]:
min_distance, closest_i, closest_j = float('inf'), None, None
for i, point1 in enumerate(contour1):
for j, point2 in enumerate(contour2):
distance = np.linalg.norm(point1[0] - point2[0], 2, None, False)
if distance < min_distance:
min_distance, closest_i, closest_j = distance, i, j
rearranged_contour1 = np.vstack((contour1[closest_i:], contour1[:closest_i]))
rearranged_contour2 = np.vstack((contour2[closest_j:], contour2[:closest_j]))
return np.vstack((rearranged_contour1, rearranged_contour2)), min_distance
if __name__ == "__main__":
check_piece_image = cv2.imread("D:/wzj/merge-contours/check_piece_image.jpg")
cv2.imshow("check_piece_image", check_piece_image)
check_piece_gray_image = cv2.cvtColor(check_piece_image, cv2.COLOR_BGR2GRAY)
cv2.imshow("check_piece_gray_image", check_piece_gray_image)
check_piece_bitwise_not_image = cv2.bitwise_not(check_piece_gray_image)
cv2.imshow("check_piece_bitwise_not_image", check_piece_bitwise_not_image)
# _, check_piece_thresh_image = cv2.threshold(check_piece_gray_image, 100, 255, cv2.THRESH_OTSU)
_, check_piece_thresh_image = cv2.threshold(check_piece_bitwise_not_image, 100, 255, cv2.THRESH_OTSU)
cv2.imshow("check_piece_thresh_image", check_piece_thresh_image)
check_piece_contours, _ = cv2.findContours(check_piece_thresh_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
check_piece_contours_image = check_piece_image.copy()
cv2.drawContours(check_piece_contours_image, check_piece_contours, -1, (0, 255, 0), 2)
cv2.imshow("check_piece_contours_image", check_piece_contours_image)
new_merged_check_piece_contours = merge_contours(check_piece_contours)
check_piece_contours_image_by_merged = check_piece_image.copy()
cv2.drawContours(check_piece_contours_image_by_merged, new_merged_check_piece_contours, -1, (0, 255, 0), 2)
cv2.imshow("check_piece_contours_image_by_merged", check_piece_contours_image_by_merged)
cv2.waitKey(0)
题外话
轮廓的合并,我有尝试其他,例如最初直接堆叠,发现效果并不好,而且无法控制轮廓的顺序;后面又考虑近似轮廓以及凸包,但关于它的一些扩展并不理想就是了;如果你们对轮廓合并有些其他的想法,可以在下面的评论中指出,欢迎指导
这里贴一些其他轮廓处理的截图
直接堆叠
all_check_piece_contours = np.vstack((check_piece_contours))
check_piece_contours_image_0 = check_piece_image.copy()
cv2.drawContours(check_piece_contours_image_0, [all_check_piece_contours], -1, (0, 255, 0), 2)
cv2.imshow("check_piece_contours_image_0", check_piece_contours_image_0)
近似轮廓
epsilon = 0.01 * cv2.arcLength(all_check_piece_contours, True)
approx_contour = cv2.approxPolyDP(all_check_piece_contours, epsilon, True)
check_piece_contours_image_1 = check_piece_image.copy()
cv2.drawContours(check_piece_contours_image_1, [approx_contour], -1, (0, 255, 0), 2)
cv2.imshow("check_piece_contours_image_1", check_piece_contours_image_1)
凸包处理的轮廓
hull = cv2.convexHull(all_check_piece_contours)
check_piece_contours_image_2 = check_piece_image.copy()
cv2.drawContours(check_piece_contours_image_2, [hull], -1, (0, 255, 0), 2)
cv2.imshow("check_piece_contours_image_2", check_piece_contours_image_2)