一、 前言
图像的灰度直方图
灰度直方图是关于灰度级分布的函数,是对图像中灰度级分布的统计。灰度直方图是将数字图像中的所有像素,按照灰度值的大小,统计其出现的频率。灰度直方图是灰度级的函数,它表示图像中具有某种灰度级的像素的个数,反映了图像中某种灰度出现的频率。【from百度百科】
计算直方图
1.使用opencv的函数
cv2.calcHist(images, channels, mask, histSize, ranges)
- 参数1:要计算的原图,以方括号的传入,如:[img]。
- 参数2:类似前面提到的dims,灰度图写[0]就行,彩色图B/G/R分别传入[0]/[1]/[2]。
- 参数3:要计算的区域ROI,计算整幅图的话,写None。
- 参数4:也叫bins,子区段数目,如果我们统计0-255每个像素值,bins=256;如果划分区间,比如0-15,16-31…240-255这样16个区间,bins=16。
- 参数5:range,要计算的像素值范围,一般为[0,256)。
eg:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('test.jpg')
hist = cv.calcHist([img],[0],None,[256],[0,256])
plt.plot(hist)
plt.show()
2. 使用Numpy函数
bincount(x, weights=None, minlength=None)
相对应的函数详解可参考:numpy.bincount详解 eg:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('test.jpg')
hist = np.bincount(img.ravel(), minlength=256)
plt.plot(hist)
plt.show()
二、 OTSU算法简介
OTSU算法是一种图像二值化算法。其算法假设图像中存在一个阈值 T ,判断图像中每个像素与T的大小关系,可以将所有像素分为背景 C0 和 前景 C1 两类,且当选取到最佳T阈值时,背景部分与前景部分的差别最大,而OTSU 算法利用最大类间方差来衡量这一差别。由于OTSU算法采用了最大类间方法的思想,因此该算法也被OTSU最大类间方差法
缺点:对图像噪声敏感;只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好
三、 数学原理
该算法的原理主要涉及 均值、方差等概念和部分公式的推导
-
将图片的像素值分为 [1, 2, …, L] 个水平,用ni 表示各个水平像素值的像素个数,那么很容易得到总像素个数N为:N = n1 + n2 + … + nL -
利用像素值对应个数与总数的商作为某个像素值出现的概率,定义pi
p
i
=
n
i
/
N
,
p
i
≥
0
,
∑
i
=
1
L
p
i
=
1
(1)
p_i = n_i/N, p_i ≥ 0, \sum_{i=1}^L p_i = 1 \tag1
pi?=ni?/N,pi?≥0,i=1∑L?pi?=1(1) -
定义两个量w0 , w1为C0,C1的局部概率之和,并且得到二者的关系
w
0
=
P
r
(
C
0
)
=
∑
i
=
1
k
p
i
=
w
(
k
)
(2)
w_0 = Pr(C_0) = \sum_{i=1}^k p_i = w(k) \tag2
w0?=Pr(C0?)=i=1∑k?pi?=w(k)(2)
w
1
=
P
r
(
C
1
)
=
∑
i
=
k
+
1
L
p
i
=
1
?
w
(
k
)
(3)
w_1 = Pr(C_1) = \sum_{i=k+1}^L p_i = 1 - w(k) \tag3
w1?=Pr(C1?)=i=k+1∑L?pi?=1?w(k)(3) -
由此我们得到总的数学期望【期望是平均数随样本趋于无穷的极限,所以可以将期望 ≈ 均值】和C0 , C1各自的数学期望并指出三者的关系,式中 i 代表像素值,除于各自概率的和用于进行归一。
u
0
=
∑
i
=
1
k
P
r
(
i
∣
C
0
)
=
∑
i
=
1
k
i
?
p
i
/
w
0
=
u
(
k
)
w
(
k
)
(4)
u_0 = \sum_{i=1}^k Pr(i|C_0) = \sum_{i=1}^k i * p_i / w_0 = \frac{u(k)}{w(k)} \tag4
u0?=i=1∑k?Pr(i∣C0?)=i=1∑k?i?pi?/w0?=w(k)u(k)?(4)
u
1
=
∑
i
=
k
+
1
L
P
r
(
i
∣
C
1
)
=
∑
i
=
k
+
1
L
i
?
p
i
/
w
1
=
u
(
T
)
?
u
(
k
)
1
?
w
(
k
)
(5)
u_1 = \sum_{i=k+1}^L Pr(i|C_1) = \sum_{i=k+1}^L i * p_i / w_1 = \frac{u(T) - u(k)}{1 - w(k)} \tag5
u1?=i=k+1∑L?Pr(i∣C1?)=i=k+1∑L?i?pi?/w1?=1?w(k)u(T)?u(k)?(5)
w
(
k
)
=
∑
i
=
1
k
p
i
(6)
w(k) = \sum_{i=1}^k p_i \tag6
w(k)=i=1∑k?pi?(6)
u
(
k
)
=
∑
i
=
1
k
i
?
p
i
(7)
u(k) = \sum_{i=1}^k i * p_i \tag7
u(k)=i=1∑k?i?pi?(7)
u
T
=
u
(
L
)
=
∑
i
=
1
L
i
?
p
i
(8)
u_T = u(L) = \sum_{i=1}^L i * p_i \tag8
uT?=u(L)=i=1∑L?i?pi?(8) 上述公式间的关系为:
w
0
u
0
+
w
1
u
1
=
u
T
,
w
0
+
w
1
=
1
(9)
w_0 u_0 + w_1 u_1 = u_T , w_0 + w_1 = 1 \tag9
w0?u0?+w1?u1?=uT?,w0?+w1?=1(9) -
各自局部方差和总方差为
σ
0
2
=
∑
i
=
1
k
(
i
?
u
0
)
2
P
r
(
i
∣
C
0
)
=
∑
i
=
1
k
(
i
?
u
0
)
2
p
i
/
w
0
(10)
σ^2_0 = \sum_{i=1}^k (i - u_0)^2 Pr(i|C_0) = \sum_{i=1}^k (i - u_0)^2 p_i / w_0 \tag{10}
σ02?=i=1∑k?(i?u0?)2Pr(i∣C0?)=i=1∑k?(i?u0?)2pi?/w0?(10)
σ
1
2
=
∑
i
=
k
+
1
L
(
i
?
u
1
)
2
P
r
(
i
∣
C
1
)
=
∑
i
=
k
+
1
L
(
i
?
u
1
)
2
p
i
/
w
1
(11)
σ^2_1 = \sum_{i=k+1}^L (i - u_1)^2 Pr(i|C_1) = \sum_{i=k+1}^L (i - u_1)^2 p_i / w_1 \tag{11}
σ12?=i=k+1∑L?(i?u1?)2Pr(i∣C1?)=i=k+1∑L?(i?u1?)2pi?/w1?(11)
σ
T
2
=
∑
i
=
1
L
(
i
?
u
T
)
2
p
i
(12)
σ^2_T = \sum_{i=1}^L (i - u_T)^2 p_i \tag{12}
σT2?=i=1∑L?(i?uT?)2pi?(12) -
within_class variance 类内差异
σ
w
2
=
w
0
σ
0
2
+
w
1
σ
1
2
(13)
σ^2_w = w_0 σ^2_0 + w_1 σ^2_1 \tag{13}
σw2?=w0?σ02?+w1?σ12?(13) between_class variance 类间差异
σ
b
2
=
w
0
(
u
0
?
u
T
)
2
+
w
1
(
u
1
?
u
T
)
2
(14)
σ^2_b = w_0(u_0 - u_T)^2 + w_1(u_1 - u_T)^2 \tag{14}
σb2?=w0?(u0??uT?)2+w1?(u1??uT?)2(14) -
当最大化类间差距时,即最小化类内差距,因为 二者的和为定值。
σ
w
2
+
σ
b
2
=
σ
T
2
(15)
σ^2_w + σ^2_b = σ^2_T \tag{15}
σw2?+σb2?=σT2?(15)
四、 python实现
假设一图像为灰度图,大小为M*N,背景部分较暗,OTSU算法步骤如下:
- 选取一个分割阈值T,统计计算属于前景的像素点数(像素值小于T)在全部像素点的占比
w
0
w_0
w0? 以及其平均灰度
u
0
u_0
u0? ; 同理统计属于背景部分的
w
1
w_1
w1? 和
u
1
u_1
u1? ;
- 计算图像全部像素的平均灰度
u
T
=
w
0
?
u
0
+
w
1
?
u
1
u_T = w_0 * u_0 + w_1 * u_1
uT?=w0??u0?+w1??u1? 以及类间方差
g
=
w
0
?
(
u
0
?
u
T
)
2
+
w
1
?
(
u
1
?
u
T
)
2
=
w
0
?
w
1
?
(
u
0
?
u
1
)
2
(16)
g = w_0 * (u_0 - u_T)^2 + w_1 * (u_1 - u_T)^2 = w_0 * w_1 * (u_0 - u_1)^2 \tag{16}
g=w0??(u0??uT?)2+w1??(u1??uT?)2=w0??w1??(u0??u1?)2(16)
- 遍历全部分割阈值T,重复1-2步骤,得到使类间方差 g 最大的阈值T;
import numpy as np
import cv2 as cv
img = cv.imread('test.jpg', 0)
cv.imshow("img", img)
cv.waitKey()
def OTSU(img_gray, GrayScale):
assert img_gray.ndim == 2, "must input a gary_img"
img_gray = np.array(img_gray).ravel().astype(np.uint8)
u1=0.0
u2=0.0
th=0.0
PixSum=img_gray.size
PixCount=np.zeros(GrayScale)
PixRate=np.zeros(GrayScale)
for i in range(PixSum):
Pixvalue=img_gray[i]
PixCount[Pixvalue]=PixCount[Pixvalue]+1
for j in range(GrayScale):
PixRate[j]=PixCount[j]*1.0/PixSum
Max_var = 0
for i in range(1,GrayScale):
u1_tem=0.0
u2_tem=0.0
w1=np.sum(PixRate[:i])
w2=1.0-w1
if w1==0 or w2==0:
pass
else:
for m in range(i):
u1_tem=u1_tem+PixRate[m]*m
u1 = u1_tem * 1.0 / w1
for n in range(i,GrayScale):
u2_tem = u2_tem + PixRate[n]*n
u2 = u2_tem / w2
tem_var=w1*w2*np.power((u1-u2),2)
if Max_var<tem_var:
Max_var=tem_var
th=i
return th
th = OTSU(img,256)
print("使用numpy的方法:" + str(th))
直接使用 opencv的函数
import numpy as np
import cv2 as cv
img = cv.imread('test.jpg',0)
retVal, a_img = cv.threshold(img, 0, 255, cv.THRESH_OTSU)
print("使用opencv函数的方法:" + str(retVal))
cv.imshow("a_img",a_img)
cv.waitKey()
五、 参考文章
|