如何理解圖像處理中的Otsu's 二值化算法(大津算法)Python編程實踐

本教程代碼已開源到github: https://github.com/varyshare/easy_slam_tutorial/tree/master/Otsu’s_Method_algorithm

Otsu’s 二值化(大津算法)

二值化是什麼?有什麼用?

PDF掃描成電子版,文字識別,車牌識別等等圖像處理場合均需要使用“二值化”操作。我們知道圖像是一個矩陣組成,矩陣的元素是一個數字,這個數字是當前像素點對應的顏色(即像素值)。而圖片的二值化操作就是將所有像素值變成要麼是0要麼是1.一般二值化怎麼做的呢?答:“設置一個數字d,只要像素值大於這個閾值d那就設置爲1,小於這個閾值d那就設置爲0。當然也可以大於這個閾值設置爲0,小於設置爲1”。但是這個閾值怎麼找到的呢?計算出一個合適的閾值出來這就是 Otsu’s 二值化(大津算法)要做的事情
下面是一幅圖片對應的像素值矩陣(圖片就是矩陣):
[200,30,4013,40,45]\begin{bmatrix} 200,30,40\\ 13, 40,45 \end{bmatrix}
假設現在我通過Otsu’s 二值化(大津算法)計算出上面那個圖片二值化的最優閾值是39.
那麼上面那個圖片就會被二值化爲:
[1,0,10,1,1]\begin{bmatrix} 1,0,1\\ 0, 1,1 \end{bmatrix}
下面我們實驗下。

實驗1. 造一個數據

做圖像處理必備技能就是人工製造一個純淨的圖片檢驗算法正確性

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

######我們先製造一個200x200的圖片用於二值化實驗#######
def get_test_img():
    img_mat = np.zeros((200,200),dtype=np.uint8)# 記得設置成整數,不然opencv會將大於1的浮點數全顯示成白色
    for row in range(200):
        for col in range(200):
            img_mat[row][col] = col
    return img_mat
img_mat = get_test_img()
plt.imshow(img_mat,cmap='gray')# 顯示圖片
plt.xlabel("raw img")

如下所示:
在這裏插入圖片描述

2. 手工設置閾值進行二值化實驗

##########我們設置二值化的閾值爲100,將像素值小於100設置爲0 (黑色)大於100設置爲1 (白色)#######
img_mat = get_test_img() # 注意這是實驗1中那個函數
img_mat[img_mat<=100]=0
img_mat[img_mat>100]=1
plt.imshow(img_mat,cmap='gray')# 顯示圖片
plt.xlabel("binary img")

我們將實驗1中的圖片二值化爲下面這張圖。
在這裏插入圖片描述

Otsu’s 二值化(大津算法)是怎麼根據一張圖片計算出它二值化的最優閾值的?

它就是統計各個像素值的出現的次數,然後遍歷各種閾值(總共有256種可能的選擇),然後讓被劃分的那兩類的像素值的方差的加權和最小。加權和的權重就是對應類中像素值之和。這個方差就用統計學中的方差計算公式即可。
我總結下Otsu僞代碼:

統計各個像素值的出現次數

while(遍歷各種閾值的取值(0到255種可能))
{
    1. 根據當前閾值對圖像所有出現的像素值進行分類。
    大於閾值的像素值分爲一個類A,小於閾值則分爲另外一個類B。(到時候你可以讓A類中所有像素值爲1,B類所有像素值爲0。也可以讓類A所有像素值爲0.這都是可以你自己定,所以我就用A,B代替了。)
    2. 計算類A的所有像素值的方差SA,計算類B中所有像素值的方差SB
    3. 計算類A中所有像素值之和IA,計算類B中所有像素點的像素值的像素值之和IB
    4. 計算當前閾值劃分下兩個類的像素值方差的加權和S=IA*SA+SB*IB
    5. 像素值方差的加權和S是否是目前遍歷到的閾值劃分下最小的那一個值?如果是那就保存當前這種取值
}

通過上面操作最終得到最優的閾值d。

while(遍歷所有像素點)
{
   像素值大於閾值d賦值爲1,
   像素值小於閾值d賦值爲0
}

otsu二值化實驗

先用cv2中的otsu庫函數看看效果

