OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
图像阈值处理

目标

  • 在本教程中,您将学习简单的阈值处理、自适应阈值处理和 Otsu 阈值处理。
  • 您将学习函数 cv.thresholdcv.adaptiveThreshold

简单阈值处理

这里,事情很简单。对于每个像素,应用相同的阈值。如果像素值小于或等于阈值,则将其设置为 0,否则将其设置为最大值。函数 cv.threshold 用于应用阈值处理。第一个参数是源图像,应该是灰度图像。第二个参数是用于对像素值进行分类的阈值。第三个参数是分配给超过阈值的像素值的最大值。OpenCV 提供了不同类型的阈值处理,由函数的第四个参数给出。如上所述的基本阈值处理是通过使用类型 cv.THRESH_BINARY 完成的。所有简单的阈值处理类型都是

有关差异,请参阅类型的文档。

该方法返回两个输出。第一个是使用的阈值,第二个输出是阈值图像

此代码比较了不同的简单阈值处理类型

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('gradient.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "无法读取文件,请检查 os.path.exists()"
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 = ['原始图像','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6)
plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件加载图像。
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
对每个数组元素应用固定级别的阈值。
注意
为了绘制多张图像,我们使用了 plt.subplot() 函数。请查看 matplotlib 文档以获取更多详细信息。

代码产生了这个结果

image

自适应阈值处理

在前一节中,我们使用一个全局值作为阈值。但这可能并不适用于所有情况,例如,如果图像在不同区域具有不同的光照条件。在这种情况下,自适应阈值处理可以提供帮助。在这里,该算法基于像素周围的小区域确定像素的阈值。因此,我们为同一图像的不同区域获得不同的阈值,这为具有不同光照的图像提供了更好的结果。

除了上述参数之外,方法 cv.adaptiveThreshold 接受三个输入参数

adaptiveMethod 决定了如何计算阈值

blockSize 确定邻域区域的大小,C 是从邻域像素的平均值或加权和中减去的常数。

下面的代码比较了全局阈值处理和自适应阈值处理,用于具有不同光照的图像

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('sudoku.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "无法读取文件,请检查 os.path.exists()"
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 = ['原始图像', '全局阈值处理 (v = 127)',
'自适应均值阈值处理', '自适应高斯阈值处理']
images = [img, th1, th2, th3]
for i in range(4)
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
void medianBlur(InputArray src, OutputArray dst, int ksize)
使用中值滤波器模糊图像。
void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C)
对数组应用自适应阈值。

结果

image

Otsu 的二值化

在全局阈值处理中,我们使用任意选择的值作为阈值。相反,Otsu 的方法避免了选择一个值,并自动确定它。

考虑一个只有两个不同图像值的图像(双峰图像),其中直方图将只包含两个峰值。一个好的阈值将位于这两个值的中间。类似地,Otsu 的方法从图像直方图中确定最佳全局阈值。

为了做到这一点,使用 cv.threshold() 函数,其中 cv.THRESH_OTSU 作为额外的标志传递。阈值可以任意选择。然后,该算法找到最佳阈值,该阈值作为第一个输出返回。

查看下面的示例。输入图像是一个嘈杂的图像。在第一种情况下,应用了值为 127 的全局阈值处理。在第二种情况下,直接应用了 Otsu 的阈值处理。在第三种情况下,首先用 5x5 高斯核过滤图像以去除噪声,然后应用 Otsu 阈值处理。看看噪声过滤如何改善结果。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "无法读取文件,请检查 os.path.exists()"
# 全局阈值处理
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu 的阈值处理
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 高斯滤波后 Otsu 的阈值处理
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 绘制所有图像及其直方图
images = [img, 0, th1,
img, 0, th2,
blur, 0, th3]
titles = ['原始噪声图像','直方图','全局阈值处理 (v=127)',
'原始噪声图像','直方图',"Otsu 的阈值处理",
'高斯滤波图像','直方图',"Otsu 的阈值处理"]
for i in range(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()
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
使用高斯滤波器模糊图像。

结果

image

Otsu 的二值化是如何工作的?

本节演示了 Otsu 二值化的 Python 实现,以显示它的实际工作方式。如果您不感兴趣,可以跳过此部分。

由于我们正在处理双峰图像,Otsu 的算法尝试找到一个阈值 (t),该阈值最小化由以下关系给出的加权类内方差

\[\sigma_w^2(t) = q_1(t)\sigma_1^2(t)+q_2(t)\sigma_2^2(t)\]

其中

\[q_1(t) = \sum_{i=1}^{t} P(i) \quad \& \quad q_2(t) = \sum_{i=t+1}^{I} P(i)\]

\[\mu_1(t) = \sum_{i=1}^{t} \frac{iP(i)}{q_1(t)} \quad \& \quad \mu_2(t) = \sum_{i=t+1}^{I} \frac{iP(i)}{q_2(t)}\]

\[\sigma_1^2(t) = \sum_{i=1}^{t} [i-\mu_1(t)]^2 \frac{P(i)}{q_1(t)} \quad \& \quad \sigma_2^2(t) = \sum_{i=t+1}^{I} [i-\mu_2(t)]^2 \frac{P(i)}{q_2(t)}\]

它实际上找到了一个位于两个峰之间的 t 值,这样两个类的方差都最小。它可以简单地用 Python 实现如下

img = cv.imread('noisy2.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "无法读取文件,请检查 os.path.exists()"
blur = cv.GaussianBlur(img,(5,5),0)
# 找到 normalized_histogram,及其累积分布函数
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.sum()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in range(1,256)
p1,p2 = np.hsplit(hist_norm,[i]) # 概率
q1,q2 = Q[i],Q[255]-Q[i] # 类的累积和
if q1 < 1.e-6 or q2 < 1.e-6
continue
b1,b2 = np.hsplit(bins,[i]) # 权重
# 寻找均值和方差
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
# 计算最小化函数
fn = v1*q1 + v2*q2
if fn < fn_min
fn_min = fn
thresh = i
# 用 OpenCV 函数找到 otsu 的阈值
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print( "{}".format(thresh,ret) )
void calcHist(const Mat *images, int nimages, const int *channels, InputArray mask, OutputArray hist, int dims, const int *histSize, const float **ranges, bool uniform=true, bool accumulate=false)
计算一组数组的直方图。

额外资源

  1. 数字图像处理,Rafael C. Gonzalez

练习

  1. Otsu 的二值化有一些优化可用。您可以搜索并实现它。