连通区域(Connected Component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域。连通区域分析是指将图像中的各个连通区域找出并标记,通常连通区域分析处理的对象是一张二值化后的图像,有四邻域和八邻域之分。

1. Two-Pass算法
两遍扫描法( Two-Pass ),正如其名,指的就是通过扫描两遍图像,将图像中存在的所有连通域找出并标记。
第一次扫描:
• 从左上角开始遍历像素点,找到第一个像素为255的点,label=1;
• 当该像素的左邻像素和上邻像素为无效值时,给该像素置一个新的label值,label ++,记录集合;
• 当该像素的左邻像素或者上邻像素有一个为有效值时,将有效值像素的label赋给该像素的label值;
• 当该像素的左邻像素和上邻像素都为有效值时,选取
其中较小的label值赋给该像素的label值;
第二次扫描:
• 对每个点的label进行更新,更新为其对于其集合中最小的label。
算法实现:

import cv2
import numpy as np

# 4邻域的连通域和 8邻域的连通域
# [row, col]
NEIGHBOR_HOODS_4 = True
OFFSETS_4 = [[0, -1], [-1, 0], [0, 0], [1, 0], [0, 1]]
print(OFFSETS_4)
NEIGHBOR_HOODS_8 = False
OFFSETS_8 = [[-1, -1], [0, -1], [1, -1],
             [-1,  0], [0,  0], [1,  0],
             [-1,  1], [0,  1], [1,  1]]
print(len(OFFSETS_8))
#记录结果
def reorganize(binary_img: np.array):
    index_map = []
    points = []
    index = -1
    rows, cols = binary_img.shape
    # 遍历图像上的每个点
    for row in range(rows):
        for col in range(cols):
            var = binary_img[row][col]
            if var < 0.5:
                continue
            if var in index_map:
                # .index方法检测字符串中是否包含子字符串 str 
                index = index_map.index(var)
                num = index + 1
            else:
                index = len(index_map)
                num = index + 1
                index_map.append(var)
                points.append([])
            binary_img[row][col] = num
            points[index].append([row, col])
    return binary_img, points

#四领域或八领域判断
def neighbor_value(binary_img: np.array, offsets, reverse=False):
    rows, cols = binary_img.shape
    label_idx = 0
    rows_ = [0, rows, 1] if reverse == False else [rows-1, -1, -1]
    cols_ = [0, cols, 1] if reverse == False else [cols-1, -1, -1]
    for row in range(rows_[0], rows_[1], rows_[2]):
        for col in range(cols_[0], cols_[1], cols_[2]):
            label = 256
            if binary_img[row][col] < 0.5:
                continue
            for offset in offsets:
                neighbor_row = min(max(0, row+offset[0]), rows-1)
                neighbor_col = min(max(0, col+offset[1]), cols-1)
                neighbor_val = binary_img[neighbor_row, neighbor_col]
                if neighbor_val < 0.5:
                    continue
                label = neighbor_val if neighbor_val < label else label
            if label == 255:
                label_idx += 1
                label = label_idx
            binary_img[row][col] = label
    print('第一遍扫描:',binary_img)
    print('开始第二遍...')
    return binary_img

# binary_img: bg-0, object-255; int
#第一遍扫描
def Two_Pass(binary_img: np.array, neighbor_hoods):
    # 判断是几邻域
    if neighbor_hoods == NEIGHBOR_HOODS_4:
        offsets = OFFSETS_4
    elif neighbor_hoods == NEIGHBOR_HOODS_8:
        offsets = OFFSETS_8
    else:
        raise ValueError

    binary_img = neighbor_value(binary_img, offsets, False)
    binary_img = neighbor_value(binary_img, offsets, True)
    return binary_img

if __name__ == "__main__":
    #创建四行七列的矩阵
    binary_img = np.zeros((4, 7), dtype=np.int16)
    #指定点设置为255
    index = [[0, 2], [0, 5],
            [1, 0], [1, 1], [1, 2], [1, 4], [1, 5], [1, 6],
            [2, 2], [2, 5],
            [3, 1], [3, 2], [3, 4],[3,5], [3, 6]]
    for i in index:
        binary_img[i[0], i[1]] = np.int16(255)
    print("原始二值图像")
    print(binary_img)

    #print("Two_Pass")
    #调用Two Pass算法,计算两遍扫面的结果
    binary_img = Two_Pass(binary_img, NEIGHBOR_HOODS_4)
    binary_img1, points = reorganize(binary_img)
    print(binary_img1)
    print(points)

结果:

python opencv求连通域质心 opencv连通域分析_计算机视觉


2. 区域生长算法

区域生长是一种串行区域分割的图像分割方法。区域生长是指从某个像素出发,按照一定的准则,逐步加入邻近像素,当满足一定的条件时,区域生长终止。

区域生长的好坏决定于:初始点(种子点)的选取、生长准则、 终止条件。

基本思想:将具有相似性质的像素集合起来构成区域。

步骤:

  1. 对图像顺序扫描,找到第1个还没有归属的像素, 设该像素为(x0, y0);
  2. 以(x0, y0)为中心, 考虑(x0, y0)的4邻域像素(x, y)如果(x0, y0)满足生长准则, 将(x, y)与(x0, y0)合并(在同一区域内), 同时将(x, y)压入堆栈;
  3. 从堆栈中取出一个像素, 把它当作(x0, y0)返回到步骤2;
  4. 当堆栈为空时,返回到步骤1;
  5. 重复步骤1 - 4直到图像中的每个点都有归属时;
  6. 生长结束。

代码:

# -*- coding:utf-8 -*-
import cv2
import numpy as np

class Point(object):
    def __init__(self , x , y):
        self.x = x
        self.y = y
    def getX(self):
        return self.x
    def getY(self):
        return self.y
connects = [ Point(-1, -1), Point(0, -1), Point(1, -1), Point(1, 0),
            Point(1, 1), Point(0, 1), Point(-1, 1), Point(-1, 0)]
print(connects)    
#计算两个点间的欧式距离
def get_dist(seed_location1,seed_location2):
    l1 = im[seed_location1.x , seed_location1.y]
    l2 = im[seed_location2.x , seed_location2.y]
    count = np.sqrt(np.sum(np.square(l1-l2)))
    return count

#import Image
im = cv2.imread('image/222.jpg')
cv2.imshow('src' , im)
cv2.waitKey(0)
cv2.destroyAllWindows()
im_shape = im.shape
height = im_shape[0]
width = im_shape[1]

print( 'the shape of image :', im_shape)

#标记,判断种子是否已经生长
img_mark = np.zeros([height , width])
cv2.imshow('img_mark' , img_mark)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 建立空的图像数组,作为一类
img_re = im.copy()
for i in range(height):
    for j in range(width):
        img_re[i, j][0] = 0
        img_re[i, j][1] = 0
        img_re[i, j][2] = 0
cv2.imshow('img_re' , img_re)
cv2.waitKey(0)
cv2.destroyAllWindows()
#取一点作为种子点
seed_list = []
seed_list.append(Point(15, 15))
T = 7#阈值
class_k = 1#类别
#生长一个类
while (len(seed_list) > 0):
    seed_tmp = seed_list[0]
    #将以生长的点从一个类的种子点列表中删除
    seed_list.pop(0)
    img_mark[seed_tmp.x, seed_tmp.y] = class_k

    # 遍历8邻域
    for i in range(8):
        tmpX = seed_tmp.x + connects[i].x
        tmpY = seed_tmp.y + connects[i].y
        if (tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= width):
            continue
        dist = get_dist(seed_tmp, Point(tmpX, tmpY))
        #在种子集合中满足条件的点进行生长
        if (dist < T and img_mark[tmpX, tmpY] == 0):
            img_re[tmpX, tmpY][0] = im[tmpX, tmpY][0]
            img_re[tmpX, tmpY][1] = im[tmpX, tmpY][1]
            img_re[tmpX, tmpY][2] = im[tmpX, tmpY][2]
            img_mark[tmpX, tmpY] = class_k
            seed_list.append(Point(tmpX, tmpY))
#输出图像
cv2.imshow('OUTIMAGE' , img_re)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果:

python opencv求连通域质心 opencv连通域分析_算法_02