在說Otsu之前,先說幾個概念
灰度直方圖:將數字圖像中的所有像素,按照灰度值的大小,統計其出現的頻率。其實就是每個值(0~255)的像素點個數統計。
Otsu算法假設這副圖片由前景色和背景色組成,通過最大類間方差選取一個閾值,將前景和背景盡可能分開。
一、代碼展示
import cv2 from matplotlib import pyplot as plt
img = cv2.imread('noisy.jpg', 0)
# 固定閾值法 ret1, th1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY) # Otsu閾值法 ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 先進行高斯濾波,再使用Otsu閾值法 blur = cv2.GaussianBlur(img, (5, 5), 0) ret3, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
images = [img, 0, th1, img, 0, th2, blur, 0, th3] titles = ['Original', 'Histogram', 'Global(v=100)', 'Original', 'Histogram', "Otsu's", 'Gaussian filtered Image', 'Histogram', "Otsu's"] for i in range(3): # 繪制原圖 plt.subplot(3, 3, i * 3 + 1) plt.imshow(images[i * 3], 'gray') plt.title(titles[i * 3], fontsize=8) plt.xticks([]), plt.yticks([]) # 繪制直方圖plt.hist, ravel函數將數組降成一維 plt.subplot(3, 3, i * 3 + 2) plt.hist(images[i * 3].ravel(), 256) plt.title(titles[i * 3 + 1], fontsize=8) plt.xticks([]), plt.yticks([]) # 繪制閾值圖 plt.subplot(3, 3, i * 3 + 3) plt.imshow(images[i * 3 + 2], 'gray') plt.title(titles[i * 3 + 2], fontsize=8) plt.xticks([]), plt.yticks([]) plt.show()
可以看出Otsu閾值明顯優於固定閾值。
二、Otsu算法推導
Otsu閾值法將整幅圖分為前景(目標)和背景,以下是一些符號規定:
- T: 分割閾值
- N0:前景像素點數
- N1:背景像素點數
- ω0:前景的像素點數占整幅圖像的比例
- ω1:背景的像素點數占整幅圖像的比例
- μ0:前景的平均像素值
- μ1:背景的平均像素值
- μ:整幅圖的平均像素值
- rows * cols:圖像的行數和列數
總的像素個數:
N0+N1=rows×cols
ω0和ω1是前景、背景所占的比例,也就是:
整幅圖的平均像素值就是:
我們定義一個前景μ0與背景μ1的方差g:
聯立前面幾個公式得:
g就是前景與背景兩類之間的方差,這個值越大,說明前景和背景的差別也就越大,效果越好。Otsu算法便是遍歷閾值T,使得g最大。所以又稱為最大類間方差法。基本上雙峰圖片的閾值T在兩峰之間的谷底。
參考網址:https://tianchi.aliyun.com/course/courseConsole?courseId=40992&chapterIndex=1§ionIndex=12