Python project practice: region growing algorithm based on 2D or 3D

1. Introduction

The region growing algorithm is a method for image segmentation that merges adjacent pixels into a region based on a certain similarity.

Steps:
(1) Initialization: Select a seed pixel point (or multiple seed points) as the starting point and mark it as a new area.
(2) Growth criterion: Define the growth criterion, which is used to decide whether to merge adjacent pixels into the current area. The growth criterion is usually based on the similarity of attributes such as intensity values, color, texture, etc. of pixels. If adjacent pixels satisfy the growth criterion, they will be merged into the current region.
(3) Growth process: Starting from the seed point, by continuously checking adjacent pixels, the area is gradually expanded according to the growth criterion. This can be achieved using data structures such as recursion, queues or stacks.
(4) Labeling and classification: Each pixel is assigned a region label to indicate which region it belongs to. This is usually done during the growth process.
(5) Termination condition: Define when to stop growing. This can be determined based on the attributes of the pixel, the size of the area, the number of growth times, etc.

Region growing algorithms usually have some adjustable parameters that affect the performance and results of the algorithm. Here are some common tunable parameters and their descriptions:

  • 种子点(Seed Point):The seed point is the starting position for region growth. Choosing appropriate seed points is very important to obtain the desired area
  • 容忍度(Tolerance):Tolerance (i.e. the range of adjacent pixels) represents the range of allowed differences between two pixel values. The larger the tolerance value, the greater the differences allowed and the wider the zone growth range. The smaller the tolerance value, the closer the growing area is to the pixel value of the seed point.
  • 连接条件(Connectivity): Defines which neighborhood pixels are considered part of the connection.
    In 2D images :
    (1) Use 4 connections (the neighborhood of a pixel includes four pixels up, down, left, and right)
    (2) Use 8 connections (the neighborhood of one pixel includes four pixels up, down, left, and right and four diagonal directions) pixel)
    in 3D images :
    (1) Use 6 connections (the neighborhood of a pixel includes its six adjacent pixels above, below, left, front and back)
    (2) Use 26 connections (the neighborhood of a pixel includes its upper, lower, left, front and back and eight pixels in the diagonal direction)
  • 最大连接区域大小(Max Region Size): Limits the size of region growth to prevent unlimited growth.
  • 像素相似度度量(Pixel Similarity Metric): Determines the measurement method of similarity between pixels. This is usually measured by the grayscale difference between pixel values , but other measures can be used. Such as: color similarity, texture similarity, etc.

2. Project actual combat

2.1, 2D image (10x10)

Insert image description here

import numpy as np
import matplotlib.pyplot as plt

"""
区域增长算法:2D图像中的4连接条件 + 8连接条件
"""


def region_growing_4connect(image, seed, tolerance=1, max_region_size=None):
    """在2D图像中,4连接条件"""
    region = np.zeros_like(image, dtype=bool)
    height, width = image.shape
    stack = [(seed[0], seed[1])]

    while stack:
        y, x = stack.pop()
        if not (0 <= x < width and 0 <= y < height):
            continue
        if region[y, x]:
            continue
        if max_region_size is not None and np.sum(region) >= max_region_size:
            continue
        ###########################################################################
        """
        若添加该条件:其会将多个被独立区域完全分割,导致区域增长无法与周边独立区域进行连接。
        若取消该条件:则将两个独立区域连接的同时,中间的黑色区域也会同时连接。      

        备注:当像素值与背景值差异越小则影响越大,反之差异越大则影响越小。
        备注:被黑色包围的区域认定为 "独立区域"
        备注:区域增长最终结果必定是一个独立的整体
        """
        if image[y, x] == 0:  # 如果当前像素值为0(不连接黑色块),则跳过
            continue
        ###########################################################################
        if abs(image[seed[0], seed[1]] - image[y, x]) <= tolerance:
            region[y, x] = 1
            stack.extend([(y - 1, x), (y + 1, x), (y, x - 1), (y, x + 1)])

    return region


