0

0

OpenCV中动态合并多个轮廓的实用指南

花韻仙語

花韻仙語

发布时间:2025-12-08 12:30:09

|

500人浏览过

|

来源于php中文网

原创

opencv中动态合并多个轮廓的实用指南

本文旨在介绍如何在OpenCV中高效地合并多个轮廓。针对`cv.findContours()`返回的可变数量轮廓,传统循环内直接使用`np.vstack`可能导致数据丢失。教程将详细阐述正确的合并策略,即先将所有待合并轮廓收集到一个列表中,然后利用NumPy的`vstack`函数一次性完成合并操作,从而确保所有轮廓数据被完整整合。

在图像处理任务中,我们经常需要使用OpenCV的cv.findContours()函数来检测图像中的轮廓。该函数返回一个轮廓列表,每个轮廓通常是一个NumPy数组,表示构成该轮廓的所有点。在某些应用场景下,我们可能需要将多个检测到的轮廓合并成一个单一的轮廓数组,例如,当多个轮廓属于同一个逻辑对象,或者需要对一组特定轮廓进行统一处理时。

理解OpenCV中的轮廓数据结构

cv.findContours()函数返回的轮廓通常是一个Python列表,列表中的每个元素都是一个NumPy数组。这些NumPy数组的形状通常是 (N, 1, 2),其中 N 是轮廓中的点数,1 是一个额外的维度(有时可以省略),2 表示每个点的 (x, y) 坐标。

例如,以下代码演示了如何获取并查看轮廓的基本信息:

import cv2 as cv
import numpy as np

# 假设 img 是一张二值图像
# 为了演示,我们创建一个简单的图像
img = np.zeros((100, 100), dtype=np.uint8)
cv.rectangle(img, (10, 10), (30, 30), 255, -1)
cv.circle(img, (70, 70), 20, 255, -1)
cv.rectangle(img, (15, 15), (25, 25), 0, -1) # 制造一个内部轮廓

# 查找轮廓
data_contours, _ = cv.findContours(img, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)

# 对轮廓按长度进行排序(从大到小)
data_cnt = sorted(data_contours, key=len, reverse=True)

# 打印轮廓数量和每个轮廓的长度
data_cnt_len = [len(cnt) for cnt in data_cnt]

print(f"检测到的轮廓总数: {len(data_contours)}")
print(f"排序后轮廓的长度: {data_cnt_len}")
# 示例输出可能为:
# 检测到的轮廓总数: 3
# 排序后轮廓的长度: [100, 80, 4] (具体数值取决于图像和轮廓查找模式)

轮廓合并的常见误区与正确策略

当需要合并多个轮廓时,一个常见的错误是在循环中直接使用np.vstack()。例如,如果目标是合并前两个最大的轮廓:

# 错误的合并尝试
# contour_number = 2  # 假设要合并前两个轮廓
#
# merged_contours_incorrect = None
# for i in range(contour_number):
#     # 每次循环都会创建一个新的 NumPy 数组,并覆盖前一个结果
#     # 这导致最终只保留了循环中最后一个轮廓的数据
#     merged_contours_incorrect = np.vstack(data_cnt[i])
#
# if merged_contours_incorrect is not None:
#     print(f"错误合并后的形状: {merged_contours_incorrect.shape}")
#     # 示例输出可能为: (407, 2) 或 (80, 2) - 只保留了最后一个轮廓的形状

上述代码的问题在于,np.vstack()在每次循环中都会将当前的data_cnt[i]转换成一个NumPy数组(如果它本身不是),并将其赋值给merged_contours_incorrect。这意味着每次迭代都会覆盖前一次的结果,最终merged_contours_incorrect只包含了循环中最后一个轮廓的数据。

Cardify卡片工坊
Cardify卡片工坊

使用Markdown一键生成精美的小红书知识卡片

下载

正确的合并策略是先将所有需要合并的轮廓收集到一个Python列表中,然后再对这个列表使用np.vstack()。np.vstack()函数能够接受一个NumPy数组的序列(如列表或元组),并将它们按垂直方向堆叠起来。

实施正确的轮廓合并

以下是合并多个轮廓的正确方法:

  1. 初始化一个空列表:用于存储所有待合并的轮廓。
  2. 遍历并追加轮廓:在循环中,将每个需要合并的轮廓追加到这个列表中。
  3. 一次性堆叠:循环结束后,使用np.vstack()函数对整个列表进行操作,完成所有轮廓的合并。
# 正确的轮廓合并方法
merged_contours_list = []  # 创建一个空列表来存储轮廓
contour_number_to_merge = 2 # 假设我们要合并前两个最大的轮廓

for i in range(contour_number_to_merge):
    merged_contours_list.append(data_cnt[i])

# 使用 np.vstack 将列表中的所有轮廓一次性堆叠起来
if merged_contours_list: # 确保列表不为空
    final_merged_contour = np.vstack(merged_contours_list)
    print(f"正确合并后的形状: {final_merged_contour.shape}")
    # 示例输出可能为: (180, 1, 2) 或 (180, 2) - 两个轮廓点数的总和
