二值图针对内部轮廓腐蚀膨胀

目的:只扩大二值图像内部的黑色孔洞,而不改变白色物体的外部轮廓

1 示例:

复制代码
import cv2
import numpy as np
import matplotlib.pyplot as plt

# --- 解决方案:处理中文乱码 ---
plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定支持中文的字体
plt.rcParams['axes.unicode_minus'] = False    # 修正负号显示问题
# ------------------------------------

# --- 1. 创建一个用于演示的、包含多个孔洞的二值图像 ---
image = np.zeros((300, 300), dtype=np.uint8)
cv2.rectangle(image, (30, 30), (270, 270), 255, -1) # 白色主体区域

# 在中间添加多个不同形状和大小的孔洞
cv2.circle(image, (150, 100), 30, 0, -1)      # 圆形孔洞
cv2.rectangle(image, (80, 180), (140, 220), 0, -1) # 矩形孔洞
cv2.ellipse(image, (220, 190), (40, 20), 0, 0, 360, 0, -1) # 椭圆形孔洞
# --------------------------------------------------

# --- 2. 核心处理逻辑 (无需改动) ---
def enlarge_holes(binary_image):
    """
    将二值图像的内部黑色孔洞扩大一圈,同时保持外部轮廓不变。
    此函数可以处理单个或多个孔洞。
    """
    result = binary_image.copy()
    
    # --- 步骤 1: 提取内部孔洞 ---
    h, w = binary_image.shape
    flood_mask = np.zeros((h + 2, w + 2), np.uint8)
    im_floodfill = result.copy()
    cv2.floodFill(im_floodfill, flood_mask, (0, 0), 255)
    holes_mask = cv2.bitwise_not(im_floodfill)
    
    # --- 步骤 2: 让孔洞"变大一圈" ---
    kernel = np.ones((3, 3), np.uint8)
    dilated_holes_mask = cv2.dilate(holes_mask, kernel, iterations=1)
    
    # --- 步骤 3: 从原图中"减去"变大后的孔洞 ---
    inverted_dilated_mask = cv2.bitwise_not(dilated_holes_mask)
    final_result = cv2.bitwise_and(result, inverted_dilated_mask)
    
    return final_result, holes_mask, dilated_holes_mask

# --- 3. 执行处理并显示结果 ---
final_image, original_holes, dilated_holes = enlarge_holes(image)

# 使用 matplotlib 显示结果,方便对比
plt.figure(figsize=(16, 4))

plt.subplot(1, 4, 1)
plt.title("原始图像 (含多个孔洞)")
plt.imshow(image, cmap='gray')

plt.subplot(1, 4, 2)
plt.title("提取出的所有孔洞")
plt.imshow(original_holes, cmap='gray')

plt.subplot(1, 4, 3)
plt.title("膨胀后的所有孔洞")
plt.imshow(dilated_holes, cmap='gray')

plt.subplot(1, 4, 4)
plt.title("最终结果")
plt.imshow(final_image, cmap='gray')

plt.tight_layout()
plt.show()

2 超大图:内部大量空洞处理

利用 cv2.findContours 及其返回的**层级(Hierarchy)**信息,快速筛选出所有父轮廓不为空的轮廓(即内部孔洞),然后对这些孔洞的蒙版进行膨胀,最后从原图中减去膨胀后的孔洞区域。

复制代码
import cv2
import numpy as np
import time
import os
import random

