十三种基于直方图的图像全局二值化算法原理、实现、代码及效果

十三种基于直方图的图像全局二值化算法实现

  • 1. 什么是基于直方图的图像全局二值化算法
  • 2. 灰度平均值
  • 3. 百分比阈值(P-Tile法)
  • 3. 基于双峰的阈值
    • 3.1 基于平均值的阈值
    • 3.2 基于最小值的阈值方法
  • 4. 迭代最佳阈值
  • 5.大津(OTSU)法
  • 6.一维最大熵
  • 7.力矩保持法
  • 8.模糊集
  • 9. Kittler最小错误分类法
  • 10. ISODATA
  • 11. Shanbhag 法
  • 12. Yen法

1. 什么是基于直方图的图像全局二值化算法

本文的内容来自与十三种基于直方图的图像全局二值化算法原理、实现、代码及效果。本文的中的算法是基于图像的灰度直方图进行阈值分割。
所谓图像的灰度直方图是在将图像进行灰度处理后,计算灰度值的分布。也就是每个灰度值在灰度图像中有多少个点。一般情况下,图像灰度值的取值范围为0~255,所以灰度直方图是一个有256个元素的数组。
阈值分割就是找到一个合适阈值,对灰度图像进行处理。大于阈值的设置为255,小于阈值的设置为0。也可以根据需要设为其他数值。这样处理灰度图像,可以将前景与背景做一个区分。
此方法的关键就是计算阈值。在前面的链接中,介绍了13种方法并提供了C语言实现。本文将这些算法用Python进行了重写。

2. 灰度平均值

灰度平均值是图像总像素值/总像素数:
总 像 素 值 = ∑ g = 0 255 g × h ( g ) 总像素值=\displaystyle \sum_{g=0}^{255}g\times h(g) =g=0255g×h(g)
总 像 素 数 = ∑ g = 0 255 h ( g ) 总像素数=\displaystyle \sum_{g=0}^{255}h(g) =g=0255h(g)
总像素数其实就是图像的宽度X图像的长度,也是图像的大小。

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetMeanThreshold(H):
    Amount = np.sum(H)
    Gray_Sum = np.sum(H*np.arange(256))
    return int(float(Gray_Sum/Amount))


def GrayThreshold(image, maxval=255):
    g = GrayHist(image)
    thresh = GetMeanThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":

    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1, img_new_1 = cv2.threshold(img_gray, 146, 255, cv2.THRESH_BINARY)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

十三种基于直方图的图像全局二值化算法原理、实现、代码及效果_第1张图片
对于小鸟的图像其灰度平均阈值为146。

3. 百分比阈值(P-Tile法)

此方法先设定一个阈值p(先验概率),比如50%。然后从0开始对灰度值求和。计算到灰度值Y时,[0~Y]的综合>=总像素值*p,则设定灰度值Y为阈值。
该方法需要根据先验概率确定一个百分比。人的经验很重要。

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetPTileThreshold(H,ptile=0.5):
    Amount = np.sum(H)
    A = Amount * ptile
    psum = 0
    for Y in range(256):
        psum += H[Y]
        if psum >= A:
            return Y
    return -1  # 没有符合条件的阈值


def GrayThreshold(image, maxval=255):
    g = GrayHist(image)
    thresh = GetPTileThreshold(g,0.25)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":

    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1, img_new_1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

当百分比设为25%时,阈值为123。这种方法需要认为调节阈值,否则很难得到理想的结果。

3. 基于双峰的阈值

此方法适合与有明显双峰值的图像,对于单峰值或者平坦的灰度直方图形并不适合。
其原理是通过迭代对直方图数据进行判断,判端是否是一个双峰的直方图。如果不是,则对直方图数据进行窗口为3的平滑。如果迭代了一定的数量比如1000次后仍未获得一个双峰的直方图,则函数执行失败。如成功获得,则最终阈值取两个双峰之间的谷底值作为阈值。
所谓窗口为3的平滑,就是将灰度p的值,用p-1,p和p+1的值进行平均。
直方图通过迭代形成双峰后,有2种方法获得阈值:
(1)双峰的平均值
(2)双峰之间的谷底值
第一种方法是找到双峰后,取平均值的整数。第二种方法找到谷底的最小值。