def region_growing_8connect(image, seed, tolerance=1, max_region_size=None):
    """在2D图像中,8连接条件"""
    region = np.zeros_like(image, dtype=bool)
    height, width = image.shape
    stack = [(seed[0], seed[1])]

    while stack:
        y, x = stack.pop()
        if not (0 <= x < width and 0 <= y < height):
            continue
        if region[y, x]:
            continue
        if max_region_size is not None and np.sum(region) >= max_region_size:
            continue
        ###########################################################################
        """
        若添加该条件:其会将多个被独立区域完全分割,导致区域增长无法与周边独立区域进行连接。
        若取消该条件:则将两个独立区域连接的同时,中间的黑色区域也会同时连接。      

        备注:当像素值与背景值差异越小则影响越大,反之差异越大则影响越小。
        备注:被黑色包围的区域认定为 "独立区域"
        备注:区域增长最终结果必定是一个独立的整体
        """
        if image[y, x] == 0:  # 如果当前像素值为0(不连接黑色块),则跳过
            continue
        ###########################################################################
        if abs(image[seed[0], seed[1]] - image[y, x]) <= tolerance:
            region[y, x] = 1
            stack.extend([(y - 1, x), (y + 1, x), (y, x - 1), (y, x + 1),
                          (y - 1, x - 1), (y - 1, x + 1), (y + 1, x - 1), (y + 1, x + 1)])

    return region


if __name__ == "__main__":

    # (1)灰度图像数据
    image = np.array([[2, 3, 2, 2, 4],
                      [2, 0, 0, 0, 4],
                      [0, 0, 4, 0, 2],
                      [2, 0, 0, 0, 0],
                      [0, 2, 2, 2, 2]])

    # (2)选择种子点并执行区域增长
    seed_point = (0, 0)  # 选择3D种子点
    tolerance_value = 2  # 设置容忍度,即相邻像素的范围
    max_region_size = 25  # 设置最大区域大小
    connect_condition = 4  # 设置连接条件
    result_region = region_growing_4connect(image, seed_point, tolerance=tolerance_value, max_region_size=max_region_size)

    # (3)可视化显示3D原始图像和区域增长的结果(按像素值进行可视化)
    plt.figure(figsize=(10, 5))
    plt.subplot(1, 2, 1)
    plt.imshow(image, cmap='gray', origin='upper')
    plt.title('Original Image')
    plt.subplot(1, 2, 2)
    plt.imshow(result_region, cmap='gray', origin='upper')
    plt.title(f'Region Grown from Seed (Tolerance={
      
      tolerance_value})')
    plt.tight_layout()
    plt.show()

2.2, 2D image (100x100)

Insert image description here

import numpy as np
import matplotlib.pyplot as plt

"""
区域增长算法:2D图像中的4连接条件 + 8连接条件
"""


def region_growing_4connect(image, seed, tolerance=1, max_region_size=None):
    """在2D图像中,4连接条件"""
    region = np.zeros_like(image, dtype=bool)
    height, width = image.shape
    stack = [(seed[0], seed[1])]

    while stack:
        y, x = stack.pop()
        if not (0 <= x < width and 0 <= y < height):
            continue
        if region[y, x]:
            continue
        if max_region_size is not None and np.sum(region) >= max_region_size:
            continue
        ###########################################################################
        """
        若添加该条件:其会将多个被独立区域完全分割,导致区域增长无法与周边独立区域进行连接。
        若取消该条件:则将两个独立区域连接的同时,中间的黑色区域也会同时连接。      

        备注:当像素值与背景值差异越小则影响越大,反之差异越大则影响越小。
        备注:被黑色包围的区域认定为 "独立区域"
        备注:区域增长最终结果必定是一个独立的整体
        """
        if image[y, x] == 0:  # 如果当前像素值为0(不连接黑色块),则跳过
            continue
        ###########################################################################
        if abs(image[seed[0], seed[1]] - image[y, x]) <= tolerance:
            region[y, x] = 1
            stack.extend([(y - 1, x), (y + 1, x), (y, x - 1), (y, x + 1)])

    return region


def region_growing_8connect(image, seed, tolerance=1, max_region_size=None):
    """在2D图像中,8连接条件"""
    region = np.zeros_like(image, dtype=bool)
    height, width = image.shape
    stack = [(seed[0], seed[1])]

    while stack:
        y, x = stack.pop()
        if not (0 <= x < width and 0 <= y < height):
            continue
        if region[y, x]:
            continue
        if max_region_size is not None and np.sum(region) >= max_region_size:
            continue
        ###########################################################################
        """
        若添加该条件:其会将多个被独立区域完全分割,导致区域增长无法与周边独立区域进行连接。
        若取消该条件:则将两个独立区域连接的同时,中间的黑色区域也会同时连接。      

        备注:当像素值与背景值差异越小则影响越大,反之差异越大则影响越小。
        备注:被黑色包围的区域认定为 "独立区域"
        备注:区域增长最终结果必定是一个独立的整体
        """
        if image[y, x] == 0:  # 如果当前像素值为0(不连接黑色块),则跳过
            continue
        ###########################################################################
        if abs(image[seed[0], seed[1]] - image[y, x]) <= tolerance:
            region[y, x] = 1
            stack.extend([(y - 1, x), (y + 1, x), (y, x - 1), (y, x + 1),
                          (y - 1, x - 1), (y - 1, x + 1), (y + 1, x - 1), (y + 1, x + 1)])

    return region


