(自己编写函数)Python + Opencv 图像分割(全局分割_动态分割_区域生长)

实验原理与步骤

1.连通定义
在数字图像中,一个像素在空间上可能非常接近其它一些像素。在用网格表示的数字图像中,一个像素与其它四个像素有公共边界,并与另外四个像素共享顶角。如果两个像素有公共边界,则称它们互为4邻点(4-neighbors)。同样,如果两个像素至少共享一个顶点,则称它们互为8邻点,如图1所示:
(自己编写函数)Python + Opencv 图像分割(全局分割_动态分割_区域生长)_第1张图片
2. 连通成分标记
在一幅图像中找出连通成分是数字图像中最常见的运算之一。连通区域内的点构成表示物体的候选区域。机器视觉中的大多数物体都有表面,显然,物体表面点投影到图像平面上会形成空间上密集的点集。这里应该指出,连通成分算法常常会在二值视觉系统中形成瓶颈效应,原因是连通成分运算是一个全局性的运算,这种算法在本质上是连贯的。如果图像中仅有一个物体,那么找连通成分就没有必要;如果图像中有许多物体,且需要求出物体的特性与位置,则必须确定连通成分。
连通标记算法可以找到图像中的所有连通成分,并对同一连通成分中的所有点分配同一标记。图2表示的是一幅图像和已标记的连通成分。
(自己编写函数)Python + Opencv 图像分割(全局分割_动态分割_区域生长)_第2张图片
3. 递归算法
算法步骤如下:

  • ① 扫描图像,找到没有标记的1点,给它分配一个新的标记L;
  • ② 递归分配标记L给1点的邻点;
  • ③ 如果不存在没标记的点,则停止;
  • ④ 返回第①步。

4.区域分裂算法
如果区域的某些特性不是恒定的,则区域应该分裂。基于分裂方法的图像分割过程是从最大的区域开始,在许多情况下,常把整个图像作为起始分裂的图像。
算法步骤如下:

  • ① 形成初始区域;
  • ② 对图像的每一个区域,连续执行下面两步:

计算区域灰度值方差;
如果方差值大于某一阈值,则沿着某一适合的边界分裂区域。

5.区域增长算法
在许多图像中,单个区域内的灰度值不是完全恒定的,因此需要更复杂的算法来进行图像分割。其中最好的算法是那些基于如下假设的算法,即图像可以划分成区域,而区域可以用简单函数模型化。
可由分割问题导出如下算法:寻找初始区域核,并从区域核开始,逐渐增长核区域,形成满足一定约束的较大的区域。
算法步骤如下:

  • ① 把图像划分成初始区域核;
  • ② 用平面模型拟合每一个区域核;
  • ③ 对每一个区域,通过区域模型向邻接区域外插,求取与该区域兼容的所有点;
  • ④ 如果没有兼容点,则增加模型阶数。如果模型阶数大于最大的模型阶数,停止区域增长;否则,回到第③步,继续区域增长;
  • ⑤ 形成新的区域,重新用相同阶数的模型能够拟合新区域,计算拟合最佳度;
  • ⑥ 计算区域模型的新老拟合最佳度之差;
  • ⑦ 如果新老拟合最佳度之差小于某一给定阈值,回到第三步,继续区域增长;
  • ⑧ 增加模型阶数,如果模型阶数大于最大的模型阶数,停止区域增长;
  • 用新的模型阶数再拟合区域模型。入伙拟合误差增加,接收新的模型阶数,回到第③步,继续区域增长,否则,停止区域增长。

算法设计思路

(1)、首先将图片转换成灰度图进行操作,先进行全局阈值的计算,最适合的全局阈值计算使用otsu(大津法计算)

  • ① 先计算图像的直方图,即将图像所有的像素点按照0~255共256个bin,统计落在每个bin的像素点数量
  • ② 归一化直方图,也即将每个bin中像素点数量除以总的像素点
  • ③ i表示分类的阈值,也即一个灰度级,从0开始迭代
  • ④ 通过归一化的直方图,统计0~i 灰度级的像素(假设像素值在此范围的像素叫做前景像素) 所占整幅图像的比例w0,并统计前景像素的平均灰度u0;统计i~255灰度级的像素(假设像素值在此范围的像素叫做背景像素) 所占整幅图像的比例w1,并统计背景像素的平均灰度u1;
  • ⑤计算前景像素和背景像素的方差 g = w0w1(u0-u1) (u0-u1)
  • ⑥i++;转到 4 ,直到i为256时结束迭代
  • ⑦将最大g相应的i值作为图像的全局阈值
#使用otsu算法思维求出阈值并对图像进行二值化处理
def myotsu(gray):
    countdown = 0
    countup = 0
    hist_new = []
    num = []
    hist_countresult = []
    hist_key = []
    hist_proresult = []
    #处理后最终输出矩阵将齐大小设置为与原图一样
    gray2=np.array([[0 for i in range(gray.shape[1])] for i in range(gray.shape[0])], dtype='float') 
    #gray1 用于统计每哥灰度级所有的个数 ,因为是列表不是矩阵,
    #所以要先将gray的灰度级矩阵变成一维列表
    gray1 = list(gray.ravel())
    #以字典的形式保存,统计出来的灰度级及其个数
    obj = dict(collections.Counter(gray1))
    obj = sorted(obj.items(),key=lambda item:item[0])
    #将统计出来的灰度级的值与他的个数分开用列表保存
    for each in obj :
        key = list(each)[0]
        num =list(each)[1]
        hist_key.append(key)
        hist_new.append(num)
    #检查从0-255每个灰度级是否都有个数,没有的话添加并将值设为0
    for i in range (0,256) :
        if i in hist_key :
            num = hist_key.index(i)
            hist_countresult.append(hist_new[num])
        else :
            hist_countresult.append(0)
    if len(hist_countresult) < 256 :
        for i in range (0,256-len(hist_countresult)) :
            hist_new.append(0)
    #计算整幅图的像素数目
    hist_sum = gray.shape[0] * gray.shape[1]
    #计算每个灰度级的像素数目占整个数目的比重
    for each in hist_countresult :
        result = float(each / hist_sum)
        hist_proresult.append(result)
    #遍历灰度级[0,255],寻找合适的threshold
    w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = deltaMax = float(0)
    for i in range (256) :
        w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = float(0)
        for j in range (256) :
            if j <= i : #背景部分
                w0 = float(w0 + hist_proresult[j])
                u0tmp += j * hist_proresult[j]
            else :  #前景部分
                w1 += float(hist_proresult[j])
                u1tmp += j * hist_proresult[j]
        if w0 == 0.0 or w1 == 0.0:
            pass
        else :
            u0 = float(u0tmp / w0)
            u1 = float(u1tmp / w1)
            deltaTmp = (float)(w0 *w1* pow((u0 - u1), 2)) 
            if deltaTmp > deltaMax : 
                deltaMax = deltaTmp
                threshold = i
    #用ostu大津算法得出最适当的阈值后,将图片进行二值化
    for i in range(gray.shape[0]) :
        for j in range(gray.shape[1]) :
             #对大于阈值的显示为255白色,小于阈值的显示为0黑色
            if gray[i][j] <= threshold :
                gray2[i][j] = 0
                countdown += 1
            else :
                gray2[i][j] = 255
                countup += 1
    return gray2

(2)、动态阈值,将原灰度图片,按照几等分的切割规律,将每一个小方格里的像素取出来,存到一个列表里,之后在进行取出,参照规律。然后按照阈值来进行二值化

#分割成为多块区域
def divide(gray,m,n) :
    height,width = gray.shape[0],gray.shape[1]
    each_width = int(width / (m-1))
    each_height = int(height / (n-1))
    #生成存放16块图像灰度值的列表
    divide_list = []
    for i in range (m-1) :
        for j in range (n-1):
            #生成存放每块小图片灰度级的矩阵
            divide_image = np.array([[0 for i in range(each_width)] for i in range(each_height)], dtype=np.uint8) 
            for divide_i in range (each_height):
                for divide_j in range (each_width) :
                    divide_image[divide_i][divide_j] = gray[ i*each_height + divide_i ][ j*each_width + divide_j]
            divide_list.append(divide_image)
    return divide_list



#*********************************
    #               动态阈值分割
    #*********************************
    #创建动态阈值矩阵
    Dynamic_threshold_image = np.array([[0 for i in range(gray.shape[1])] for i in range(gray.shape[0])], dtype=np.uint8) 
    #调用自己写的otsu算法,进行阈值化分割图像处理
    m=4
    n=4
    each_width = int(width / (m))
    each_height = int(height / (n))
    divide_list = divide(gray,m+1,n+1)   #分割成为4*4的块
    #用ostu算法计算每一小块图片的最佳阈值,进行动态阈值处理
    num = 0 #记录这是第几块图形,第一块为0
    for each in divide_list :
        #根据第几幅图算出他在原图中所在的行号和列号
        r = int (num / m) #行
        c = num % n #列
        ostu_image = myotsu(each)
        for i in range (ostu_image.shape[0]) :
            for j in range (ostu_image.shape[1]) :
                Dynamic_threshold_image[ i + r*each_height ][ j + c*each_width ] = ostu_image [i][j]
        num += 1
    cv2.imshow('Dynamic_threshold',Dynamic_threshold_image)

(3)、动态生长:

  • ① 对图像顺序扫描!找到第1个还没有归属的像素, 设该像素为(x0, y0);
  • ②以(x0, y0)为中心, 考虑(x0, y0)的4邻域像素(x, y)如果(x0, y0)满足生长准则, 将(x, y)与(x0, y0)合并(在同一区域内), 同时将(x, y)压- 入堆栈;
  • ③从堆栈中取出一个像素, 把它当作(x0, y0)返回到步骤2;
  • ④当堆栈为空时!返回到步骤1;
  • ⑤重复步骤1 - 4直到图像中的每个点都有归属时。生长结束。
#求两个点的差值
def getGrayDiff(image,currentPoint,tmpPoint):
    return abs(int(image[currentPoint[0],currentPoint[1]]) - int(image[tmpPoint[0],tmpPoint[1]]))

#区域生长算法
def regional_growth (gray,seeds,threshold=15) :
    #每次区域生长的时候的种子像素之间的八个邻接点
    connects = [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), \
                        (0, 1), (-1, 1), (-1, 0)]    
    threshold = threshold #种子生长时候的相似性阈值,默认即灰度级不相差超过15以内的都算为相同
    height, weight = gray.shape
    seedMark = np.zeros(gray.shape)
    seedList = []
    for seed in seeds:
        seedList.append(seed)   #将种子添加到种子的列表中
    label = 1	#标记点的flag
    while(len(seedList)>0):     #如果种子列表里还存在种子点
        currentPoint = seedList.pop(0)  #将最前面的那个种子抛出
        seedMark[currentPoint[0],currentPoint[1]] = label   #将对应位置的点标志为1
        for i in range(8):  #对这个种子点周围的8个点一次进行相似性判断
            tmpX = currentPoint[0] + connects[i][0]
            tmpY = currentPoint[1] + connects[i][1]
            if tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= weight:    #如果超出限定的阈值范围
                continue    #跳过并继续
            grayDiff = getGrayDiff(gray,currentPoint,(tmpX,tmpY))   #计算此点与种子像素点的灰度级之差
            if grayDiff < threshold and seedMark[tmpX,tmpY] == 0:
                seedMark[tmpX,tmpY] = label
                seedList.append((tmpX,tmpY))
    return seedMark

完整代码:

import matplotlib.pyplot as plt
from scipy import signal
import numpy as np
import copy as cp
import random
import math
import cv2
import collections

#使用otsu算法思维求出阈值并对图像进行二值化处理
def myotsu(gray):
    countdown = 0
    countup = 0
    hist_new = []
    num = []
    hist_countresult = []
    hist_key = []
    hist_proresult = []
    #处理后最终输出矩阵将齐大小设置为与原图一样
    gray2=np.array([[0 for i in range(gray.shape[1])] for i in range(gray.shape[0])], dtype='float') 
    #gray1 用于统计每哥灰度级所有的个数 ,因为是列表不是矩阵,
    #所以要先将gray的灰度级矩阵变成一维列表
    gray1 = list(gray.ravel())
    #以字典的形式保存,统计出来的灰度级及其个数
    obj = dict(collections.Counter(gray1))
    obj = sorted(obj.items(),key=lambda item:item[0])
    #将统计出来的灰度级的值与他的个数分开用列表保存
    for each in obj :
        key = list(each)[0]
        num =list(each)[1]
        hist_key.append(key)
        hist_new.append(num)
    #检查从0-255每个灰度级是否都有个数,没有的话添加并将值设为0
    for i in range (0,256) :
        if i in hist_key :
            num = hist_key.index(i)
            hist_countresult.append(hist_new[num])
        else :
            hist_countresult.append(0)
    if len(hist_countresult) < 256 :
        for i in range (0,256-len(hist_countresult)) :
            hist_new.append(0)
    #计算整幅图的像素数目
    hist_sum = gray.shape[0] * gray.shape[1]
    #计算每个灰度级的像素数目占整个数目的比重
    for each in hist_countresult :
        result = float(each / hist_sum)
        hist_proresult.append(result)
    #遍历灰度级[0,255],寻找合适的threshold
    w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = deltaMax = float(0)
    for i in range (256) :
        w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = float(0)
        for j in range (256) :
            if j <= i : #背景部分
                w0 = float(w0 + hist_proresult[j])
                u0tmp += j * hist_proresult[j]
            else :  #前景部分
                w1 += float(hist_proresult[j])
                u1tmp += j * hist_proresult[j]
        if w0 == 0.0 or w1 == 0.0:
            pass
        else :
            u0 = float(u0tmp / w0)
            u1 = float(u1tmp / w1)
            deltaTmp = (float)(w0 *w1* pow((u0 - u1), 2)) 
            if deltaTmp > deltaMax : 
                deltaMax = deltaTmp
                threshold = i
    #用ostu大津算法得出最适当的阈值后,将图片进行二值化
    for i in range(gray.shape[0]) :
        for j in range(gray.shape[1]) :
             #对大于阈值的显示为255白色,小于阈值的显示为0黑色
            if gray[i][j] <= threshold :
                gray2[i][j] = 0
                countdown += 1
            else :
                gray2[i][j] = 255
                countup += 1
    return gray2