3.1 基于平均值的阈值

# coding:utf8
import numpy as np
import cv2
from matplotlib import pyplot as plt


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetIntermodesThreshold(H,method=0):
    Iter = 0
    HistGramC = np.array(H, dtype=np.float64)  # 基于精度问题,一定要用浮点数来处理,否则得不到正确的结果
    HistGramCC = np.array(H, dtype=np.float64)  # 求均值的过程会破坏前面的数据,因此需要两份数据

    # 通过三点求均值来平滑直方图
    while IsDimodal(HistGramCC) == False:  # 判断是否已经是双峰的图像了
        HistGramCC[0] = (HistGramC[0] + HistGramC[0] + HistGramC[1]) / 3  # 第一点
        for Y in range(1, 255):
            HistGramCC[Y] = (HistGramC[Y - 1] + HistGramC[Y] + HistGramC[Y + 1]) / 3  # 中间的点
        HistGramCC[255] = (HistGramC[254] + HistGramC[255] + HistGramC[255]) / 3  # 最后一点
        HistGramC = np.array(HistGramCC, dtype=np.float64)  # 备份数据
        Iter += 1
        if Iter >= 1000:
            return -1  # 直方图无法平滑为双峰的,返回错误代码

    if method > 0:
        Peakfound = False
        for Y in range(1,255):
            if HistGramCC[Y - 1] < HistGramCC[Y] and HistGramCC[Y + 1] < HistGramCC[Y]:
                Peakfound = True
            if Peakfound and HistGramCC[Y - 1] >= HistGramCC[Y] and HistGramCC[Y + 1] >= HistGramCC[Y]:
                return Y - 1
        return -1
    else:
        # 阈值为两峰值的平均值
        Peak = np.zeros(2,dtype=np.uint16)
        Index = 0
        for Y in range(1, 255):
            if HistGramCC[Y - 1] < HistGramCC[Y] and HistGramCC[Y + 1] < HistGramCC[Y]:
                Peak[Index] = Y - 1
                Index += 1
        return int((Peak[0] + Peak[1]) / 2)


def IsDimodal(H):  # 检测直方图是否为双峰的

    # 对直方图的峰进行计数,只有峰数位2才为双峰
    Count = 0
    for Y in range(1, 255):
        if H[Y - 1] < H[Y] and H[Y + 1] < H[Y]:
            Count += 1
            if Count > 2: return False
    if Count == 2:
        return True
    else:
        return False


def GrayThreshold(image, maxval=255):
    g = GrayHist(img_gray)
    thresh = GetIntermodesThreshold(g,1)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":
    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th1, img_new_1 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_TRIANGLE)
    th, img_new = GrayThreshold(img_gray)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

此种方法得到的小鸟图像阈值为159。原始直方图和平滑后的直方图(黄色)如图:
十三种基于直方图的图像全局二值化算法原理、实现、代码及效果_第2张图片

3.2 基于最小值的阈值方法

该方法是查找双峰之间的最小值。使用此方法需要调用GetIntermodesThreshold(g,1)。最后一个参数设置为1即可。

4. 迭代最佳阈值

