閾值分割
一: 全閾值分割
實例代碼:
image = cv2.imread('img.jpg', cv2.IMREAD_GRAYSCALE)
the = 100 # 設置閾值爲100
maxval = 255
dst, img = cv2.threshold(image, the, maxval, cv2.THRESH_BINARY)
cv2.imshow('hand_thresh', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
給出你的閾值 ,然後告訴你的最大閾值是多少 。。。也就是你二值圖中一個閾值爲0,另外一個閾值可以指定爲多少。。這裏指定爲255
看一下輸出結果。。
二:局部閾值分割
局部閾值分割的核心是計算閾值矩陣。。比較常用的是後面提到的自適應閾值算法。。我們等會後面講實現。。
一個很好的例子:https://blog.csdn.net/qq_16540387/article/details/78892080
先給出一個MATLAB實現
由於光照的影響,圖像的灰度可能是不均勻分佈的,此時單一閾值的方法分割效果不好。Yanowitz提出了一種局部閾值分割方法。結合邊緣和灰度信息找到閾值表面(treshhold surface)。在閾值表面上的就是目標。
算法的主要步驟如下:
- step1:均值平滑圖像
- step2:求平滑圖像的梯度圖
- step3:運用Laplacian算子,找到具有局部最大閾值的點,這些點的原始灰度值就是候選的局部閾值。
- step4 :採樣候選點,灰度值替換。將平滑圖像中的候選點灰度值替換爲原始圖像中的灰度值或者更大一點的值。這麼做的目的是不會檢測到虛假目標,因而會損失一部分真實的目標。
- step5:插值灰度點,得到閾值表面。
其中,只有當時,殘差消失(residual vanish)。 時收斂更快。爲拉普拉斯算子,強迫任意點的幾何意義是使得曲線光滑。光滑曲線的梯度是連續變化的,因而其二次導數爲0。
- step6:閾值表面分割圖像
- step7:校正。由於光照和噪聲,閾值表面和原始原始灰度曲線可能相交如下圖所示。可以看到分割結果中出現 “ghost” 目標,應該予以去除。去除的原理是,這些虛假的目標邊緣梯度值應該較小。因而,可以根據分割的結果,標記所有連通區域,注意背景和目標應該分開標記。比較標記部分邊緣在梯度圖中的值,如果某個目標的邊緣梯度的平均值不超過某個閾值,則去除這個目標。
matlab 代碼
function [P_new,label]=Local_Yanowitz(I,varargin);
% local adaptive treshold segment by Yanowitz
% input:
% I is Image M by N
% writed by radishgiant
%reference:S. D. Yanowitz and A. M. Bruckstein, "A new method for image
% segmentation," Comput. Graph. Image Process. 46, 82–95 ,1989.
dbstop if error
if nargin<=1||isempty(varargin{1});
hsize=[3,3];
end
if nargin<=2||isempty(varargin{2});
MaxInterNum=15500;
end
if nargin<=3||isempty(varargin{3});
InterTreshhold=10e-6;
end
if nargin<=4||isempty(varargin{4});
GradTresh=20;
end
% I=double(I);
[M,N]=size(I);
% step1:smooth the image
h1=fspecial('average',hsize);
SI=imfilter(I,h1);
%step2: calculate gradiant map
[Fx,Fy]=gradient(double(SI));
F=sqrt(Fx.^2+Fy.^2);
%step3 :Laplacian Image
h2=fspecial('laplacian',0);
LI=imfilter(SI,h2);
%step4:sample the smoothed image at the places which the maximal
%gradiant-mask points
P_new=zeros(M,N);
P_new(LI==0)=I(LI==0);
% step5: interpolate the sampled gray level over the image
Residual=InterTreshhold+1;
InterNum=0;
while (Residual>InterTreshhold)
if(InterNum>MaxInterNum)
fprintf('up to MaxInterNum without diveregence');
break;
end
InterNum=InterNum+1;
P_last=P_new;
R=imfilter(P_new,h2);
P_new=P_new+R./4;
Residual=mean(abs(P_new(:)-P_last(:)));
end
% step:6 segment the Image
bw=zeros(M,N);
bw(I>P_new)=255;%background
figure,imshow(bw);title('first segment result')
% step:7 validation progress
label=bwlabel(bw,4);
RGBLabel=label2rgb(label);
figure,imshow(RGBLabel);title('connected component');
lable_n=length(unique(label));
gradientmean=zeros(lable_n,1);
toglabel=zeros(lable_n,1);
for ci=0:lable_n-1
temp=zeros(size(I));
temp(label==ci)=255;
eg=edge(temp);
gradientmean(ci+1)=mean(F(eg==1));
[egr,egc]=find(eg==1);
[~,mingI]=min(F(eg>=1));% find the location of gradient of min value in eg
mingr=egr(mingI);%find the location of gradient of min value over image
mingc=egc(mingI);
nearborlabel=[mingr+1,mingc;mingr-1,mingc;mingr,mingc+1;mingr,mingc-1];
nearborlogical=ones(4,1);
if (mingr==1)
nearborlogical(2)=0;
end
if (mingr==M)
nearborlogical(1)=0;
end
if (mingc==1)
nearborlogical(4)=0;
end
if mingc==N
nearborlogical(3)=0;
end
nearborlabel=nearborlabel(nearborlogical==1,:);
nearborlabel=label(sub2ind([M,N],nearborlabel(:,1),nearborlabel(:,2)));
dlilabel=label(mingr,mingc);
if nnz(nearborlabel~=dlilabel)
toglabel(ci+1)=mode(nearborlabel(nearborlabel~=dlilabel));
else
toglabel(ci+1)=dlilabel;
end
end
dli=find(gradientmean<GradTresh);
% find background label
bl=mode(label(bw==255));
for di=1:length(dli)
label(label==dli(di))=toglabel(dli(di));
end
RGBLabel=label2rgb(label);
figure,imshow(RGBLabel);title('segment result after valiation');
figure,plot(1:N,I(mingr,:),1:N,P_new(mingr,:));
legend('gray level','Treshold surface');
end
參考文獻
[1]S. D. Yanowitz and A. M. Bruckstein, “A new method for image
segmentation,” Comput. Graph. Image Process. 46, 82–95 ,1989.
三:直方圖技術法
代碼實現:
import numpy as np
import cv2
def calcGrayHist(image):
'''
統計像素值
:param image:
:return:
'''
# 灰度圖像的高,寬
rows, cols = image.shape
# 存儲灰度直方圖
grayHist = np.zeros([256], np.uint64)
for r in range(rows):
for c in range(cols):
grayHist[image[r][c]] += 1
return grayHist
def threshTwoPeaks(image):
# 計算灰度直方圖
histogram = calcGrayHist(image)
# 找到灰度直方圖的最大峯值對應的灰度值
maxLoc = np.where(histogram == np.max(histogram))
firstPeak = maxLoc[0][0]
# 尋找灰度直方圖的第二個峯值對應的灰度值
measureDists = np.zeros([256], np.float32)
for k in range(256):
measureDists[k] = pow(k - firstPeak, 2)*histogram[k]
maxLoc2 = np.where(measureDists == np.max(measureDists))
secondPeak = maxLoc2[0][0]
# 找兩個峯值之間的最小值對應的灰度值,作爲閾值
thresh = 0
if firstPeak > secondPeak:
temp = histogram[int(secondPeak): int(firstPeak)]
minLoc = np.where(temp == np.min(temp))
thresh = secondPeak + minLoc[0][0] + 1
else:
temp = histogram[int(firstPeak): int(secondPeak)]
minLoc = np.where(temp == np.min(temp))
thresh = firstPeak + minLoc[0][0] + 1
# 找到閾值,我們進行處理
img = image.copy()
img[img > thresh] = 255
img[img <= thresh] = 0
cv2.imshow('deal_image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
image = cv2.imread('img.jpg', cv2.IMREAD_GRAYSCALE)
threshTwoPeaks(image)
輸出結果:
四:熵算法
代碼實現:
import numpy as np
import cv2
import math
def calcGrayHist(image):
'''
統計像素值
:param image:
:return:
'''
# 灰度圖像的高,寬
rows, cols = image.shape
# 存儲灰度直方圖
grayHist = np.zeros([256], np.uint64)
for r in range(rows):
for c in range(cols):
grayHist[image[r][c]] += 1
return grayHist
def threshEntroy(image):
rows, cols = image.shape
# 求灰度直方圖
grayHist = calcGrayHist(image)
# 歸一化灰度直方圖,即概率直方圖
normGrayHist = grayHist / float(rows*cols)
# 第一步:計算累加直方圖,也稱零階累積矩
zeroCumuMoment = np.zeros([256], np.float32)
for k in range(256):
if k == 0:
zeroCumuMoment[k] = normGrayHist[k]
else:
zeroCumuMoment[k] = zeroCumuMoment[k-1] + normGrayHist[k]
# 第二步:計算各個灰度級的熵
entropy = np.zeros([256], np.float32)
for k in range(256):
if k == 0:
if normGrayHist[k] == 0:
entropy[k] = 0
else:
entropy[k] = -normGrayHist[k]*math.log10(normGrayHist[k])
else:
if normGrayHist[k] == 0:
entropy[k] = entropy[k-1]
else:
entropy[k] = entropy[k-1] - normGrayHist[k]*math.log10(normGrayHist[k])
# 第三步:找閾值
fT = np.zeros([256], np.float32)
ft1, ft2 = 0.0, 0.0
totalEntropy = entropy[255]
for k in range(255):
# 找最大值
maxFront = np.max(normGrayHist[0: k+1])
maxBack = np.max(normGrayHist[k+1: 256])
if (maxFront == 0 or zeroCumuMoment[k] == 0
or maxFront == 1 or zeroCumuMoment[k] == 1 or totalEntropy == 0):
ft1 = 0
else:
ft1 = entropy[k] / totalEntropy*(math.log10(zeroCumuMoment[k])/math.log10(maxFront))
if (maxBack == 0 or 1-zeroCumuMoment[k] == 0
or maxBack == 1 or 1-zeroCumuMoment[k] == 1):
ft2 = 0
else:
if totalEntropy == 0:
ft2 = (math.log10(1-zeroCumuMoment[k]) / math.log10(maxBack))
else:
ft2 = (1-entropy[k]/totalEntropy)*(math.log10(1-zeroCumuMoment[k])/math.log10(maxBack))
fT[k] = ft1 + ft2
# 找最大值的索引,作爲得到的閾值
threshLoc = np.where(fT == np.max(fT))
thresh = threshLoc[0][0]
# 閾值處理
threshold = np.copy(image)
threshold[threshold > thresh] = 255
threshold[threshold <= thresh] = 0
return threshold
if __name__ == '__main__':
image = cv2.imread('img5.jpg', cv2.IMREAD_GRAYSCALE)
img = threshEntroy(image)
cv2.imshow('origin', image)
cv2.imshow('deal_image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
輸出結果:
五:Otsu算法
這裏就不具體實現了。。我們調用opencv給的API
image = cv2.imread('img.jpg', cv2.IMREAD_GRAYSCALE)
maxval = 255
otsuThe = 0
otsuThe, dst_Otsu = cv2.threshold(image, otsuThe, maxval, cv2.THRESH_OTSU)
cv2.imshow('Otsu', dst_Otsu)
cv2.waitKey(0)
cv2.destroyAllWindows()
輸出結果:
六:自適應閾值算法
代碼實現:
import cv2
import numpy as np
def adaptiveThresh(I, winSize, ratio=0.15):
# 第一步:對圖像矩陣進行均值平滑
I_mean = cv2.boxFilter(I, cv2.CV_32FC1, winSize)
# 第二步:原圖像矩陣與平滑結果做差
out = I - (1.0 - ratio) * I_mean
# 第三步:當差值大於或等於0時,輸出值爲255;反之,輸出值爲0
out[out >= 0] = 255
out[out < 0] = 0
out = out.astype(np.uint8)
return out
if __name__ == '__main__':
image = cv2.imread('img7.jpg', cv2.IMREAD_GRAYSCALE)
img = adaptiveThresh(image, (5, 5))
cv2.imshow('origin', image)
cv2.imshow('deal_image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
結果展示:
OSTU
一、OTSU法(大津閾值分割法)介紹
OTSU算法是由日本學者OTSU於1979年提出的一種對圖像進行二值化的高效算法,是一種自適應的閾值確定的方法,又稱大津閾值分割法,是最小二乘法意義下的最優分割。
二、單閾值OTSU法
設圖像包含L個灰度級,灰度值爲i的像素點個數爲Ni,像素總點數爲:
則灰度值爲i的點的概率爲:
根據期望公式,圖像灰度的均值爲:
按圖像的灰度特性,使用閾值T將圖像分成目標c0和背景c1兩類,則ω0(T)和ω1(T)分別表示閾值爲T時,c0和c1發生的概率,即:
和的均值爲:
表示直方圖中閾值爲T的類間方差,定義爲:
最優閾值定義爲類間方差最大時對應的T值,即:
下面給出python源代碼。
#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()
結果如下所示。可以看到,使用OTSU法計算出來的閾值爲165。
三、多閾值OTSU法
將單閾值的OTSU推廣到多閾值的圖像分割中,假設將圖像直方圖分爲m+1類,對應的閾值爲T1,T2,···,Tm。則最大類間方差爲:
其中,
爲求得最優閾值,需要使用窮舉搜索,隨着m增大,計算量驟增。若使用牛頓迭代等優化搜索方法,容易陷入局部最優解。
四、遺傳算法解OTSU
遺傳算法是一種基於自然選擇和羣體遺傳機理的搜索算法。它模擬了自然選擇和自然遺傳過程中發生的繁殖、交配和突變現象,將每一個可能的解看作是羣體的一個個體,並將每個個體編碼,根據設定的目標函數對每個個體進行評價,給出一個適應度值。開始時隨機產生一些個體,利用遺傳算子產生新一代的個體,新個體繼承上一代的優良性狀,逐步向更優解進化。由於遺傳算法在每一代同時搜索參數空間的不同區域,從而能夠使找到全局最優解的可能性大大增加。遺傳算法屬於啓發式算法,無限趨緊最優解並收斂。
那麼怎麼將圖像分割問題抽象成遺傳問題,即怎麼將問題編碼成基因串,如何構造適應度函數來度量每條基因的適應度值。假設如上述三所示,將圖像分爲m+1類,則m個閾值按順序排列起來構成一個基因串:
由於灰度爲0~255,所以可以使用8位二進制代碼表示每個閾值,此時每個基因串由長度爲8*m個比特位的傳組成。
將類間方差作爲其適應度函數,類間方差越大,適應度函數值就越高。
python代碼如下所示:
#coding:utf-8
import cv2
import numpy as np
from matplotlib import pyplot as plt
import random
#將不足8*m位的染色體擴展爲8*m位
def expand(k, m):
for i in range(len(k)):
k[i] = k[i][:2] + '0'*(8*m+2 - len(k[i])) + k[i][2:len(k[i])]
return k
def Hist(image):
a=[0]*256
h=image.shape[0]
w=image.shape[1]
MN=h*w
average=0.0
for i in range(w):
for j in range(h):
pixel=int(image[j][i])
a[pixel]=a[pixel]+1
for i in range(256):
a[i]=a[i]/float(MN)
average=average+a[i]*i
return a, average
#解析多閾值基因串
def getTh(seed, m):
th = [0, 256]
seedInt = long(seed, 2)
for i in range(0, m):
tmp = seedInt & 255
if tmp != 0:
th.append(tmp)
seedInt = seedInt >> 8
th.sort()
return th
#適應度函數 Ostu全局算法
def fitness(seed, p, average, m):
Var = [0.0] * len(seed)
g_muT = 0.0
for i in range(256):
g_muT = g_muT + i * p[i]
for i in range(len(seed)):
th = getTh(seed[i], m)
for j in range(len(th)-1):
w = [0.0] * (len(th)-1)
muT = [0.0] * (len(th)-1)
mu = [0.0] * (len(th)-1)
for k in range(th[j], th[j+1]):
w[j] = w[j] + p[k]
muT[j] = muT[j] + + p[k] * k
if w[j] > 0:
mu[j] = muT[j] / w[j]
Var[i] = Var[i] + w[j] * pow(mu[j] - g_muT, 2)
return Var
#選擇算子 輪盤賭選擇算法
def wheel_selection(seed, Var):
var = [0.0]*len(Var)
s = 0.0
n = ['']*len(seed)
sumV = sum(Var)
for i in range(len(Var)):
var[i] = Var[i]/sumV
for i in range(1, len(Var)):
var[i] = var[i] + var[i-1]
for i in range(len(seed)):
s = random.random()
for j in range(len(var)):
if s <= var[j]:
n[i] = seed[j]
return n
#單點交叉算子
def Cross(Next, m):
for i in range(0, len(Next) - 1, 2):
if random.random() < 0.7:
if m > 2:
tmp = Next[i][10:]
Next[i] = Next[i][:10] + Next[i+1][10:]
Next[i+1] = Next[i+1][:10] + tmp
else:
tmp = Next[i][6:]
Next[i] = Next[i][:6] + Next[i+1][6:]
Next[i+1] = Next[i+1][:6] + tmp
return Next
#變異算子
def Variation(Next):
for i in range(len(Next)):
if random.random()<0.06:
Next[i]=bin(long(Next[i],2)+2)
return Next
#多閾值分割
def genetic_thres(image, k, m):
th = image
for i in range(image.shape[0]):
for j in range(image.shape[1]):
for t in range(1, len(k)-1):
if k[t-1] <= image[i][j] < k[t]:
th[i][j] = int(k[t-1])
return th
# main
imagesrc = cv2.imread("E:/python/cv/OTSU/test2.jpg")
gray = cv2.cvtColor(imagesrc, cv2.COLOR_BGR2GRAY)
m = 3 #閾值數
items_x = range(0, imagesrc.shape[0])
items_y = range(0, imagesrc.shape[1])
random.shuffle(items_x)
random.shuffle(items_y)
x = items_x[0:20*m] #產生隨機x座標
y = items_y[0:20*m] #產生隨機y座標
seed = []
Var = 0.0
times = 0
k = 0
P, average = Hist(gray) #計算直方圖,P爲各灰度的概率的數組,average爲均值
for i in range(0, 20):
code = long(0)
for j in range(0, m):
code = code + gray[x[i*j]][y[i*j]] << j*8 #將閾值連起來組成一個8*m比特的基因串
seed.append(bin(code)) #生成第一代
while times < 2000:
Var = fitness(seed, P, average, m)
Next = wheel_selection(seed, Var)
Next = Cross(Next, m)
Next = expand(Variation(Next), m)
seed = Next
times = times + 1
for j in range(len(Var)):
if Var[j] == max(Var):
k = getTh(Next[j], m)
print k
plt.subplot(131), plt.imshow(imagesrc, "gray")
plt.title("source image"), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.hist(imagesrc.ravel(), 256)
plt.title("Histogram"), plt.xticks([]), plt.yticks([])
th1 = genetic_thres(gray, k, m)
plt.subplot(133), plt.imshow(th1, "gray")
titleName = ''
for i in range(1, len(k)-1):
titleName = titleName + str(k[i]) + ', '
titleName = titleName[:len(titleName)-2]
plt.title("threshold is " + titleName), plt.xticks([]), plt.yticks([])
plt.show()
這裏使用的是標準二進制,若使用格雷碼,應該能收斂得更好。結果如下所示:
參考:
https://blog.csdn.net/shawroad88/article/details/87965784
https://blog.csdn.net/u010128736/article/details/52801310