二值化的目的,是确定一个像素值,以像素为分界,将图像划分为前景和背景,前景的像素值取相同值,背景的像素也取相同值,从而将前景和背景的差异,在图像中最大化,或者说可以突出前景或者背景信息。
二值化可以有效的降低噪声,并且可以一定程度的增强目标特征
我使用一下,这篇文章的配图:https://blog.youkuaiyun.com/bigat/article/details/80889636
该文是关于图像混合的文章,我只是想用这两张图来说明,二值化的效果。
那么最终要的问题就是,选择哪个像素值,作为划分最合适?
日本学者大津(Nobuyuki Otsu)于1979年给出了很好的解答,论文:
算法的核心思想是,选择使得划分出来的前景与背景有最大方差的划分为最优划分,很多文章称为类间方差,因为可以将前景数据作为一类,而背景数据作为另一类,所以可以称为前景类与背景类的类间方差。
所以只要知道,怎么计算图像数据的类间方差就可以实现了,这里引用一下:https://blog.youkuaiyun.com/u012198575/article/details/81128799
中的公式,因为很全,所以我就不重新写了,如下:
公式: 记 M = 256 单通道灰度分级 Sum = 像素总数
- 背景像素占比
- 前景像素占比
- 背景的平均灰度值
- 前景的平均灰度值
- 0~M灰度区间的灰度累计值
- 类间方差:
- 将公式3.4.5带入公式6 可得最终简化公式:
下面使用Python+cv2来实现,并且和cv2中的OTSU的结果进行比较,使用cv2主要是为了读取图片:
将三通道的RGB图像转换为单通道的灰度图像:
公式如下I[i][j] = (299*r[i][j] + 587*g[i][j] + 114*b[i][j])/1000
这里不做过多解释,可以使用cv2的方法进行转换,这里是自己实现的算法:
def Gray(img):
a = np.shape(img)
r,g,b = cv2.split(img)
img_new = np.zeros((a[0], a[1]))
for i in range(a[0]):
for j in range(a[1]):
data = (299*r[i][j] + 587*g[i][j] + 114*b[i][j])/1000
img_new[i][j] = data
img_new = img_new.astype('uint8')
return img_new
效果如下:
统计各个灰度值像素的数量和占比:
def Pixel_num(img):
num = [0 for _ in range(256)]
a = np.shape(img)
for i in range(a[0]):
for j in range(a[1]):
num[img[i][j]] += 1
return num
def Pixel_rate(num_list):
rate_list = []
n = sum(num_list)
for i in range(len(num_list)):
rate = num_list[i] / n
rate_list.append(rate)
return rate_list
遍历0~255的像素值,寻找最优:
def Optimal_partition(rate_list):
deltaMax = 0
T = 0
for i in range(256):
w1 = w2 = u1 = u2 = 0
u1tmp = u2tmp = 0
deltaTmp = 0
for j in range(256):
if (j <= i):
w1 += rate_list[j]
u1tmp += j * rate_list[j]
else:
w2 += rate_list[j]
u2tmp += j * rate_list[j]
if w1 == 0:
u1 = 0
else:
u1 = u1tmp / w1
if w2 == 0:
u2 = 0
else:
u2 = u2tmp / w2
deltaTmp = w1 * w2 * ((u1- u2) ** 2)
if deltaTmp > deltaMax:
deltaMax = deltaTmp
T = i
return T
根据最优灰度值进行划分:
def Otsu(img, T):
a = np.shape(img)
new_img = np.zeros((a[0], a[1]))
for i in range(a[0]):
for j in range(a[1]):
if img[i][j] > T:
new_img[i][j] = 255
else:
new_img[i][j] = 0
return new_img
结果如下:
cv2结果:
ret, th = cv2.threshold(new_img, 0, 255, cv2.THRESH_OTSU)
效果一致
使用面向对象的方式将算法封装为类(文件名为Threshold):
'''
@Author: BTboay
@Date: 2019-12-05 12:28:10
@LastEditTime: 2019-12-05 14:28:25
@LastEditors: Please set LastEditors
@Description: In User Settings Edit
@FilePath: \YOLOv3_01\OTSU.py
'''
import numpy as np
import cv2
class OTSU():
def __init__(self, img_path):
img = cv2.imread(img_path)
self.img = img
img_gray = self.Gray()
self.img = img_gray
num_list = self.Pixel_num()
self.num_list = num_list
rate_list = self.Pixel_rate()
self.rate_list = rate_list
optimal_pixel = self.Optimal_partition()
self.optimal_pixel = optimal_pixel
def Gray(self):
a = np.shape(self.img)
r,g,b = cv2.split(self.img)
img_new = np.zeros((a[0], a[1]))
for i in range(a[0]):
for j in range(a[1]):
data = (299*r[i][j] + 587*g[i][j] + 114*b[i][j])/1000
img_new[i][j] = data
img_new = img_new.astype('uint8')
return img_new
def Pixel_num(self):
num = [0 for _ in range(256)]
a = np.shape(self.img)
for i in range(a[0]):
for j in range(a[1]):
num[self.img[i][j]] += 1
return num
def Pixel_rate(self):
rate_list = []
n = sum(self.num_list)
for i in range(len(self.num_list)):
rate = self.num_list[i] / n
rate_list.append(rate)
return rate_list
def Optimal_partition(self):
deltaMax = 0
T = 0
for i in range(256):
w1 = w2 = u1 = u2 = 0
u1tmp = u2tmp = 0
deltaTmp = 0
for j in range(256):
if (j <= i):
w1 += self.rate_list[j]
u1tmp += j * self.rate_list[j]
else:
w2 += self.rate_list[j]
u2tmp += j * self.rate_list[j]
if w1 == 0:
u1 = 0
else:
u1 = u1tmp / w1
if w2 == 0:
u2 = 0
else:
u2 = u2tmp / w2
deltaTmp = w1 * w2 * ((u1- u2) ** 2)
if deltaTmp > deltaMax:
deltaMax = deltaTmp
T = i
return T
def Otsu(self):
a = np.shape(self.img)
new_img = np.zeros((a[0], a[1]))
for i in range(a[0]):
for j in range(a[1]):
if self.img[i][j] > self.optimal_pixel:
new_img[i][j] = 255
else:
new_img[i][j] = 0
return new_img
调用该类:
'''
@Author: your name
@Date: 2019-12-03 15:43:11
@LastEditTime: 2019-12-05 15:20:49
@LastEditors: Please set LastEditors
@Description: In User Settings Edit
@FilePath: gray.py
'''
import numpy as np
import matplotlib.pyplot as plt
import cv2
import Threshold
if __name__ == "__main__":
path = 'D:/WorkSpace/YOLOv3_01/05.jpg'
a = Threshold.OTSU(path)
new_img = a.Otsu()
plt.imshow(new_img, 'gray')
plt.axis('off')
plt.show()