图像阈值化
<h1>目标</h1>
<p>在本教程中,您将学习简单的阈值设置、自适应阈值设置和Otsu的阈值设置。
你将学习cv.threshold和cv.adaptiveThreshold。</p>
<h1>简单的阈值化</h1>
<p>需要做的事情很简单。对于每个像素,应用相同的阈值。如果像素值小于阈值,则将其设置为0,否则设置为最大值。函数 cv.threshold用来阈值化。第一个参数是源图像,它应该是灰度图像。第二个参数是用于对像素值进行分类的阈值。第三个参数是分配给超过阈值的像素值的最大值。OpenCV提供了不同类型的阈值,阈值由函数的第四个参数给出。上面描述的基本阈值是通过使用cv.THRESH_BINARY来完成的。所有简单的阈值类型都是:</p>
<ul>
<li>cv.THRESH_BINARY</li>
<li>cv.THRESH_BINARY_INV</li>
<li>cv.THRESH_TRUNC</li>
<li>cv.THRESH_TOZERO</li>
<li>cv.THRESH_TOZERO_INV</li>
</ul>
<p>该方法返回两个输出。第一个是使用的阈值,第二个输出是经过阈值处理的图像。</p>
<p>这段代码比较了不同的简单阈值类型:</p>
<pre><code class="language-python">import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('gradient.png',0)
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in xrange(6):
plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()</code></pre>
<pre><code>为了绘制多个图像,我们使用了pl .subplot()函数。请检查matplotlib文档以了解更多细节。</code></pre>
<p>该代码产生如下结果:</p>
<p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/ced872a64d396b8fa89b8399e6e9da2f?showdoc=.jpg" alt="" /></p>
<h1>自适应阈值化</h1>
<p>在上一节中,我们使用一个全局值作为阈值。但这并不适用于所有情况,例如,如果一张图像在不同的区域有不同的光照条件。在这种情况下,自适应阈值阈值可以提供帮助。这里,算法根据像素周围的小区域来确定像素的阈值。因此,对于同一幅图像的不同区域,我们得到了不同的阈值,对于光照不同的图像,我们得到了更好的结果。</p>
<p>除上述参数外,函数cv.adaptiveThreshold有三个输入参数:</p>
<p>adaptiveMethod决定阈值如何计算:</p>
<ul>
<li>cv.ADAPTIVE_THRESH_MEAN_C: 阈值是邻域面积减去常数C的平均值。</li>
<li>cv.ADAPTIVE_THRESH_GAUSSIAN_C: 阈值是邻域值减去常数C的高斯加权和。</li>
</ul>
<p>块的大小决定了邻域的大小,C是从邻域像素的均值或加权和中减去的常数。</p>
<p>下面的代码比较了具有不同光照的图像的全局阈值和自适应阈值:</p>
<pre><code class="language-python">import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('sudoku.png',0)
img = cv.medianBlur(img,5)
ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv.THRESH_BINARY,11,2)
titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]
for i in xrange(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()</code></pre>
<p>结果:</p>
<p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/ad98adb8707df95b227f0f3e382ade31?showdoc=.jpg" alt="" /></p>
<h1>最大类间方差法(大津算法, Otus)的二值化</h1>
<p>在全局阈值化中,我们使用一个任意选择的值作为阈值。相反,最大类间方差法(大津算法, Otus)的方法避免了必须选择一个值并自动确定它。</p>
<p>考虑只有两个不同图像值的图像(双峰图像),其中直方图只包含两个峰值。一个好的阈值应该位于这两个值的中间。同样,Otsu的方法从图像直方图中确定一个最优的全局阈值。</p>
<p>为此,使用cv.threshold()函数,其中cv.THRESH_OTSU作为一个额外的标志传递。阈值可以任意选择。然后,该算法找到作为第一个输出返回的最优阈值。</p>
<p>看看下面的例子。输入图像是一个有噪声的图像。在第一种情况下,应用值为127的全局阈值。在第二种情况下,直接应用Otsu的阈值。在第三种情况下,首先用5x5高斯核对图像进行滤波,去除噪声,然后应用最大类间方差法(大津算法, Otus)阈值。看看噪声滤波如何改进结果。</p>
<pre><code class="language-python">import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png',0)
# global thresholding
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu's thresholding
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# plot all the images and their histograms
images = [img, 0, th1,
img, 0, th2,
blur, 0, th3]
titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
'Original Noisy Image','Histogram',"Otsu's Thresholding",
'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
for i in xrange(3):
plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()</code></pre>
<p>结果:
<img src="https://www.showdoc.cc/server/api/common/visitfile/sign/8f061d6452f9ac2d64e4cf01c6f07b12?showdoc=.jpg" alt="" /></p>
<h1>最大类间方差法(大津算法, Otus)的二值化是如何运作的?</h1>
<p>本节将演示Otsu二值化的Python实现,以展示它实际上是如何工作的。如果你不感兴趣,可以跳过这个。</p>
<p>由于我们处理的是双峰图像,Otsu算法试图找到一个阈值(t),该阈值最小化关系给出的加权类内方差:</p>
<p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/30e6cf07ab888c559495eb2accaea667?showdoc=.jpg" alt="" /></p>
<p>它实际上找到了一个位于两个峰值之间的t值,使得这两个类的方差最小。它可以简单地用Python实现如下:</p>
<pre><code class="language-python">img = cv.imread('noisy2.png',0)
blur = cv.GaussianBlur(img,(5,5),0)
# find normalized_histogram, and its cumulative distribution function
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in xrange(1,256):
p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
b1,b2 = np.hsplit(bins,[i]) # weights
# finding means and variances
m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2
# calculates the minimization function
fn = v1*q1 + v2*q2
if fn < fn_min:
fn_min = fn
thresh = i
# find otsu's threshold value with OpenCV function
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print( "{} {}".format(thresh,ret) )</code></pre>
<h1>其它资源</h1>
<ol>
<li>数字图像处理,Rafael C. Gonzalez</li>
</ol>
<h1>练习题</h1>
<p>有一些优化Otsu的二值化可用。您可以搜索并实现它。</p>