阈值分割
简介:
图像分割:1.基于阈值的分割方法,2.基于区域的分割方法3.基于边缘的分割方法
阈值分割适合于那些前景与背景有较强对比度的图,如果不明显可以增大对比度(不知道怎么弄)
方法概述:
全局阈值分割
全局指对整个图像进行,阈值分割指给定一个阈值,大于阈值就放大为255,不大于就变为零,或者可以反过来操作
这里介绍一下python中的一个语法糖:
import numpy as np
import cv2 as cv
# src=np.array([[[123,144,156],[222,32,67]],[[123,144,156],[222,32,67]]],dtype=np.uint8)
src=cv.imread(r'C:\Users\19583\Desktop\3.png',cv.IMREAD_GRAYSCALE)
src[src>180]=255
src[src<=180]=0#这个语法糖爱了
cv.imshow('1',src)
cv.waitKey(0)
上面这个写法对于所有类型的矩阵都行,直接遍历
然后opencv也提供了现成的函数threshold:
注意返回值,第一个是阈值,第二个才是矩阵
import numpy as np
import cv2 as cv
# src=np.array([[[123,144,156],[222,32,67]],[[123,144,156],[222,32,67]]],dtype=np.uint8)
src=cv.imread(r'C:\Users\19583\Desktop\3.png',cv.IMREAD_COLOR)
a,gj1=cv.threshold(src,100,255,cv.THRESH_BINARY)
a,gj2=cv.threshold(src,100,255,cv.THRESH_BINARY_INV)
a,gj3=cv.threshold(src,100,255,cv.THRESH_MASK)
#a,gj4=cv.threshold(src,100,255,cv.THRESH_OTSU)
a,gj5=cv.threshold(src,100,255,cv.THRESH_TOZERO)
a,gj6=cv.threshold(src,100,255,cv.THRESH_TOZERO_INV)
#a,gj7=cv.threshold(src,100,255,cv.THRESH_TRIANGLE)
#a,gj8=cv.threshold(src,100,255,cv.THRESH_TRUNC)
cv.imshow('1',gj1)
cv.imshow('2',gj2)
cv.imshow('3',gj3)
#cv.imshow('4',gj4)
cv.imshow('5',gj5)
cv.imshow('6',gj6)
#cv.imshow('7',gj7)
#cv.imshow('8',gj8)
cv.waitKey(0)
_INV是反过来
THRESH_BINARY模式:大于阈值取max,小于等于阈值取0
src = np.array([[123,234,68],[33,51,17],[48,98,234],
[129,89,27],[45,167,134]],np.uint8)
#手动设置阈值
the = 150
maxval = 255
dst = cv.threshold(src,the,maxval,cv.THRESH_BINARY)
# Otsu 阈值处理
otsuThe = 0
otsuThe,dst_Otsu = cv.threshold(src,otsuThe,maxval,cv.THRESH_OTSU)#先用otsh得到阈值再用binary(默认是这个)
print(otsuThe,dst_Otsu)
# TRIANGLE 阈值处理
triThe = 0
triThe,dst_tri = cv.threshold(src,triThe,maxval,cv.THRESH_TRIANGLE+cv.THRESH_BINARY_INV)#先用training得到阈值再用binary_INV
print(triThe,dst_tri)
98.0 [[255 255 0]
[ 0 0 0]
[ 0 0 255]
[255 0 0]
[ 0 255 255]]
232.0 [[255 0 255]
[255 255 255]
[255 255 0]
[255 255 255]
[255 255 255]]
还要说明一点,otsu,training要求,类型为uint8
其他模式不讲了
局部阈值分割
不同点的阈值不一样,关键在于各点阈值的计算
直方图技术法
我们的目的是找到下图的位置:
但是实际情况有时并不会像上面这样,比如可能两个波峰之间会有好几个波谷,而且他们都是最小值,一种思路是对直方图进行高斯平滑处理,但是这样要调好参数使之只有一个波谷,需要手工调,不好,下面介绍一种自动的方法:
思路就是先找到最高的峰,这个很容易取频数最大的灰度值就行了,然后找第二个峰,怎么找?先对原直方图进行量化(不影响原直方图),这个量化与离第一个峰的距离和该点的频数有关(具体的在代码中给出),然后截取两个波峰之间的区域,找出中间频数最低的那个灰度值,如果有多个波峰,就取最左边的那个。这个就是分割的阈值,最好是处理前看一下直方图的形状,有些图压根分不开前景和背景。
python
import numpy as np
import cv2 as cv
def calcGrayHist(img):#获得直方图
rows,cols=img.shape
grayhist=np.zeros((256,1),dtype=np.float32)
for r in range(rows):
for c in range(cols):
grayhist[img[r][c]]+=1
return grayhist
src=cv.imread(r'C:\Users\19583\Desktop\4.jpg',cv.IMREAD_GRAYSCALE)
src=cv.resize(src,(500,500))
#print(calcGrayHist(src))
def threshTowPeaks(img):
histgram=calcGrayHist(img)
maxloc=np.where(histgram==max(histgram))
print(maxloc)
firstpeak=maxloc[0][0]#取出最大值的灰度值,第一个峰
print(firstpeak)
measuredists=np.zeros(256,np.float32)
for k in range(256):
measuredists[k]=pow(k-firstpeak,2)*histgram[k]#用到第一个峰的的距离和该点灰度值来量化
print(measuredists)
maxloc1=np.where(measuredists==max(measuredists))
secondpeak=maxloc1[0][0]#第二个峰
print(secondpeak)
#thresh = 0
#根据第二个峰与第一个峰的位置关系分类
if firstpeak > secondpeak:
temp = histgram[int(secondpeak):int(firstpeak)]#取出之间的区域
minLoc = np.where(temp == np.min(temp))#找到最小值对应的灰度值(截取的从零开始记,所以这个值是相对的)
thresh = secondpeak + minLoc[0][0] + 1#找到最小值对应的灰度值
else:
temp = histgram[int(firstpeak):int(secondpeak)]
minLoc = np.where(temp == np.min(temp))
thresh = firstpeak + minLoc[0][0] + 1
threhimg=img.copy()
print(thresh)
threhimg[threhimg>thresh]=255
threhimg[threhimg <= thresh] = 0
return thresh,threhimg/255#返回阈值和处理好的图
a,dst=threshTowPeaks(src)
cv.imshow('src',src)
cv.imshow('dst',dst)
cv.waitKey(0)
(array([11], dtype=int64), array([0], dtype=int64))
11
[0.0000000e+00 1.4000000e+03 4.9410000e+03 1.5680000e+04 2.5333000e+04
2.9412000e+04 2.9925000e+04 2.4544000e+04 1.6974000e+04 7.6680000e+03
1.9890000e+03 0.0000000e+00 1.8450000e+03 6.4960000e+03 1.1169000e+04
1.9136000e+04 3.4175000e+04 5.2416000e+04 7.0805000e+04 9.5872000e+04
1.1161800e+05 1.3160000e+05 1.4144900e+05 1.5696000e+05 1.6832400e+05
1.7816400e+05 1.9845000e+05 2.1452800e+05 2.4507200e+05 2.7734400e+05
3.1190400e+05 3.2840000e+05 3.2854500e+05 3.4218800e+05 3.8669900e+05
3.9225600e+05 4.5187500e+05 4.8536800e+05 5.1030000e+05 5.8564800e+05
5.8197200e+05 5.5170000e+05 5.8044400e+05 7.1168000e+05 6.8280300e+05
7.6527200e+05 9.2487500e+05 1.0108800e+06 1.0130600e+06 1.1032160e+06
1.0829520e+06 1.2112000e+06 1.1918290e+06 1.1818800e+06 1.2000010e+06
1.2796960e+06 1.2048750e+06 1.3732840e+06 1.5153740e+06 1.6773120e+06
2.0072360e+06 2.0975000e+06 2.2134510e+06 2.5255360e+06 2.7387750e+06
2.9130840e+06 3.2004500e+06 3.5091840e+06 3.8208240e+06 4.0401640e+06
4.0762510e+06 4.2372000e+06 4.5284570e+06 4.8511280e+06 4.6794510e+06
4.4236800e+06 4.3559750e+06 4.2688800e+06 4.2241490e+06 4.5823840e+06
4.6467360e+06 4.6991000e+06 5.4291570e+06 5.9823360e+06 6.5919730e+06
7.0640400e+06 7.7568750e+06 8.7737440e+06 9.4864000e+06 9.8013240e+06
1.0285168e+07 1.0758400e+07 1.1849166e+07 1.1787172e+07 1.2606870e+07
1.2721968e+07 1.2911075e+07 1.3712184e+07 1.4509773e+07 1.5023360e+07
1.4693455e+07 1.5681600e+07 1.5816710e+07 1.5057456e+07 1.5395220e+07
1.5330460e+07 1.5369575e+07 1.5879168e+07 1.6955018e+07 1.7316012e+07
1.7524188e+07 1.7780000e+07 1.7198886e+07 1.7405892e+07 1.6794048e+07
1.7381312e+07 1.6250850e+07 1.5752872e+07 1.5055435e+07 1.4323392e+07
1.2475050e+07 1.1168300e+07 9.7212690e+06 7.9027200e+06 7.4187890e+06
7.6676400e+06 6.9960250e+06 7.3469760e+06 7.7069070e+06 8.3822480e+06
9.9693440e+06 1.1203200e+07 1.0702571e+07 1.1788128e+07 1.2012426e+07
1.2823584e+07 1.4265625e+07 1.4590044e+07 1.3854811e+07 1.5024128e+07
1.6191693e+07 1.5801500e+07 1.7452736e+07 1.8504288e+07 2.1828226e+07
2.3468492e+07 2.4038776e+07 2.8058432e+07 2.7684276e+07 2.6871084e+07
2.7686992e+07 2.8341600e+07 2.8270782e+07 3.0205672e+07 3.1286970e+07
3.2514048e+07 3.4859448e+07 3.3935072e+07 3.1505922e+07 3.2001744e+07
3.2191450e+07 2.9205000e+07 3.1465380e+07 3.2715264e+07 3.0642380e+07
3.1067960e+07 3.1640924e+07 3.2147856e+07 3.3818428e+07 3.6347584e+07
3.7668688e+07 3.6121600e+07 3.6626372e+07 3.6269208e+07 3.7807688e+07
3.8326800e+07 3.9775724e+07 3.8302840e+07 3.6925036e+07 3.5534016e+07
3.8100376e+07 3.7916800e+07 3.7984060e+07 3.7275840e+07 3.7351392e+07
3.7481688e+07 3.5341248e+07 3.8781952e+07 3.7939420e+07 3.7260384e+07
3.7263684e+07 3.6028800e+07 3.9509768e+07 3.7628864e+07 4.0890068e+07
4.0356352e+07 4.0488176e+07 3.8263176e+07 3.6297824e+07 3.3930240e+07
3.3399136e+07 3.2309500e+07 3.0315712e+07 2.9048832e+07 2.5850806e+07
2.7060284e+07 2.3347350e+07 2.5008816e+07 2.4449670e+07 2.4267276e+07
2.6017856e+07 2.5000000e+07 2.2907368e+07 1.8606624e+07 2.0975380e+07
2.0932848e+07 2.0172000e+07 1.6719784e+07 1.5382791e+07 1.3498368e+07
1.1750189e+07 1.1113200e+07 1.0373393e+07 9.5281280e+06 9.7543350e+06
7.9685040e+06 5.9630250e+06 4.8055680e+06 3.5316750e+06 8.5543200e+05
1.4388300e+06 8.7120000e+05 8.3029700e+05 7.3926000e+05 6.4647700e+05
7.0246400e+05 9.1125000e+05 8.6829200e+05 1.0305800e+06 1.0396800e+06
9.9637900e+05 5.8190000e+05 7.4705400e+05 1.1303040e+06 9.2291300e+05
1.1498760e+06 1.2149500e+06 1.5594880e+06 1.0672110e+06 1.1895240e+06
1.7707510e+06 1.6704000e+06 2.0909160e+06 5.0950680e+06 8.0897130e+06
5.3463328e+07]
255
242
熵算法
python实现
import cv2 as cv
import numpy as np
import math
src=cv.imread(r'C:\Users\19583\Desktop\4.jpg',cv.IMREAD_GRAYSCALE)
src=cv.resize(src,(500,500))
def threshentroy(img):
rows,cols=img.shape
#获得灰度直方图
threshgram=np.ones(256,dtype=np.float32)#这里选一防止后面log真值为0,差个1应该没关系吧
for r in range(rows):
for c in range(cols):
threshgram[img[r][c]]+=1
threshentroygram=np.zeros(256,dtype=np.float32)
threshgram/=np.sum(threshgram)
for i in range(1,256,1):
#重新归一化
H=threshgram[0:i+1].copy()
T=threshgram[i+1:].copy()
#print(np.sum(H))
if np.sum(H)==0:
H=0
else:
H/=np.sum(H)
if np.sum(T)==0:
T=0
else:
T/=np.sum(T)
#定义两个变量存前后两个熵的值
f1=0
f2=0
#计算两部分的值
for j in range(i+1):
f1+=-H[j]*math.log(H[j],2.71)
for j in range(255-i):
f2+=-T[j]*math.log(T[j],2.71)
threshentroygram[i]=f1+f2
thresh=np.where(threshentroygram==max(threshentroygram))
ret=thresh[0][0]
img1=img.copy()
img1[img1>ret]=255
img1[img1<=ret]=0
return ret,img1
thresh,dst=threshentroy(src)
print(thresh)
cv.imshow('src',src)
cv.imshow('dst',dst)
cv.waitKey(0)
117
otsu阈值处理
python
import numpy as np
import cv2
import math
#计算图像灰度直方图
def calcGrayHist(image):
#灰度图像矩阵的宽高
rows,cols = image.shape
#存储灰度直方图
grayHist = np.zeros([1,256],np.uint32)
for r in range(rows):
for c in range(cols):
grayHist[0][image[r][c]] +=1
return grayHist
def ostu(image):
rows,cols = image.shape
#计算图像的灰度直方图
grayHist = calcGrayHist(image)
#归一化灰度直方图
uniformGrayHist = grayHist/float(rows*cols)
#计算零阶累积矩(sigma概率)和一阶累积矩(sigma概率乘灰度值)
zeroCumuMoment = np.zeros([1,256],np.float32)
oneCumuMoment = np.zeros([1,256],np.float32)
for k in range(256):
if k == 0:
zeroCumuMoment[0][k] = uniformGrayHist[0][0]
oneCumuMoment[0][k] = (k+1)*uniformGrayHist[0][0]
else:
zeroCumuMoment[0][k] = zeroCumuMoment[0][k-1] + uniformGrayHist[0][k]
oneCumuMoment[0][k] = oneCumuMoment[0][k-1] + k*uniformGrayHist[0][k]
#计算类间方差
variance = np.zeros([1,256],np.float32)
for k in range(255):
if zeroCumuMoment[0][k] == 0:
variance[0][k] = 0
else:
variance[0][k] = math.pow(oneCumuMoment[0][255]*zeroCumuMoment[0][k] - oneCumuMoment[0][k],2)/(zeroCumuMoment[0][k]*(1.0-zeroCumuMoment[0][k]))
#找到阈值
threshLoc = np.where(variance[0][0:255] == np.max(variance[0][0:255]))
thresh = threshLoc[0]
#阈值处理
threshold = np.copy(image)
print(thresh)
threshold[threshold > thresh] = 255
threshold[threshold <= thresh] = 0
return threshold
src=cv2.imread(r'C:\Users\19583\Desktop\4.jpg',cv2.IMREAD_GRAYSCALE)
src=cv2.resize(src,(500,500))
dst=ostu(src)
cv2.imshow('src',src)
cv2.imshow('dst',dst)
cv2.waitKey(0)
opencv中提供了现成的函数
import cv2 as cv
import numpy as np
src=cv.imread(r'C:\Users\19583\Desktop\3.jpg',cv.IMREAD_GRAYSCALE)
src=cv.resize(src,(500,500))
thresh,dst=cv.threshold(src,0,255,type=cv.THRESH_OTSU|cv.THRESH_BINARY)#这个0是随便写的,OTSU模式加会用otsu得到的阈值做二值处理
print(thresh)
cv.imshow('src',src)
cv.imshow('dst',dst)
cv.waitKey(0)
139.0
自适应阈值分割
如果一个图像光照不均(没有明显的双峰)用上面的方法就不好了,用自适应阈值分割就好了,根据不同地方周围的情况不同地方的阈值不一样。
以平滑的值作为阈值,根据经验,平滑的算子大小要大于被识别物体的大小。
自适应操作过程
- 对图像进行平滑处理结果存在一个矩阵中
- 将(1-a)*b作为各点的阈值(a为调整用的参数一般取0.15,b是对应位置的平滑结果)
python实现:
import cv2 as cv
import numpy as np
src=cv.imread(r'C:\Users\19583\Desktop\4.jpg',cv.IMREAD_GRAYSCALE)
src=cv.resize(src,(500,500))
def adaptiveThresh(img,size,x):
a = cv.GaussianBlur(img, size, 2, borderType=cv.BORDER_REFLECT)
rows,cols=img.shape
img1=img.copy()
for r in range(rows):
for c in range(cols):
if img1[r][c]>a[r][c]*(1-x):
img1[r][c]=255
else:
img1[r][c]=0
return img1
dst1=adaptiveThresh(src,(3,3),0.15)
dst2=adaptiveThresh(src,(23,23),0.15)
cv.imshow('src',src)
cv.imshow('dst1',dst1)
cv.imshow('dst2',dst2)
cv.waitKey(0)
opencv自带函数:
import cv2 as cv
import numpy as np
src=cv.imread(r'C:\Users\19583\Desktop\4.jpg',cv.IMREAD_GRAYSCALE)
src=cv.resize(src,(500,500))
dst1=cv.adaptiveThreshold(src,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,thresholdType=cv.THRESH_BINARY,blockSize=3,C=0.15)#图,最大值(阈值分割后),平滑方法(只有均值和高斯),取阈值方法,算子大小.C是那个a
cv.imshow('src',src)
cv.imshow('dst1',dst1)
cv.waitKey(0)
二值图的逻辑运算
对图像进行阈值分割后得到二值图,可以进行或,与的运算,opencv提供了现成的函数:
很简单就直接放图了