#分割成为多块区域
def divide(gray,m,n) :
    height,width = gray.shape[0],gray.shape[1]
    each_width = int(width / (m-1))
    each_height = int(height / (n-1))
    #生成存放16块图像灰度值的列表
    divide_list = []
    for i in range (m-1) :
        for j in range (n-1):
            #生成存放每块小图片灰度级的矩阵
            divide_image = np.array([[0 for i in range(each_width)] for i in range(each_height)], dtype=np.uint8) 
            for divide_i in range (each_height):
                for divide_j in range (each_width) :
                    divide_image[divide_i][divide_j] = gray[ i*each_height + divide_i ][ j*each_width + divide_j]
            divide_list.append(divide_image)
    return divide_list

#求两个点的差值
def getGrayDiff(image,currentPoint,tmpPoint):
    return abs(int(image[currentPoint[0],currentPoint[1]]) - int(image[tmpPoint[0],tmpPoint[1]]))

#区域生长算法
def regional_growth (gray,seeds,threshold=15) :
    #每次区域生长的时候的种子像素之间的八个邻接点
    connects = [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), \
                        (0, 1), (-1, 1), (-1, 0)]    
    threshold = threshold #种子生长时候的相似性阈值,默认即灰度级不相差超过15以内的都算为相同
    height, weight = gray.shape
    seedMark = np.zeros(gray.shape)
    seedList = []
    for seed in seeds:
        seedList.append(seed)   #将种子添加到种子的列表中
    label = 1	#标记点的flag
    while(len(seedList)>0):     #如果种子列表里还存在种子点
        currentPoint = seedList.pop(0)  #将最前面的那个种子抛出
        seedMark[currentPoint[0],currentPoint[1]] = label   #将对应位置的点标志为1
        for i in range(8):  #对这个种子点周围的8个点一次进行相似性判断
            tmpX = currentPoint[0] + connects[i][0]
            tmpY = currentPoint[1] + connects[i][1]
            if tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= weight:    #如果超出限定的阈值范围
                continue    #跳过并继续
            grayDiff = getGrayDiff(gray,currentPoint,(tmpX,tmpY))   #计算此点与种子像素点的灰度级之差
            if grayDiff < threshold and seedMark[tmpX,tmpY] == 0:
                seedMark[tmpX,tmpY] = label
                seedList.append((tmpX,tmpY))
    return seedMark

def MAIN(): 
    image = cv2.imread(r"D:/Code/Python/7.png")
    cv2.imshow("color_image",image)
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    height,width = gray.shape[0],gray.shape[1]
    cv2.imshow("gray_img",gray)
    #*********************************
    #               动态阈值分割
    #*********************************
    #创建动态阈值矩阵
    Dynamic_threshold_image = np.array([[0 for i in range(gray.shape[1])] for i in range(gray.shape[0])], dtype=np.uint8) 
    #调用自己写的otsu算法,进行阈值化分割图像处理
    m=4
    n=4
    each_width = int(width / (m))
    each_height = int(height / (n))
    divide_list = divide(gray,m+1,n+1)   #分割成为4*4的块
    #用ostu算法计算每一小块图片的最佳阈值,进行动态阈值处理
    num = 0 #记录这是第几块图形,第一块为0
    for each in divide_list :
        #根据第几幅图算出他在原图中所在的行号和列号
        r = int (num / m) #行
        c = num % n #列
        ostu_image = myotsu(each)
        for i in range (ostu_image.shape[0]) :
            for j in range (ostu_image.shape[1]) :
                Dynamic_threshold_image[ i + r*each_height ][ j + c*each_width ] = ostu_image [i][j]
        num += 1
    cv2.imshow('Dynamic_threshold',Dynamic_threshold_image)

    #*********************************
    #               全局阈值分割
    #*********************************
    Global_threshold_image = myotsu(gray)
    cv2.imshow('Global_threshold',Global_threshold_image)

    #*********************************
    #               区域生长分割
    #*********************************
    seed_points = [(10,150),(100,150),(75,250),(129,210),(263,243)]  #输入选取的种子像素
    seed_grow_image = regional_growth(gray,seed_points,30)
    cv2.imshow('region_growth',seed_grow_image)
    cv2.waitKey(0)


if __name__ == "__main__":
    MAIN()

实验结果:

(自己编写函数)Python + Opencv 图像分割(全局分割_动态分割_区域生长)_第3张图片
(自己编写函数)Python + Opencv 图像分割(全局分割_动态分割_区域生长)_第4张图片

你可能感兴趣的:(python,OpenCV)