if __name__ == "__main__":
    ######################################################################
    # (1.1)原始数组大小
    original_array = np.array([[2, 3, 2, 2, 4],
                               [2, 0, 0, 0, 4],
                               [0, 0, 4, 0, 2],
                               [2, 0, 0, 0, 0],
                               [0, 2, 2, 2, 2]])

    # (1.2)目标数组大小
    target_size = (100, 100)  # 目标大小
    x_scale = target_size[1] / original_array.shape[1]  # 计算x轴方向的缩放因子
    y_scale = target_size[0] / original_array.shape[0]  # 计算y轴方向的缩放因子
    target_array = np.zeros(target_size, dtype=original_array.dtype)  # 创建目标数组

    # (1.3)使用双线性插值填充目标数组
    for y in range(target_size[0]):
        for x in range(target_size[1]):
            # 3.1、计算在原始数组中的坐标
            original_x = x / x_scale
            original_y = y / y_scale
            # 3.2、四个最近的原始数组中的点
            x1, y1 = int(original_x), int(original_y)
            x2, y2 = x1 + 1, y1 + 1
            # 3.3、边界检查
            x1 = min(max(x1, 0), original_array.shape[1] - 1)
            x2 = min(max(x2, 0), original_array.shape[1] - 1)
            y1 = min(max(y1, 0), original_array.shape[0] - 1)
            y2 = min(max(y2, 0), original_array.shape[0] - 1)
            # 3.4、双线性插值
            fx = original_x - x1
            fy = original_y - y1
            target_array[y, x] = (1 - fx) * (1 - fy) * original_array[y1, x1] + fx * (1 - fy) * original_array[y1, x2] + \
                                 (1 - fx) * fy * original_array[y2, x1] + fx * fy * original_array[y2, x2]

    image = target_array
    ######################################################################
    # (2)选择种子点并执行区域增长
    seed_point = (0, 0)  # 选择3D种子点
    tolerance_value = 2  # 设置容忍度,即相邻像素的范围
    max_region_size = 10000  # 设置最大区域大小
    connect_condition = 4  # 设置连接条件
    result_region = region_growing_4connect(image, seed_point, tolerance=tolerance_value, max_region_size=max_region_size)

    # (3)可视化显示3D原始图像和区域增长的结果(按像素值进行可视化)
    plt.figure(figsize=(10, 5))
    plt.subplot(1, 2, 1)
    plt.imshow(image, cmap='gray', origin='upper')
    plt.title('Original Image')
    plt.subplot(1, 2, 2)
    plt.imshow(result_region, cmap='gray', origin='upper')
    plt.title(f'Region Grown from Seed (Tolerance={
      
      tolerance_value})')
    plt.tight_layout()
    plt.show()

2.3. 3D image (10x10x10)

Insert image description here

import numpy as np
import matplotlib.pyplot as plt

"""
区域增长算法:3D图像中的6连接条件 + 26连接条件
"""


def region_growing_6connect(image, seed, tolerance=1, max_region_size=None):
    """在3D图像中,6连接条件"""
    region = np.zeros_like(image, dtype=bool)
    depth, height, width = image.shape
    stack = [(seed[0], seed[1], seed[2])]

    while stack:
        z, y, x = stack.pop()
        if not (0 <= x < width and 0 <= y < height and 0 <= z < depth):
            continue
        if region[z, y, x]:
            continue
        if max_region_size is not None and np.sum(region) >= max_region_size:
            continue
        ###########################################################################
        """
        若添加该条件:其会将多个被独立区域完全分割,导致区域增长无法与周边独立区域进行连接。
        若取消该条件:则将两个独立区域连接的同时,中间的黑色区域也会同时连接。      

        备注:当像素值与背景值差异越小则影响越大,反之差异越大则影响越小。
        备注:被黑色包围的区域认定为 "独立区域"
        备注:区域增长最终结果必定是一个独立的整体
        """
        if image[z, y, x] == 0:  # 如果当前像素值为0(背景),则跳过
            continue
        ###########################################################################
        if abs(image[seed[0], seed[1], seed[2]] - image[z, y, x]) <= tolerance:
            region[z, y, x] = 1
            stack.extend([(z - 1, y, x), (z + 1, y, x), (z, y - 1, x), (z, y + 1, x), (z, y, x - 1), (z, y, x + 1)])
    return region