此方法是先从左边找到HistGram中第一个数值不为0的点min,再从右边找到第一个数值不为0的点max。然后取阈值threshold为此2点的中间值。通过计算[min,threshold]和(threshold,max]平均值灰度值对应的点,进行迭代直到平均灰度值点与阈值点相等。
平均灰度值计算为:
G m i n = ∑ g = m i n T k g × h ( g ) ∑ g = m i n T k h ( g ) G_{min}=\frac{\sum_{g=min}^{T_k}g\times h(g)}{\sum_{g=min}^{T_k}h(g)} Gmin=g=minTkh(g)g=minTkg×h(g)
G m a x = ∑ g = T k m a x g × h ( g ) ∑ g = T k m a x h ( g ) G_{max}=\frac{\sum_{g=T_k}^{max}g\times h(g)}{\sum_{g=T_k}^{max}h(g)} Gmax=g=Tkmaxh(g)g=Tkmaxg×h(g)
T k + 1 = G m i n + G m a x 2 T_{k+1}=\frac{G_{min}+G_{max}}{2} Tk+1=2Gmin+Gmax
重复进行运算,直到Tk=Tk+1
需要注意的是可能存在不收敛的情况。

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetIterativeBestThreshold(HistGram):
    Iter = 0
    value = np.where(HistGram > 0)[0]  # 非0下标
    index = len(value)
    if index == 0:
        return 0
    elif index == 1 or index == 2:
        return value[0]
    else:
        MinValue = value[0]
        MaxValue = value[-1]

    Threshold = MinValue
    NewThreshold = int(MaxValue + MinValue) >> 1
    while Threshold != NewThreshold:  # 当前后两次迭代的获得阈值相同时,结束迭代

        Threshold = NewThreshold
        #[MinValue,Threshold]灰度平均值对应的点
        SUmInteralOne = np.sum(HistGram[MinValue:Threshold + 1] * np.arange(MinValue, Threshold + 1))
        SumOne = np.sum(HistGram[MinValue:Threshold + 1])
        MeanValueOne = SUmInteralOne / SumOne
        # [Threshold+1,MaxValue]灰度平均值对应的点
        SUmInteralTwo = np.sum(HistGram[Threshold + 1:MaxValue + 1] * np.arange(Threshold + 1, MaxValue + 1))
        SumTwo = np.sum(HistGram[Threshold + 1:MaxValue + 1])
        MeanValueTwo = SUmInteralTwo / SumTwo

        NewThreshold = int(MeanValueOne + MeanValueTwo) >> 1  # 求出新的阈值
        Iter += 1
        if Iter >= 1000:
            return -1
    return Threshold


def GrayThreshold(image, maxval=255):
    g = GrayHist(image)
    thresh = GetIterativeBestThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":
    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1, img_new_1 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

此方法求出的阈值为126。

5.大津(OTSU)法

大津法是日本学者大津在1979年提出的方法,也叫最大类间法。其对单峰值或者比较平坦的灰度值有很好的效果,对双峰或者多峰的情况则较差。其方法如下:
记t为前景与背景的分割阈值,前景点数占图像比例为w0,平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1
则图像的总平均灰度为:u=w0 *u0+w1*u1
前景和背景图象的方差:g=w0*(u0-u)*(u0-u)+w1*(u1-u)*(u1-u)=w0*w1*(u0-u1)2,此公式为方差公式。
不断的迭代求解出最大的方差值,可以认为此时前景和背景的相差最大,从而得到分割图像的阈值。

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetOSTUThreshold(H):
    # 判断只有1种灰度、2种灰度以及多种灰度
    V = np.where(H > 0)[0]  # 非0下标
    I = len(V)
    if I == 0: return 0
    if I == 1: return V[0]
    if I == 2: return V[0] if H[V[0]] < H[V[1]] else V[1]

    MinValue = V[0]
    MaxValue = V[-1]

    PixelBack = 0
    PixelIntegralBack = 0
    Threshold = 0

    Amount = np.sum(H)
    PixelIntegral = np.sum(H * np.arange(256))
    SigmaB = -1
    for Y in range(MinValue, MaxValue):
        PixelBack = PixelBack + H[Y]
        PixelFore = Amount - PixelBack
        OmegaBack = PixelBack / Amount
        OmegaFore = PixelFore / Amount
        PixelIntegralBack += H[Y] * Y
        PixelIntegralFore = PixelIntegral - PixelIntegralBack
        MicroBack = PixelIntegralBack / PixelBack
        MicroFore = PixelIntegralFore / PixelFore
        Sigma = OmegaBack * OmegaFore * (MicroBack - MicroFore) * (MicroBack - MicroFore)
        if Sigma > SigmaB:
            SigmaB = Sigma
            Threshold = Y

    return Threshold


def GrayThreshold(image, maxval=255):
    g = GrayHist(image)
    thresh = GetOSTUThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":
    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1, img_new_1 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

自定义的OTSU方法与opencv中的OTSU方法得到的阈值是一样的,都是125。