def enlarge_holes_contours(binary_image):
    """
    【高效】使用轮廓检测法,将图像内部的黑色孔洞扩大一圈。
    """
    # 寻找所有轮廓及其层级关系
    # RETR_TREE能构建完整的层级树,非常适合查找内部轮廓
    contours, hierarchy = cv2.findContours(binary_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    # 创建一个与原图等大的空白蒙版,用于绘制所有识别出的孔洞
    height, width = binary_image.shape
    holes_mask = np.zeros((height, width), dtype=np.uint8)

    # 检查hierarchy是否存在,以避免在没有轮廓时出错
    if hierarchy is not None:
        # 遍历所有轮廓,根据层级信息筛选出孔洞
        # hierarchy[0][i][3] 代表第 i 个轮廓的父轮廓索引。
        # 如果父轮廓存在(即索引不为-1),说明它是一个内部孔洞。
        for i, contour in enumerate(contours):
            if hierarchy[0][i][3] != -1:
                # 在蒙版上将这个孔洞轮廓填充为白色
                cv2.drawContours(holes_mask, [contour], 0, 255, -1)

    # 定义一个3x3的结构元素用于膨胀(扩大一圈)
    kernel = np.ones((3, 3), np.uint8)
    # 对孔洞蒙版进行膨胀操作
    dilated_holes_mask = cv2.dilate(holes_mask, kernel, iterations=1)

    # 使用位运算,从原图中"减去"膨胀后的孔洞区域
    # bitwise_not(dilated_holes_mask) 会得到一个孔洞区域为黑色的蒙版
    final_result = cv2.bitwise_and(binary_image, cv2.bitwise_not(dilated_holes_mask))
    
    return final_result

# --- 主程序 ---
if __name__ == "__main__":
    image_size = 8000
    num_holes_side = 15  # 每行/每列的孔洞数,总共 7x7=49 个
    hole_radius = 30    # 孔洞半径

    # --- 1. 创建5000x5000的测试图像 ---
    print(f"正在创建 {image_size}x{image_size} 的测试图像,包含 {num_holes_side**2} 个半径为 {hole_radius} 的孔洞...")
    large_image = np.zeros((image_size, image_size), dtype=np.uint8)
    
    # 绘制一个大的白色主体区域,留出边框
    cv2.rectangle(large_image, (200, 200), (image_size - 200, image_size - 200), 255, -1)
    
    # 在主体区域内均匀生成几十个孔洞
    step = (image_size - 1000) // num_holes_side
    for i in range(num_holes_side):
        for j in range(num_holes_side):
            center_x = 500 + i * step + random.randint(-step//4, step//4)
            center_y = 500 + j * step + random.randint(-step//4, step//4)
            cv2.circle(large_image, (center_x, center_y), hole_radius, 0, -1)
    
    print("测试图像创建完毕。")
    cv2.imwrite("original_large_uniform_holes.png", large_image)
    print("原始大图已保存为 'original_large_uniform_holes.png'")
    
    # --- 2. 使用高效的轮廓检测法进行处理和计时 ---
    print("\n--- 开始使用 'findContours' 方法处理大图 ---")
    start_time = time.time()
    result_image = enlarge_holes_contours(large_image)
    end_time = time.time()
    elapsed_time = end_time - start_time
    print(f"✅ 'findContours' 方法处理5000x5000图像耗时: {elapsed_time:.4f} 秒")
    
    cv2.imwrite("processed_large_uniform_holes.png", result_image)
    print("处理后的大图已保存为 'processed_large_uniform_holes.png'")

    # --- 3. 生成一个局部区域的视觉对比图 ---
    print("\n--- 正在生成局部区域的对比效果图 ---")
    # 截取图像中包含几个孔洞的区域以便观察
    crop_x, crop_y, crop_w, crop_h = 450, 450, 800, 800
    original_crop = large_image[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]
    processed_crop = result_image[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]

    # 将灰度图转为BGR以添加彩色文字
    original_crop_bgr = cv2.cvtColor(original_crop, cv2.COLOR_GRAY2BGR)
    processed_crop_bgr = cv2.cvtColor(processed_crop, cv2.COLOR_GRAY2BGR)

    # 添加标签
    cv2.putText(original_crop_bgr, "原始图像 (局部)", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)
    cv2.putText(processed_crop_bgr, "处理结果 (局部)", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)

    # 水平拼接图像
    comparison_image = np.hstack([original_crop_bgr, processed_crop_bgr])
    cv2.imwrite("comparison_crop_uniform_holes.png", comparison_image)
    print("局部对比图已保存为 'comparison_crop_uniform_holes.png'")

1. 寻找所有轮廓及其层级
复制代码
contours, hierarchy = cv2.findContours(binary_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
  • cv2.findContours: 这是OpenCV的轮廓查找函数。

  • binary_image: 输入的必须是二值图像(只有黑白两色)。

  • cv2.RETR_TREE: 这是最关键的参数。它告诉函数去检测所有轮廓并重建一个完整的层级树。这棵树描述了轮廓间的嵌套关系。

  • hierarchy: 这是一个数组,存储了所有轮廓的层级信息。它的结构是 [下一个轮廓, 上一个轮廓, 第一个子轮廓, 父轮廓]

2. 筛选出内部孔洞
复制代码
# hierarchy[0][i][3] 代表第 i 个轮廓的父轮廓索引。
# 如果父轮廓存在(即索引不为-1),说明它是一个内部孔洞。
for i, contour in enumerate(contours):
    if hierarchy[0][i][3] != -1:
        # 在蒙版上将这个孔洞轮廓填充为白色
        cv2.drawContours(holes_mask, [contour], 0, 255, -1)
  • 代码遍历找到的每一个轮廓。

  • 对于第 i 个轮廓,它会检查 hierarchy[0][i][3] 的值。这个值代表了该轮廓的父轮廓的索引。

  • 如果一个轮廓有父轮廓 (即这个值不是 -1),就意味着它被另一个轮廓所包围,因此它是一个内部孔洞

  • 一旦确定是孔洞,就使用 cv2.drawContours 将它填充为白色,绘制到一个新建的空白**蒙版 (holes_mask)**上。

执行完这一步,holes_mask 就成了一张只包含所有孔洞形状的图像。

3. 扩大孔洞
复制代码
kernel = np.ones((3, 3), np.uint8)
dilated_holes_mask = cv2.dilate(holes_mask, kernel, iterations=1)
  • 这一步是对上一步生成的孔洞蒙版进行膨胀 (Dilation) 操作。

  • 膨胀会使图像中的白色区域(在这里就是孔洞)向外扩张。使用一个3x3的核心并迭代1次,正好能让每个孔洞的边界向外扩大一圈(1个像素)。

4. 应用回原图
复制代码
final_result = cv2.bitwise_and(binary_image, cv2.bitwise_not(dilated_holes_mask))
  • 这是最后一步,将扩大后的孔洞应用回原始图像。

  • cv2.bitwise_not(dilated_holes_mask): 首先,将膨胀后的孔洞蒙版进行反色 。现在,这张蒙版上,扩大后的孔洞区域是黑色,其他所有地方都是白色。

  • cv2.bitwise_and(...): 然后,将原始图像与这张反色后的蒙版进行**"位与"**操作。这个操作的效果是:

    • 在蒙版为白色的区域(非孔洞区域),保留原始图像的像素。

    • 在蒙版为黑色的区域(扩大后的孔洞区域),结果将变为黑色。

相关推荐
清朝牢弟1 小时前
Ubuntu系统VScode实现opencv(c++)图像像素类型转换和归一化
c++·opencv·ubuntu
AI technophile8 小时前
OpenCV计算机视觉实战(18)——视频处理详解
opencv·计算机视觉·音视频
WSSWWWSSW1 天前
Jupyter Notebook 中高效处理和实时展示来自 OpenCV 和 Pillow 的图像数据探究
opencv·jupyter·pillow
Tony沈哲1 天前
LLM + 图像处理的第一步:用自然语言驱动调色逻辑
opencv·llm
kv18301 天前
opencv解迷宫
人工智能·opencv·计算机视觉·广度优先搜索·图算法
bright_colo1 天前
Python-初学openCV——图像预处理(六)
人工智能·opencv·计算机视觉
清朝牢弟1 天前
Ubuntu系统VScode实现opencv(c++)图像放缩与插值
c++·vscode·opencv·ubuntu·计算机视觉
清朝牢弟1 天前
Ubuntu系统VScode实现opencv(c++)视频的处理与保存
c++·人工智能·vscode·opencv·ubuntu
R-G-B2 天前
【05】OpenCV C#——OpenCvSharp 图像基本操作---转灰度图、边缘提取、兴趣区域ROI,图像叠加
opencv·c#·opencvsharp边缘提取·cvsharp图像基本操作·cvsharp感兴趣区域roi·opencvsharp图像叠加