def region_growing_26connect(image, seed, tolerance=1, max_region_size=None):
    """在3D图像中,26连接条件"""
    region = np.zeros_like(image, dtype=bool)
    depth, height, width = image.shape
    stack = [(seed[0], seed[1], seed[2])]

    while stack:
        z, y, x = stack.pop()
        if not (0 <= x < width and 0 <= y < height and 0 <= z < depth):
            continue
        if region[z, y, x]:
            continue
        if max_region_size is not None and np.sum(region) >= max_region_size:
            continue
        ###########################################################################
        """
        若添加该条件:其会将多个被独立区域完全分割,导致区域增长无法与周边独立区域进行连接。
        若取消该条件:则将两个独立区域连接的同时,中间的黑色区域也会同时连接。      

        备注:当像素值与背景值差异越小则影响越大,反之差异越大则影响越小。
        备注:被黑色包围的区域认定为 "独立区域"
        备注:区域增长最终结果必定是一个独立的整体
        """
        if image[z, y, x] == 0:  # 如果当前像素值为0(背景),则跳过
            continue
        ###########################################################################
        if abs(image[seed[0], seed[1], seed[2]] - image[z, y, x]) <= tolerance:
            region[z, y, x] = 1
            stack.extend([(z - 1, y, x), (z + 1, y, x), (z, y - 1, x), (z, y + 1, x), (z, y, x - 1), (z, y, x + 1),
                          (z - 1, y - 1, x), (z - 1, y + 1, x), (z + 1, y - 1, x), (z + 1, y + 1, x),
                          (z - 1, y, x - 1), (z - 1, y, x + 1), (z + 1, y, x - 1), (z + 1, y, x + 1),
                          (z, y - 1, x - 1), (z, y - 1, x + 1), (z, y + 1, x - 1), (z, y + 1, x + 1),
                          (z - 1, y - 1, x - 1), (z - 1, y - 1, x + 1), (z - 1, y + 1, x - 1), (z - 1, y + 1, x + 1),
                          (z + 1, y - 1, x - 1), (z + 1, y - 1, x + 1), (z + 1, y + 1, x - 1), (z + 1, y + 1, x + 1)])
    return region


if __name__ == "__main__":

    # (1)创建一个典型的3D图像数据
    depth, height, width = 10, 10, 10
    image3D = np.zeros((depth, height, width), dtype=int)
    image3D[1:3, 1:3, 1:3] = 1
    image3D[2:4, 2:4, 2:4] = 2
    image3D[5:8, 5:8, 5:8] = 4
    image3D[7:9, 7:9, 7:9] = 6
    image3D[7:9, 1:3, 1:3] = 8

    # (2)选择种子点并执行区域增长
    seed_point = (6, 6, 6)  # 选择3D种子点
    tolerance_value = 100  # 设置容忍度,即相邻像素的范围
    max_region_size = 15  # 设置最大区域大小
    connect_condition = 26  # 设置连接条件
    result_region = region_growing_26connect(image3D, seed_point, tolerance=tolerance_value, max_region_size=max_region_size)

    # (3)可视化显示3D原始图像和区域增长的结果(按像素值进行可视化)
    fig = plt.figure(figsize=(12, 5))
    # (3.1)原始3D图像
    ax1 = fig.add_subplot(121, projection='3d')
    x, y, z = np.where(image3D > 0)
    ax1.scatter(x, y, z, c=image3D[x, y, z], cmap='viridis')  # 点状图
    ax1.set_title('Original 3D Image')
    ax1.set_xlim(0, 10)  # 设置X轴范围
    ax1.set_ylim(0, 10)  # 设置Y轴范围
    ax1.set_zlim(0, 10)  # 设置Z轴范围
    # (3.2)区域增长结果,使用第一张图的颜色映射
    from matplotlib.colors import Normalize
    ax2 = fig.add_subplot(122, projection='3d')
    x, y, z = np.where(result_region)
    cmap = plt.get_cmap('viridis')
    norm = Normalize(vmin=np.min(image3D), vmax=np.max(image3D))
    ax2.scatter(x, y, z, c=cmap(norm(image3D[x, y, z])), alpha=0.5)  # 点状图
    ax2.set_title(f'Region Grown from Seed'
                  f'\n [seed_point={
      
      seed_point}, Tolerance={
      
      tolerance_value}, max_region={
      
      max_region_size}, connect={
      
      connect_condition}]')
    ax2.set_xlim(0, 10)  # 设置X轴范围
    ax2.set_ylim(0, 10)  # 设置Y轴范围
    ax2.set_zlim(0, 10)  # 设置Z轴范围

    plt.tight_layout()
    plt.show()

Guess you like

Origin blog.csdn.net/shinuone/article/details/133014004