else:
    print("没有轮廓可供合并。")

通过这种方法,final_merged_contour将包含所有被追加到merged_contours_list中的轮廓点数据。其形状的第一个维度将是所有合并轮廓点数的总和。

完整示例

为了提供一个更完整的上下文,我们结合图像创建、轮廓查找、排序和合并的整个流程:

import cv2 as cv
import numpy as np

def merge_selected_contours(image_path, num_to_merge=2):
    """
    加载图像,查找轮廓,排序,并合并指定数量的最大轮廓。

    Args:
        image_path (str): 图像文件路径。
        num_to_merge (int): 要合并的最大轮廓数量。

    Returns:
        np.ndarray: 合并后的轮廓数组,如果无轮廓则返回 None。
    """
    img = cv.imread(image_path, cv.IMREAD_GRAYSCALE)
    if img is None:
        print(f"错误: 无法加载图像 '{image_path}'")
        return None

    # 对图像进行二值化处理,以便 cv.findContours 工作
    _, binary_img = cv.threshold(img, 127, 255, cv.THRESH_BINARY)

    # 查找轮廓
    contours_found, _ = cv.findContours(binary_img, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)

    if not contours_found:
        print("未检测到任何轮廓。")
        return None

    # 按轮廓点数从大到小排序
    sorted_contours = sorted(contours_found, key=len, reverse=True)

    print(f"检测到的轮廓总数: {len(sorted_contours)}")
    print(f"排序后轮廓的长度: {[len(c) for c in sorted_contours]}")

    # 准备合并
    contours_to_merge = []
    # 确保不会尝试合并超过实际存在的轮廓数量
    actual_num_to_merge = min(num_to_merge, len(sorted_contours))

    print(f"将合并前 {actual_num_to_merge} 个轮廓。")

    for i in range(actual_num_to_merge):
        contours_to_merge.append(sorted_contours[i])

    if contours_to_merge:
        final_merged_contour = np.vstack(contours_to_merge)
        print(f"合并后的轮廓形状: {final_merged_contour.shape}")
        return final_merged_contour
    else:
        print("没有轮廓被选中进行合并。")
        return None

# 创建一个虚拟图像文件进行演示
dummy_image_path = "dummy_contours.png"
dummy_img = np.zeros((200, 200), dtype=np.uint8)
cv.rectangle(dummy_img, (20, 20), (80, 80), 255, -1)
cv.circle(dummy_img, (150, 150), 30, 255, -1)
cv.imwrite(dummy_image_path, dummy_img)

# 调用函数进行合并
merged_contour_result = merge_selected_contours(dummy_image_path, num_to_merge=2)

if merged_contour_result is not None:
    # 可以在这里对合并后的轮廓进行进一步处理,例如绘制
    output_img = np.zeros((200, 200, 3), dtype=np.uint8)
    cv.drawContours(output_img, [merged_contour_result], -1, (0, 255, 0), 2)
    cv.imshow("Merged Contours", output_img)
    cv.waitKey(0)
    cv.destroyAllWindows()

注意事项与最佳实践

  1. 数据类型和维度一致性:np.vstack()要求所有待堆叠的数组在除第一个维度(行数)之外的所有维度上都保持一致。OpenCV返回的轮廓通常满足这一条件,即它们的形状都是 (N, 1, 2) 或 (N, 2)。
  2. 效率:对于大量的NumPy数组合并,先将它们收集到一个Python列表中,然后调用一次np.vstack()通常比在循环中反复调用np.vstack()更高效。这是因为后者可能涉及多次内存重新分配和数据复制。
  3. 内存管理:合并非常大的轮廓或大量轮廓可能会占用大量内存。在处理大型图像或复杂轮廓时,请注意内存使用情况。
  4. 轮廓排序:在合并之前对轮廓进行排序(例如按面积、长度或位置)是一个常见的预处理步骤,这取决于你的具体需求。本教程中的示例展示了按轮廓长度排序。
  5. 空轮廓列表处理:在调用np.vstack()之前,最好检查一下用于存储轮廓的列表是否为空,以避免在列表为空时引发错误。

总结

在OpenCV中合并多个轮廓时,核心策略是避免在循环中直接使用np.vstack()。正确的做法是创建一个Python列表来暂存所有需要合并的轮廓,然后在循环结束后,对这个列表调用一次np.vstack()函数。这种方法不仅能确保所有轮廓数据被完整地整合,而且在处理大量轮廓时也更为高效。通过理解轮廓的数据结构和np.vstack()的工作原理,开发者可以更准确、高效地处理图像中的复杂轮廓数据。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

338

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

225

2025.10.31

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

138

2026.02.12

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

550

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

30

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

45

2026.01.06

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

448

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

606

2023.08.10

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

49

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号