6.一维最大熵

最大熵的思想是将图像分为两部分(可以认为是前景和背景),循环计算两部分的灰度值的熵,使其和最大。取得最大值的灰度即为阈值。
其中熵的概念为:
h i s t = h ( g ) ∑ x = 0 255 h ( x ) + 最 小 正 浮 点 值 hist=\frac{h(g)}{\sum_{x=0}^{255}h(x)}+最小正浮点值 hist=x=0255h(x)h(g)+

S 1 = ∑ g = 0 T h i s t ( g ) S_1=\sum_{g=0}^{T}hist(g) S1=g=0Thist(g)

E 1 = − ∑ g = 0 T h i s t ( g ) S 1 × l o g ( ∑ g = 0 T h i s t ( g ) S 1 ) E_1=-\frac{\sum_{g=0}^{T}hist(g)}{S_1}\times log(\frac{\sum_{g=0}^{T}hist(g)}{S_1}) E1=S1g=0Thist(g)×log(S1g=0Thist(g))

E 2 = − ∑ g = T + 1 255 h i s t ( g ) 1 − S 1 × l o g ( ∑ g = T 255 h i s t ( g ) 1 − S 1 ) E_2=-\frac{\sum_{g=T+1}^{255}hist(g)}{1-S_1}\times log(\frac{\sum_{g=T}^{255}hist(g)}{1-S_1}) E2=1S1g=T+1255hist(g)×log(1S1g=T255hist(g))
计算出最大的E1+E2的灰度值即为阈值。

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt
import math

def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def Get1DMaxEntropyThreshold(H):

    V = np.where(H > 0)[0]  # 非0下标
    I = len(V)
    if I == 0: return 0
    if I == 1: return V[0]
    if I == 2: return V[0] if H[V[0]] < H[V[1]] else V[1]

    MinValue = V[0]
    MaxValue = V[-1]
    Threshold=MinValue
    HistGramD = np.zeros(256,dtype=np.float64)
    Amount = np.sum(H[MinValue:MaxValue + 1])
    eps = np.finfo(np.float64).eps
    HistGramD[MinValue:MaxValue+1] = H[MinValue:MaxValue+1]/Amount + eps
    MaxEntropy = 0.0
    for Y in range(MinValue+1,MaxValue):
        SumIntegral = 0.
        for X in range(MinValue,Y+1):
            SumIntegral += HistGramD[X]
        EntropyBack = 0.
        for X in range(MinValue,Y+1):
            EntropyBack += -HistGramD[X] / SumIntegral * np.log(HistGramD[X] / SumIntegral)
        EntropyFore = 0.
        for X in range(Y+1,MaxValue+1):
            EntropyFore += -HistGramD[X] / (1 - SumIntegral) * np.log(HistGramD[X] / (1 - SumIntegral))
        if MaxEntropy < EntropyBack + EntropyFore:

            Threshold = Y
            MaxEntropy = EntropyBack + EntropyFore
    return Threshold


def GrayThreshold(image,maxval=255):
    g = GrayHist(image)
    thresh = Get1DMaxEntropyThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out

