1. OTSU阈值分割
参考1. https://zhuanlan.zhihu.com/p/124944108
OTSU算法(大津算法)的详细步骤:
- 假设初始有个阈值 T 0 T_0 T0,并将图像分为两个部分(阈值分割),前景F和背景B。
- 假设像素的总个数为N,前景像素个数为 N f N_f Nf,背景像素个数为 N b N_b Nb;
- 假设图像的总灰度级为
L
−
1
L-1
L−1,每个灰度级的像素个数为
N
i
N_i
Ni,那么满足如下的公式:
P f = ∑ i = 0 i = T 0 N i N P_f = \sum ^{i=T_0} _{i=0} \frac {N_i}{N} Pf=i=0∑i=T0NNi
P b = ∑ i = T 0 i = L − 1 N i N P_b = \sum ^{i=L-1} _{i=T_0} \frac {N_i}{N} Pb=i=T0∑i=L−1NNi - 前景和背景的灰度平均值分别为:
M f = ∑ i = 0 i = T 0 i ∗ P i P f M_f = \sum ^{i=T_0} _{i=0} i * \frac {P_i}{P_f} Mf=i=0∑i=T0i∗PfPi
M b = ∑ i = T 0 i = L − 1 i ∗ P i P b M_b = \sum ^{i=L-1} _{i=T_0} i * \frac {P_i}{P_b} Mb=i=T0∑i=L−1i∗PbPi - 整个图像的灰度平均值为:
M = P f ∗ M f + P b ∗ M b M = P_f * M_f + P_b * M_b M=Pf∗Mf+Pb∗Mb - 那么,前景和背景之间的类间方差为:
δ 2 = P f ∗ ( M f − M ) 2 + P b ∗ ( M b − M ) 2 \delta ^2 = P_f * (M_f - M)^2 + P_b * (M_b - M)^2 δ2=Pf∗(Mf−M)2+Pb∗(Mb−M)2 - 大津算法的目的就是求得一个阈值,使得第六步的类间方差最大。
- 至于怎么求解,可以用遍历的方法,或者采用其他优化算法,找出那个类间方差最大的阈值来分割即可。
参考2. https://zhuanlan.zhihu.com/p/95034826
g:类间方差(那个灰度的g最大,那个灰度就是需要的阈值t)
g
=
w
0
∗
(
u
0
−
u
)
2
+
w
1
∗
(
u
1
−
u
)
2
g = w_0 * (u_0 - u) ^2 + w_1 * (u_1 - u) ^2
g=w0∗(u0−u)2+w1∗(u1−u)2
根据上面的关系,可以推出:
g
=
w
0
∗
w
1
∗
(
u
0
−
u
1
)
2
g = w_0 * w_1 * (u_0 - u_1) ^2
g=w0∗w1∗(u0−u1)2
然后,遍历每一个灰度值,找到这个灰度值对应的g,找到最大的g对应的t。
参考3. https://www.cnblogs.com/yinliang-liang/p/9293310.html
解释阈值分割法
参考4. https://blog.csdn.net/u010128736/article/details/52801310
#coding:utf-8
import cv2
import numpy as np
from matplotlib import pyplot as plt
image = cv2.imread("E:/python/cv/OTSU/test.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
plt.subplot(131), plt.imshow(image, "gray")
plt.title("source image"), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.hist(image.ravel(), 256)
plt.title("Histogram"), plt.xticks([]), plt.yticks([])
ret1, th1 = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) #方法选择为THRESH_OTSU
plt.subplot(133), plt.imshow(th1, "gray")
plt.title("OTSU,threshold is " + str(ret1)), plt.xticks([]), plt.yticks([])
plt.show()
2. 去除小区域
加入高斯滤波,代码如下:
import cv2
import numpy as np
from matplotlib import pyplot as plt
image = cv2.imread("H:/wj/pictures/test.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
#plt.subplot(131), plt.imshow(image, "gray")
#plt.title("source image"), plt.xticks([]), plt.yticks([])
#plt.subplot(132), plt.hist(image.ravel(), 256)
#plt.title("Histogram"), plt.xticks([]), plt.yticks([])
blur = cv2.GaussianBlur(image, (5,5), 0) # 高斯滤波
ret1, th1 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) #方法选择为THRESH_OTSU
#plt.subplot(111), plt.imshow(th1, "gray")
#plt.title("OTSU,threshold is " + str(ret1)), plt.xticks([]), plt.yticks([])
#plt.show()
#cv2.imwrite("H:/wj/pictures/test_re.jpg", th1) #保存OTSU处理的图片
# 去除小区域
contours,hierarch=cv2.findContours(th1,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
for i in range(len(contours)):
area = cv2.contourArea(contours[i])
if area < 700:
cv2.drawContours(th1,[contours[i]],0,0,-1)
cv2.imwrite("H:/wj/pictures/test_sArea.jpg", th1)
执行效果如下:
3. 去除孔洞
参考1. https://www.cnblogs.com/denny402/p/5166258.html
消除孤立区域,导入skimage
参考2. https://www.cnblogs.com/picassooo/p/12024471.html
Python-OpenCV实现二值图像孔洞填充
import cv2
import numpy as np
def FillHole(mask):
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
len_contour = len(contours)
contour_list = []
for i in range(len_contour):
drawing = np.zeros_like(mask, np.uint8) # create a black image
img_contour = cv2.drawContours(drawing, contours, i, (255, 255, 255), -1)
contour_list.append(img_contour)
out = sum(contour_list)
return out
if __name__ == '__main__':
mask_in = cv2.imread('H:/wj/pictures/test_sArea.jpg', 0)
mask_out = FillHole(mask_in)
cv2.imwrite('H:/wj/pictures/test_sArea+erase.jpg', mask_out)
执行效果如下:
参考3. https://cloud.tencent.com/developer/article/1016690
python下使用cv2.drawContours填充轮廓颜色
代码:
import cv2
import os
img = cv2.imread("H:/wj/pictures/001159.jpg")
# 定义海陆分离函数
def segmentFunc(img):
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 一、高斯滤波 滤波之后001159效果不佳
#blur = cv2.GaussianBlur(gray_img, (5, 5), 0)
# 二、otsu阈值
(t, thresh) = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 三、形态学操作
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# 四、彩色图像的otsu分割
# 1.颜色空间转换:BGR转RGB
#result = cv2.cvtColor(cv2.bitwise_and(img, img, mask=thresh), cv2.COLOR_BGR2RGB)
result = cv2.bitwise_and(img, img, mask=thresh)
# 2.保存otsu分割的彩色照片
#cv2.imwrite("H:/wj/pictures/001159_otsu.jpg", result)
# 五、去除小连通域和大连通域
h, w, _ = result.shape
# 1.找到边界
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 2.需要搞一个list给cv2.drawContours()
c_max = []
for i in range(len(contours)):
cnt = contours[i]
area = cv2.contourArea(cnt)
# 3.处理掉小区域和大区域
if(area < (h/50 * w/50) or area > (h/3 * w/3)):
c_min = []
c_min.append(cnt)
# thickness不为-1时,表示画轮廓线,thickness的值表示线的宽度。
cv2.drawContours(thresh, c_min, -1, (0, 0, 0), thickness=-1)
continue
c_max.append(cnt)
cv2.drawContours(thresh, c_max, -1, (255, 255, 255), thickness=-1)
# 4.保存去除小连通域和大连通域的二值化图片
#cv2.imwrite("H:/wj/pictures/001159_area.jpg", thresh)
# 六、将彩色图片去除小连通域和大连通域
final = cv2.bitwise_and(result, result, mask=thresh)
return final
# 定义批量处理图片函数
def imgProcess(filePath):
for filename in os.listdir(filePath):
img = cv2.imread(filePath + "/" + filename)
final = segmentFunc(img)
path = os.path.dirname(filePath) + "/result"
if os.path.exists(path) == False:
os.makedirs(path)
cv2.imwrite(path + "/" + filename, final)
# 主函数测试
if __name__ == "__main__":
imgProcess("H:/wj/pictures")
效果:
4. 去除大连通域会导致大目标的漏检
otsu.py
仅去除小连通域
import cv2
import os
def ostu(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 高斯滤波
blur = cv2.GaussianBlur(gray, (5,5), 0)
# 1. 方法选择为THRESH_OTSU
ret1, th1 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#保存OTSU处理的图片
# cv2.imwrite("H:/wj/pictures/000014_otsu_bin.jpg", th1)
# 3. 去除孤立区域
h, w, _ = image.shape
# Find Contour
contours, hierarchy = cv2.findContours( th1, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 需要搞一个list给cv2.drawContours()才行!!!!!
c_max = []
for i in range(len(contours)):
cnt = contours[i]
area = cv2.contourArea(cnt)
# 处理掉小的轮廓区域,这个区域的大小自己定义。
if(area < (h/40*w/40)):
c_min = []
c_min.append(cnt)
# thickness不为-1时,表示画轮廓线,thickness的值表示线的宽度。
cv2.drawContours(th1, c_min, -1, (0,0,0), thickness=-1)
continue
#
c_max.append(cnt)
cv2.drawContours(th1, c_max, -1, (255, 255, 255), thickness=-1)
final = cv2.bitwise_and(image, image, mask=th1)
return final
# img = th1
# return img
def imgs_process(filePath):
for filename in os.listdir(filePath):
img = cv2.imread(filePath + "/" + filename)
img = ostu(img)
path_ostu = os.path.dirname(filePath) + "/ostu"
if os.path.exists(path_ostu) == False:
os.makedirs(path_ostu)
cv2.imwrite(path_ostu + "/" + filename, img)
if __name__ == "__main__":
imgs_process("H:/wj/pictures")
效果: