文章目录
- 概要
- 简单阈值调整
- 自适应阈值调整
- 大津(Otsu's)阈值法
- Otsu's 二值化是如何工作的
概要
OpenCV库中的图像处理技术,主要分为几何变换、图像阈值调整和平滑处理三个部分。
在几何变换方面,OpenCV提供了cv.warpAffine和cv.warpPerspective函数,用于实现仿射变换和透视变换。这些技术包括缩放、平移、旋转等,通过变换矩阵的运用,可以实现图像的各种变换操作。
图像阈值调整是将图像的像素值按照一定规则进行二值化处理,OpenCV提供了cv.threshold函数,支持多种阈值处理类型,如二值化、反二值化、截断、阈值以下置零等。还有自适应阈值调整,它可以根据图像的局部特性动态调整阈值,适用于光照不均匀的情况。
另外,介绍了大津(Otsu’s)阈值法,该方法是一种自动确定全局阈值的技术。通过计算图像的直方图,找到最佳阈值,实现图像的二值化处理。
简单阈值调整
简单的阈值调整,自适应阈值和大津阈值法。
函数 cv.threshold 和 cv.adaptiveThreshold
在图像处理中,阈值调整是一种基本技术。其核心思想是对图像的每个像素应用相同的阈值规则,如果像素值低于阈值,就将其设置为0,反之则设置为最大值。OpenCV提供了用于阈值调整的函数cv.threshold。该函数的第一个参数是源图像,必须是灰度图像;第二个参数是阈值,用于区分像素值;第三个参数是最大值,当像素值超过阈值时,会被设置为这个值。OpenCV还提供了不同的阈值调整类型,通过第四个参数来选择。常用的阈值调整类型包括:
cv.THRESH_BINARY:超过阈值的像素值设为最大值,其他设为0。
cv.THRESH_BINARY_INV:超过阈值的像素值设为0,其他设为最大值。
cv.THRESH_TRUNC:超过阈值的像素值设为阈值,其他像素值不变。
cv.THRESH_TOZERO:超过阈值的像素值不变,其他设为0。
cv.THRESH_TOZERO_INV:超过阈值的像素值设为0,其他不变。
这些类型的具体应用可以根据需求选择。cv.threshold函数返回两个输出:第一个是使用的阈值,第二个是阈值化后的图像。选择合适的阈值调整类型和阈值参数可以有效地处理图像,提取出感兴趣的信息。详细了解每种类型的使用场景,可以参考OpenCV的相关文档以获取更多信息。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 读取灰度图像
img = cv.imread('img.png', 0)
# 使用cv.threshold进行阈值调整,得到不同类型的阈值化图像
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循环显示图像和标题
for i in range(6):
plt.subplot(2, 3, i + 1) # 2行3列的子图中的第i+1个
plt.imshow(images[i], 'gray', vmin=0, vmax=255) # 显示灰度图像,灰度范围0-255
plt.title(titles[i]) # 设置子图标题
plt.xticks([]), plt.yticks([]) # 隐藏坐标轴
# 显示图像
plt.show()
我们使用 plt.subplot() 函数来排列图像。
这段代码输出这个结果。
自适应阈值调整
在图像处理中,使用全局阈值可能不适用于所有情况。例如,当图像在不同区域具有不同的光照条件时,使用固定阈值可能导致不准确的结果。为了解决这个问题,我们可以采用自适应阈值调整的方法。这种方法根据像素周围的小区域确定每个像素的阈值,因此,同一张图像的不同区域可以获得不同的阈值,从而更好地适应图像中的光照变化。
在OpenCV中,cv.adaptiveThreshold函数用于实现自适应阈值调整。该函数接受三个主要参数:
adaptiveMethod: 它决定了阈值如何被计算。
cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻域像素的平均值减去常数C。
cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域像素的高斯加权平均值减去常数C。
blockSize: 它指定了邻域的大小。该值定义了算法在图像中寻找阈值时所考虑的像素邻域范围。
C: 它是一个常数,用于从计算得到的阈值中减去,可以调整阈值的灵敏度。
通过调整这些参数,我们可以更精确地适应不同区域的光照变化,从而得到更准确的图像处理结果。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('img.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 range(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
大津(Otsu’s)阈值法
在全局阈值调整中,我们必须手动选择一个适当的阈值来将图像分为目标和背景。然而,在某些情况下,我们无法确定最佳的阈值,尤其是当图像具有复杂的光照变化或噪声时。在这种情况下,使用大津(Otsu)阈值法是一个更好的选择。
大津阈值法适用于具有双峰直方图(即具有两个明显峰值)的图像。在这种情况下,图像的直方图将具有两个主要的峰值,分别代表目标和背景像素的灰度级别。大津方法的目标是找到一个最佳的阈值,将这两个峰值之间的灰度级别作为分割点。
在OpenCV中,我们使用cv.threshold()函数,并将cv.THRESH_OTSU作为阈值类型的额外参数传递。大津算法会自动确定最佳阈值,并将其作为函数的返回值。这样,我们就不需要手动选择阈值,而是依赖算法找到最适合的分割点,使图像的分割更准确。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('img.png',0)
# 全局阈值
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu's
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 高斯后Otsu's
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 = ['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 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()
Otsu’s 二值化是如何工作的
Otsu’s二值化算法是一种自动确定图像阈值的方法。该算法的目标是找到一个阈值 tt,使得将图像的灰度级别分为两个类别(背景和前景),并且这两个类别的内部方差最小。
在具体实现中,我们首先计算图像的直方图,该直方图显示了不同灰度级别的像素数量。接着,算法尝试在直方图中找到两个主要的峰值,这两个峰值分别代表了背景和前景像素的灰度级别。然后,Otsu’s算法尝试选择一个阈值 tt,将这两个峰值之间的灰度级别作为分割点。
为了选择最佳的阈值 tt,算法将尝试所有可能的阈值,并计算对应的两个类别的内部方差。内部方差越小,说明两个类别之间的差异越小,图像分割得越好。最终,选择使内部方差最小的阈值 tt 作为最佳阈值,将图像二值化。
这种方法的核心思想是通过寻找使得两个类别差异最小的阈值,实现图像的自动分割。对于了解过线性规划或神经网络的人来说,这个思想可能更容易理解。
import cv2 as cv
import numpy as np
img = cv.imread('img.png',0)
blur = cv.GaussianBlur(img,(5,5),0)
# 求归一化直方图及其累积分布函数
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]) # weights
# 寻找均值和方差
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) )
# 显示原始图像和Otsu's 二值化结果
cv.imshow('Original Image', img)
cv.imshow('Otsu Thresholding', otsu)
cv.waitKey(0)
cv.destroyAllWindows()