if __name__ == "__main__":

    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1,img_new_1 = cv2.threshold(img_gray, 0, 255,  cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    print(th,th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

计算出的阈值为104。

7.力矩保持法

力矩法的论文可以从此链接中获得力矩法论文。上述论文是英文版的。此方法比较复杂。
实现方法如下:

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt
import math


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def AH(H, index):
    return np.sum(H[:index+1])

def BH(H, index):
    K = np.arange(index+1)
    return np.sum(H[:index+1]*K)

def CH(H, index):
    K = np.float_power(np.arange(index+1),2)
    return np.sum(H[:index + 1] * K)


def DH(H, index):
    K = np.float_power(np.arange(index+1),3)
    return np.sum(H[:index + 1] * K)


def GetMomentPreservingThreshold(H):
    V = np.where(H > 0)[0]  # 非0下标
    I = len(V)
    if I == 0: return 0
    if I == 1: return V[0]
    if I == 2: return V[0] if H[V[0]] < H[V[1]] else V[1]

    MaxValue = V[-1]
    Avec = np.zeros(256, dtype=np.float64)
    Amount = np.sum(H)
    for Y in range(256):
        Avec[Y] = AH(H, Y) / Amount
    Index = 0
    B = BH(H,255)
    A = AH(H,255)
    C = CH(H,255)
    D = DH(H,255)
    X2 = (B * C - A * D) / (A * C - B * B)
    X1 = (B * D - C * C) / (A * C - B * B)
    X0 = 0.5 - (B / A + X2 / 2) / math.sqrt(X2 * X2 - 4 * X1)

    Min = float(MaxValue)
    for Y in range(256):
        if math.fabs(Avec[Y] - X0) < Min:
            Min = math.fabs(Avec[Y] - X0)
            Index = Y

    return int(Index)


def GrayThreshold(image, maxval=255):
    g = GrayHist(image)
    thresh = GetMomentPreservingThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":
    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1, img_new_1 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

8.模糊集

具体方法参考基于模糊集理论的一种图像二值化算法的原理、实现效果及代码

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt
import math


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetHuangFuzzyThreshold(H):

    Threshold = -1
    BestEntropy = np.finfo(np.float32).max
    # 找到第一个和最后一个非0的色阶值
    V = np.where(H > 0)[0]  # 非0下标
    I = len(V)
    if I == 0: return 0
    if I == 1: return V[0]
    if I == 2: return V[0] if H[V[0]] < H[V[1]] else V[1]

    First = V[0]
    Last = V[-1]

    # 计算累计直方图以及对应的带权重的累计直方图
    S = np.zeros(Last+1,dtype=np.int64)
    W = np.zeros(Last+1,dtype=np.int64)

    S[0] = H[0]
    start = First if First>1 else 1
    for Y in range(start,Last+1):
        S[Y] = S[Y - 1] + H[Y]
        W[Y] = W[Y - 1] + Y * H[Y]

    # 建立公式(4)及(6)所用的查找表
    Smu = np.zeros(Last+1-First,dtype=np.float32)

    for Y in range(1,Last+1-First):
        mu = 1. / (1. + float(Y) / (Last - First))               # 公式(4)
        Smu[Y] = -mu * math.log(mu) - (1 - mu) * math.log(1 - mu)      # 公式(6)

    # 迭代计算最佳阈值
    for Y in range(First,Last+1):
        Entropy = 0
        mu = int(np.round(float(W[Y] / S[Y])))          # 公式17
        for X in range(First,Y+1):
            Entropy += Smu[abs(X - mu)] * H[X]
        mu = int(np.round(float((W[Last] - W[Y]) / (S[Last] - S[Y])))) if Y < Last else 0
        for X in range(Y + 1,Last+1):
            Entropy += Smu[abs(X - mu)] * H[X]       # 公式8
        if BestEntropy > Entropy:
            BestEntropy = Entropy       #取最小熵处为最佳阈值
            Threshold = Y
    return Threshold


def GrayThreshold(image, maxval=255):
    g = GrayHist(image)
    thresh = GetHuangFuzzyThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":
    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1, img_new_1 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

9. Kittler最小错误分类法

具体方法见Kittler, J & Illingworth, J (1986), “Minimum error thresholding”, Pattern Recognition 19: 41-47

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt
import math

def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetKittlerMinError(HistGram):

    value = np.where(HistGram>0)[0] #非0下标
    index = len(value)
    if index == 0:
        return 0
    elif index == 1:
        return value[0]
    elif index == 2:
        return value[0]
    else:
        MinValue = value[0]
        MaxValue = value[-1]

    Threshold = -1
    MinSigma = np.finfo(np.float64).eps
    for Y in range(MinValue,MaxValue):
        PixelBack = 0
        PixelFore = 0
        OmegaBack = 0
        OmegaFore = 0
        for X in range(MinValue,Y+1):
            PixelBack += HistGram[X]
            OmegaBack = OmegaBack + X * HistGram[X]
        for X in range(Y+1,MaxValue+1):
            PixelFore += HistGram[X]
            OmegaFore = OmegaFore + X * HistGram[X]
        OmegaBack = OmegaBack / PixelBack
        OmegaFore = OmegaFore / PixelFore
        SigmaBack = 0
        SigmaFore = 0
        for X in range(MinValue,Y+1):
            SigmaBack = SigmaBack + (X - OmegaBack) * (X - OmegaBack) * HistGram[X]
        for X in range(Y+1,MaxValue):
            SigmaFore = SigmaFore + (X - OmegaFore) * (X - OmegaFore) * HistGram[X]

        if SigmaBack == 0 or SigmaFore == 0:
            if Threshold == -1:
                Threshold = Y
        else:
            SigmaBack = math.sqrt(SigmaBack / PixelBack)
            SigmaFore = math.sqrt(SigmaFore / PixelFore)
            Sigma = 1 + 2 * (PixelBack * math.log(SigmaBack / PixelBack) + PixelFore * math.log(SigmaFore / PixelFore))
            if Sigma < MinSigma:
                MinSigma = Sigma
                Threshold = Y
    return Threshold



def GrayThreshold(image,maxval=255):
    g = GrayHist(image)
    thresh = GetKittlerMinError(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out

if __name__ == "__main__":

    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1,img_new_1 = cv2.threshold(img_gray, 0, 255,  cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    print(th,th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

10. ISODATA

参考论文:

Ridler, TW & Calvard, S (1978), “Picture thresholding using an iterative selection method”, IEEE Transactions on Systems, Man and Cybernetics 8: 630-632, http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=4310039

# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt
import math

def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetIsoDataThreshold(H):

    V = np.where(H > 0)[0]  # 非0下标
    I = len(V)
    if I == 0: return 0
    if I == 1: return V[0]
    if I == 2: return V[0] if H[V[0]] < H[V[1]] else V[1]
    MinValue = V[0]
    MaxValue = V[-1]
    threshold = MinValue

    while (True):
        SumOne = 0
        SumInteralOne = 0
        SumTwo = 0
        SumInteralTwo = 0
        for i in range(threshold):
            SumOne += H[i]
            SumInteralOne += H[i] * i
        for i in range(threshold+1,MaxValue):
            SumTwo += H[i]
            SumInteralTwo += (H[i] * i)

        if SumOne > 0 and SumTwo > 0:
            SumInteralOne /= SumOne
            SumInteralTwo /= SumTwo

            if threshold == int(np.round((SumInteralOne + SumInteralTwo) / 2.0)):
                break
        threshold += 1
        if threshold > 253:
            return 0
    return threshold


def GrayThreshold(image,maxval=255):
    g = GrayHist(image)
    thresh = GetIsoDataThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out

if __name__ == "__main__":


    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1,img_new_1 = cv2.threshold(img_gray, 0, 255,  cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    print(th,th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()


11. Shanbhag 法

参考论文:

  Shanbhag, Abhijit G. (1994), "Utilization of information measure as a means of image thresholding", Graph. Models Image Process. (Academic Press, Inc.) 56 (5): 414--419, ISSN 1049-9652, DOI 10.1006/cgip.1994.1037
# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt
import math


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetShanbhagThreshold(H):
    V = np.where(H > 0)[0]  # 非0下标
    I = len(V)
    if I == 0: return 0
    if I == 1: return V[0]
    if I == 2: return V[0] if H[V[0]] < H[V[1]] else V[1]

    P1 = np.zeros(256, dtype=np.float64)
    P2 = np.zeros(256, dtype=np.float64)

    Amount = np.sum(H)
    norm_histo = H / Amount

    P1[0] = norm_histo[0]
    P2[0] = 1.0 - P1[0]

    for ih in range(1, 256):
        P1[ih] = P1[ih - 1] + norm_histo[ih]
        P2[ih] = 1.0 - P1[ih]

    first_bin = 0
    for ih in range(1, 256):
        if not (math.fabs(P1[ih]) < np.finfo(np.float64).eps):
            first_bin = ih
            break

    last_bin = 255
    for ih in range(255, first_bin - 1, -1):
        if not (math.fabs(P2[ih]) < np.finfo(np.float64).eps):
            last_bin = ih
            break

    threshold = -1
    min_ent = np.finfo(np.float64).max
    for it in range(first_bin, last_bin + 1):
        ent_back = 0.0
        term = 0.5 / P1[it]
        for ih in range(1, it + 1):
            ent_back -= norm_histo[ih] * math.log(1.0 - term * P1[ih - 1]);
        ent_back *= term
        ent_obj = 0.0
        term = 0.5 / P2[it]
        for ih in range(it + 1, 256):
            ent_obj -= norm_histo[ih] * math.log(1.0 - term * P2[ih])

        ent_obj *= term

        tot_ent = math.fabs(ent_back - ent_obj)

        if tot_ent < min_ent:
            min_ent = tot_ent
            threshold = it

    return threshold


def GrayThreshold(image, maxval=255):
    g = GrayHist(image)
    thresh = GetShanbhagThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":

    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1, img_new_1 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

12. Yen法

参考论文:

 1) Yen J.C., Chang F.J., and Chang S. (1995) "A New Criterion  for Automatic Multilevel Thresholding" IEEE Trans. on Image  Processing, 4(3): 370-378
 2) Sezgin M. and Sankur B. (2004) "Survey over Image Thresholding Techniques and Quantitative Performance Evaluation" Journal of  Electronic Imaging, 13(1): 146-165
# coding:utf8

import numpy as np
import cv2
from matplotlib import pyplot as plt
import math


def GrayHist(img):
    grayHist = np.zeros(256, dtype=np.uint64)
    for v in range(256):
        grayHist[v] = np.sum(img == v)
    return grayHist


def GetYenThreshold(H):
    V = np.where(H > 0)[0]  # 非0下标
    I = len(V)
    if I == 0: return 0
    if I == 1: return V[0]
    if I == 2: return V[0] if H[V[0]] < H[V[1]] else V[1]

    P1 = np.zeros(256, dtype=np.float64)
    P1_sq = np.zeros(256, dtype=np.float64)
    P2_sq = np.zeros(256, dtype=np.float64)

    norm_histo = H/ np.sum(H)

    P1[0] = norm_histo[0]

    for ih in range(1, 256):
        P1[ih] = P1[ih - 1] + norm_histo[ih]
    P1_sq[0] = norm_histo[0] * norm_histo[0]
    for ih in range(1, 256):
        P1_sq[ih] = P1_sq[ih - 1] + norm_histo[ih] * norm_histo[ih]

    P2_sq[255] = 0.0
    for ih in range(254, -1, -1):
        P2_sq[ih] = P2_sq[ih + 1] + norm_histo[ih + 1] * norm_histo[ih + 1]

    threshold = -1
    max_crit = np.finfo(np.float64).eps

    for it in range(256):
        l1 = math.log(P1_sq[it] * P2_sq[it]) if P1_sq[it] * P2_sq[it] > 0.0 else 0.0
        l2 = math.log(P1[it] * (1.0 - P1[it])) if P1[it] * (1.0 - P1[it]) > 0.0 else 0.0
        crit = -1.0 * l1 + 2 * l2
        if crit > max_crit:
            max_crit = crit
            threshold = it

    return threshold


def GrayThreshold(image, maxval=255):
    g = GrayHist(image)
    thresh = GetYenThreshold(g)
    threshImage_out = image.copy()
    # 大于阈值的都设置为maxval
    threshImage_out[threshImage_out > thresh] = maxval
    # 小于阈值的都设置为0
    threshImage_out[threshImage_out <= thresh] = 0
    return thresh, threshImage_out


if __name__ == "__main__":

    img = cv2.imread('bird.png')
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    th, img_new = GrayThreshold(img_gray)
    th1, img_new_1 = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    print(th, th1)
    plt.subplot(131), plt.imshow(img_gray, cmap='gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(132), plt.imshow(img_new, cmap='gray')
    plt.title('Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(133), plt.imshow(img_new_1, cmap='gray')
    plt.title('CV2 Image1'), plt.xticks([]), plt.yticks([])
    plt.show()

你可能感兴趣的:(Python,图像处理,算法,python)