# 調用cv2中的otsu庫
img_mat = get_test_img() # 這是實驗1中的那個函數
img_mat = img_mat.astype(np.uint8)
threshold,img_mat = cv2.threshold(img_mat,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
print(threshold)
plt.imshow(img_mat,cmap='gray')

在這裏插入圖片描述
再我們自己造一個輪子Python代碼從零實現otsu二值化算法


import numpy as np
# 自己造輪子寫otsu二值化算法
img_mat = get_test_img()
img_mat = img_mat.astype(np.uint8)
##############統計各個像素值的出現次數##############
img_mat_vector = img_mat.flatten()
pixel_counter = np.zeros(256)
for pixel_value in img_mat_vector:
    pixel_counter[pixel_value] += 1

############遍歷閾值的各種可能的取值############
min_variance = np.inf
best_threshold = 0
pixel_value = np.arange(256)
for threshold in range(256):
    # 1. 根據閾值對各個像素值進行劃分
    pixel_value_A = pixel_value[0:threshold]
    pixel_value_B = pixel_value[threshold:]
    # 2. 計算類A的所有像素值的方差SA,計算類B中所有像素值的方差SB
    totalPixelNum_A = np.sum(pixel_counter[pixel_value_A]) 
    totalPixelNum_B = np.sum(pixel_counter[pixel_value_B])
    
    Probability_pixelvalue_A = pixel_counter[pixel_value_A]/totalPixelNum_A
    Probability_pixelvalue_B = pixel_counter[pixel_value_B]/totalPixelNum_B
    
    meanPixelValue_A = np.sum(pixel_value_A  *  Probability_pixelvalue_A)
    meanPixelValue_B = np.sum(pixel_value_B  *  Probability_pixelvalue_B)
    
    varianceA = np.sum(Probability_pixelvalue_A * (pixel_value_A-meanPixelValue_A)**2)
    varianceB = np.sum(Probability_pixelvalue_B * (pixel_value_B-meanPixelValue_B)**2)
    
    current_total_variance = totalPixelNum_A*varianceA + totalPixelNum_B*varianceB
    if current_total_variance<min_variance:
        min_variance = current_total_variance
        best_threshold = threshold
        
print("最優像素值的閾值爲",best_threshold)
#######根據獲得的閾值對圖像各個像素像素點進行二值化######
img_mat[img_mat<best_threshold] = 0
img_mat[img_mat>=best_threshold] = 1
plt.imshow(img_mat,cmap='gray')

最優像素值的閾值爲 100
在這裏插入圖片描述

用實際數據實踐

# 手寫一個otsu二值化
img = cv2.imread('./eight.png',cv2.IMREAD_GRAYSCALE)
# 圖片數據我已放到github了
# https://github.com/varyshare/easy_slam_tutorial/tree/master/Otsu's_Method_algorithm
##############統計各個像素值的出現次數##############
img_vector = img.flatten()
pixel_counter = np.zeros(256)
for pixel_value in img_vector:
    pixel_counter[pixel_value] += 1

############遍歷閾值的各種可能的取值############
min_variance = np.inf
best_threshold = 0
pixel_value = np.arange(256)
for threshold in range(256):
    # 1. 根據閾值對各個像素值進行劃分
    pixel_value_A = pixel_value[0:threshold]
    pixel_value_B = pixel_value[threshold:]
    # 2. 計算類A的所有像素值的方差SA,計算類B中所有像素值的方差SB
    totalPixelNum_A = np.sum(pixel_counter[pixel_value_A]) 
    totalPixelNum_B = np.sum(pixel_counter[pixel_value_B])
    
    Probability_pixelvalue_A = pixel_counter[pixel_value_A]/totalPixelNum_A
    Probability_pixelvalue_B = pixel_counter[pixel_value_B]/totalPixelNum_B
    
    meanPixelValue_A = np.sum(pixel_value_A  *  Probability_pixelvalue_A)
    meanPixelValue_B = np.sum(pixel_value_B  *  Probability_pixelvalue_B)
    
    varianceA = np.sum(Probability_pixelvalue_A * (pixel_value_A-meanPixelValue_A)**2)
    varianceB = np.sum(Probability_pixelvalue_B * (pixel_value_B-meanPixelValue_B)**2)
    
    current_total_variance = totalPixelNum_A*varianceA + totalPixelNum_B*varianceB
    if current_total_variance<min_variance:
        min_variance = current_total_variance
        best_threshold = threshold
        
print("最優像素值的閾值爲",best_threshold)
#######根據獲得的閾值對圖像各個像素像素點進行二值化######
img[img<best_threshold] = 0
img[img>=best_threshold] = 1
plt.imshow(img,cmap='gray')

實驗結果如下所示
在這裏插入圖片描述

它的原始圖片爲:
在這裏插入圖片描述

你的贊和關注是我分享易懂教程的最大動力

今日github項目推薦:
簡單易懂的SLAM實踐教程Python代碼實踐

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章