图像阈值处理 OpenCV-Python v4.7.0

本文介绍了图像处理中的阈值技术,包括简单的阈值处理、自适应阈值处理和大津阈值处理。OpenCV库提供了cv.threshold和cv.adaptiveThreshold函数来实现这些功能。文章通过示例代码对比了不同阈值方法的效果,强调了自适应阈值处理在处理光照变化图像时的优势,以及大津二值化自动确定最优阈值的能力,特别适合于双峰图像的二值化处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目标

简单的阈值处理

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

cv.THRESH_BINARY
cv.THRESH_BINARY_INV
cv.THRESH_TRUNC
cv.THRESH_TOZERO
cv.THRESH_TOZERO_INV
请参阅类型的文档以了解其中的区别。

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

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

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 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()


为了绘制多个图像,我们使用了plt.subplot()函数。请查阅matplotlib文档以了解更多细节。

这段代码产生了这样的结果:
image

自适应阈值处理

在上一节中,我们使用一个全局值作为阈值。但这可能不是在所有情况下都好,例如,如果一幅图像在不同区域有不同的光照条件。在这种情况下,自适应阈值处理可以提供帮助。在这里,算法根据一个像素周围的小区域来确定该像素的阈值。因此,我们对同一图像的不同区域得到不同的阈值,这对具有不同光照度的图像有更好的效果。

除了上述参数外,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('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 range(4):
    plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

结果:
image

Otsu的二值化

在全局阈值化中,我们使用一个任意选择的值作为阈值。相比之下,大津的方法避免了选择一个值,而是自动确定它。

考虑一个只有两个不同图像值的图像(双峰图像),其中直方图只由两个峰值组成。一个好的阈值会在这两个值的中间。同样地,大津的方法从图像直方图中确定一个最佳的全局阈值。

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

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

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png',0)
# 全局阈值处理
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 = ['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()

结果:
image

Otsu二值化是如何工作的?

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

由于我们正在处理双模图像,Otsu的算法试图找到一个阈值(t),使关系所给的加权类内方差最小:
σ 2 w ( t ) = q 1 ( t ) σ 1 2 ( t ) + q 2 ( t ) σ 2 2 ( t ) \sigma ^2w\left( t \right) =q_1\left( t \right) \sigma _1^2\left( t \right) +q_2\left( t \right) \sigma _2^2\left( t \right) σ2w(t)=q1(t)σ12(t)+q2(t)σ22(t)
其中:
q 1 ( t ) = ∑ i = 1 t P ( i )       &       q 2 ( t ) = ∑ i = t + 1 I P ( i ) q_1\left( t \right) =\sum_{i=1}^t{P\left( i \right)}\ \ \,\,\&\,\,\ \ q_2\left( t \right) =\sum_{i=t+1}^I{P\left( i \right)} q1(t)=i=1tP(i)  &  q2(t)=i=t+1IP(i)

μ 1 ( t ) = ∑ i = 1 t i P ( i ) q 1 ( t )     &     μ 2 ( t ) = ∑ i = t + 1 I i P ( i ) q 2 ( t ) \mu _1\left( t \right) =\sum_{i=1}^t{\frac{iP\left( i \right)}{q_1\left( t \right)}\ \ \ \&\ \ \ \mu _2\left( t \right) =\sum_{i=t+1}^I{\frac{iP\left( i \right)}{q_2\left( t \right)}}} μ1(t)=i=1tq1(t)iP(i)   &   μ2(t)=i=t+1Iq2(t)iP(i)

σ 1 2 ( t ) = ∑ i = 1 t [ i − μ 1 ( t ) ] 2 P ( i ) q 1 ( t )     &     σ 2 2 ( t ) = ∑ i = t + 1 I [ i − μ 2 ( t ) ] 2 P ( i ) q 2 ( t ) \sigma _1^2\left( t \right) =\sum_{i=1}^t{\left[ i-\mu _1\left( t \right) \right] ^2\frac{P\left( i \right)}{q_1\left( t \right)}\ \ \ \&\ \ \ \sigma _2^2\left( t \right) =\sum_{i=t+1}^I{\left[ i-\mu _2\left( t \right) \right] ^2\frac{P\left( i \right)}{q_2\left( t \right)}}} σ12(t)=i=1t[iμ1(t)]2q1(t)P(i)   &   σ22(t)=i=t+1I[iμ2(t)]2q2(t)P(i)

它实际上是找到一个位于两个峰值之间的t值,使两个类的差异最小。它可以在Python中简单地实现,如下:

img = cv.imread('noisy2.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]) # 权重
    # 寻找平均值和方差
    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) )

其他资源

《数字图像处理》,Rafael C. Gonzalez
(Digital Image Processing, Rafael C. Gonzalez)

练习

对Otsu的二进制化有一些优化。你可以搜索并实现它

### PyCharm 打开文件显示全的解决方案 当遇到PyCharm打开文件显示全的情况时,可以尝试以下几种方法来解决问题。 #### 方法一:清理缓存并重启IDE 有时IDE内部缓存可能导致文件加载异常。通过清除缓存再启动程序能够有效改善此状况。具体操作路径为`File -> Invalidate Caches / Restart...`,之后按照提示完成相应动作即可[^1]。 #### 方法二:调整编辑器字体设置 如果是因为字体原因造成的内容显示问题,则可以通过修改编辑区内的文字样式来进行修复。进入`Settings/Preferences | Editor | Font`选项卡内更改合适的字号大小以及启用抗锯齿功能等参数配置[^2]。 #### 方法三:检查项目结构配置 对于某些特定场景下的源码视图缺失现象,可能是由于当前工作空间未能正确识别全部模块所引起。此时应该核查Project Structure的Content Roots设定项是否涵盖了整个工程根目录;必要时可手动添加遗漏部分,并保存变更生效[^3]。 ```python # 示例代码用于展示如何获取当前项目的根路径,在实际应用中可根据需求调用该函数辅助排查问题 import os def get_project_root(): current_file = os.path.abspath(__file__) project_dir = os.path.dirname(current_file) while not os.path.exists(os.path.join(project_dir, '.idea')): parent_dir = os.path.dirname(project_dir) if parent_dir == project_dir: break project_dir = parent_dir return project_dir print(f"Current Project Root Directory is {get_project_root()}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值