【opencv-python】大津法(Otsu)阈值分割原理深入分析

大津法(Otsu)是图像处理领域里面较为重要的阈值分割方法,适用于处理双峰图像。但大多数开发人员并不熟悉其原理,因此有必要对其进行详细说明与分析。

opnecv的实例代码链接为:
opencv-python大津法(Otsu)官方说明文档
python代码为:

img = cv2.imread('road.jpg',0)
blur = cv2.GaussianBlur(img,(5,5),0)
# find normalized_histogram, and its cumulative distribution function
hist = cv2.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()
bins = np.arange(256)
print(bins)
fn_min = np.inf
thresh = -1
for i in range(1,256):
    p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
    q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
    b1,b2 = np.hsplit(bins,[i]) # weights

    # finding means and variances
    m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
    v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2

    # calculates the minimization function
    fn = v1*q1 + v2*q2
    if fn < fn_min:
        fn_min = fn
        thresh = i
# find otsu's threshold value with OpenCV function
ret, otsu = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
print (thresh,ret)

下面对关键代码进行分析:

blur = cv2.GaussianBlur(img,(5,5),0)

用高斯滤波器对图像进行模糊操作,高斯核大小为5✖️5,参考说明,得到模糊后的图像变量blur。

hist = cv2.calcHist([blur],[0],None,[256],[0,256])

这句代码得到了blur图像的灰度直方图,函数calcHist定义可参考说明,得到了代表blur图像灰度分布的对象hist,hist为256行1列的数组,数据类型为numpy.ndarray,每个元素为该位置对应灰度值所对应的像素数,部分灰度值对应的像素数如下图所示:
【opencv-python】大津法(Otsu)阈值分割原理深入分析_第1张图片

hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()

hist_norm代表由每个灰度对应像素数量与某个最大像素数比值构成的数组,shape为[256,1](由于hist.ravel()),新数组Q中的每个元素表示hist_norm中每个元素与前所有元素的累加和。假设图像被阈值 T T T所分割,在灰度值小于 T T T的区间内(灰度直方图左侧),将灰度值为 i i i的像素个数定义为 P ( i ) P(i) P(i),在一张图像里的256个灰度值中,像素个数最大的灰度值定义为 P m P_m Pm;同理在灰度直方图右侧,分别定义为 Q ( i ) , Q m Q(i),Q_m Q(i),Qm。令: p ( i ) = P ( i ) / P m p(i)=P(i)/P_m p(i)=P(i)/Pm q ( i ) = Q ( i ) / Q m q(i)=Q(i)/Q_m q(i)=Q(i)/Qm
定义: s 1 = ∑ i = 0 T p ( i ) s_1=\sum_{i=0}^{T}p(i) s1=i=0Tp(i) s 2 = ∑ i = T 255 q ( i ) s_2=\sum_{i=T}^{255}q(i) s2=i=T255q(i)定义平均值: m 1 = ∑ i = 0 i ( i ∗ p ( i ) ) / s 1 m_1=\sum_{i=0}^i(i*p(i))/s_1 m1=i=0i(ip(i))/s1 m 2 = ∑ i = T 255 ( i ∗ q ( i ) ) / s 2 m_2=\sum_{i=T}^{255}(i*q(i))/s_2 m2=i=T255(iq(i))/s2定义方差: v 1 = ∑ i = 0 T ( i − m 1 ) 2 ∗ p ( i ) / s 1 v_1=\sum_{i=0}^T(i-m_1)^2*p(i)/s_1 v1=i=0T(im1)2p(i)/s1 v 2 = ∑ i = T 255 ( i − m 2 ) 2 q ( i ) / s 2 v_2=\sum_{i=T}^{255}(i-m_2)^2q(i)/s_2 v2=i=T255(im2)2q(i)/s2定义目标函数 f n = v 1 + v 2 f_n=v_1+v_2 fn=v1+v2,大津法认为:最合适的阈值 T T T,就是令目标函数 f n f_n fn取最小值的那一个,实现代码段为:

for i in range(1,256):
   p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
   print('i', i, 'p1,p2', p1, '---', p2)
   q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
   b1,b2 = np.hsplit(bins,[i]) # weights

   # finding means and variances
   m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
   #v1,v2 = np.sum(((b1-m1)**2)*p1)/q1, np.sum(((b2-m2)**2)*p2)/q2
   v1,v2 = np.sum(((b1-m1)**2)*p1), np.sum(((b2-m2)**2)*p2)
   fn = v1 + v2

   # calculates the minimization function
   #fn = v1*q1 + v2*q2

   if fn < fn_min:
       fn_min = fn
       thresh = i

至此,Otsu阈值分割法推导完毕。问题:为什么令 f n f_n fn最小的阈值 T T T就是最合适的 T T T?欢迎大家入群深入交流。
【opencv-python】大津法(Otsu)阈值分割原理深入分析_第2张图片
下面我们对其原理进行验证,我们找一张双峰图像,如下图所示:
【opencv-python】大津法(Otsu)阈值分割原理深入分析_第3张图片
我们将这张图片的灰度直方图打开,呈现出来如下图所示:
【opencv-python】大津法(Otsu)阈值分割原理深入分析_第4张图片可以看出,灰度直方图呈现出近似双峰图像的特点,灰度值较低处体现的是图像中下方“马路”附近的大片像素,灰度值较高处体现的是图像中上方“蓝天”中的大片像素。我们用大津法对其进行分割,效果如下图所示:
【opencv-python】大津法(Otsu)阈值分割原理深入分析_第5张图片代码:

import cv2
import numpy as np
from numpy.core.fromnumeric import shape
from PIL import Image
#plotly
import plotly as py
import plotly.graph_objs as go

img = cv2.imread('road.jpg',0)
img1 = np.ravel(img)
print('type', type(img1), img1.shape, img1)
pyplt = py.offline.plot
data = [go.Histogram(x=img1, histnorm = 'probability')] 
ret, th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
print('ret', ret, cv2.THRESH_BINARY_INV, cv2.THRESH_OTSU
pyplt(data)
self.show_img(img, 'road')
self.show_img(th2, 'th2')

将cv2函数计算得到的阈值和我自己计算的阈值打印出来,均为157,说明大津法计算正确。
在这里插入图片描述

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