图像阈值
本章节内容是博主网上收集的。当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
不同编程语言对应的OpenCv版本以及开发环境信息如下:
语言 | OpenCv版本 | IDE |
C# | OpenCvSharp4.4.8.0.20230708 | Visual Studio 2022 |
C++ | OpenCv-4.5.5-vc14_vc15 | Visual Studio 2022 |
Python | OpenCv-Python (4.6.0.66) | PyCharm Community Edition 2022.1.3 |
C#版本运行效果及代码如下:
【请参考其他文章中的使用方法】
C++版本运行效果及代码如下:
【请参考其他文章中的使用方法】
Python版本运行效果及代码如下:
python版本代码一共有4套,主要包括:
简单阈值demo1.py:全局简单阈值
与名字一样,这种方法非常简单。但像素值高于阈值时,我们给这个像素赋予一个新值(可能是白色),否则我们给它赋予另外一种颜色(也许是黑色)。这个函数就是cv2.threshhold()。这个函数的第一个参数就是原图像,原图像应该是灰度图。第二个参数就是用来对像素值进行分类的阈值。第三个参数就是当像素值高于(有时是小于)阈值时应该被赋予的新的像素值。
OpenCV 提供了多种不同的阈值方法 , 是由第四个参数来决定的。
这些方法包括
• cv2.THRESH_BINARY
• cv2.THRESH_BINARY_INV
• cv2.THRESH_TRUNC
• cv2.THRESH_TOZERO
• cv2.THRESH_TOZERO_INV
demo2.py:自适应阈值
在前面的部分我们使用是全局间值,整幅图像采用同一个数作为间值。当时这种方法并不适应与所有情况,尤其是当同一幅图像上的不同部分的具有不同亮度时。这种情况下我们需要采用自适应间值。此时的间值是根据图像上的每一个小区域计算与其对应的阀值。因此在同一幅图像上的不同区域采用的是不同的间值,从而使我们能在亮度不同的情况下得到更好的结果。
这种方法需要我们指定三个参数,返回值只有一个。
AdaptiveMethod 指定计算间值的方法。
• cv2.ADPTIVE_THRESHMEANC:值取自相邻区域的平均值
• CV2.ADPTIVE_THRESHGAUSSIANC:闵值取值相邻区域的加权和,权重为一个高斯窗口
• Block Size 邻域大小(用来计算阀值的区域大小)
• C 这就是是一个常数,间值就等于的平均值或者加权平均值减去这个常数。
demo3.py:
在第一部分中我们提到过retVal,当我们使用Otsu二值化时会用到它那么它到底是什么呢?
在使用全局阁值时,我们就是随便给了一个数来做阁值,那我们怎么知道我们选取的这个数的好坏呢?答案就是不停的尝试。如果是一副双峰图像(简单来说双峰图像是指图像直方图中存在两个峰)呢?我们巨不是应该在两个峰之间的峰谷选一个值作为闹值?这就是Otsu二值化要做的。简单来说就是对一副双峰图像自动根据其直方图计算出一个阁值。(对于非双峰图像,这种方法得到的结果可能会不理想)。
这里用到到的函数还是cv2.threshold(),但是需要多传入一个参数(flag):cv2.THRESH_OTSU。这时要把阀值设为0。然后算法会找到最优商值,这个最优闹值就是返回值retVal。如果不使用Otsu二值化,返回的retVal值与设定的间值相等。
下面的例子中,输入图像是一副带有噪声的图像。第一种方法,我们设127为全局阀值。第二种方法,我们直接使用Otsu二值化。第三种方法,我们首先使用一个5x5的高斯核除去噪音,然后再使用Otsu二值化。看看噪音去除对结果的影响有多大吧。
demo4.py:
在这一部分我们会演示怎样使用Python来实现Otsu二值化算法,从而告诉大家它是如何工作的。如果你不感兴趣的话可以跳过这一节。
因为是双峰图,Otsu算法就是要找到一个阀值(t),使得同一类加权方差最小,需要满足下列关系式:(其实我也看不懂这啥东西。。。)

其实就是在两个峰之间找到一个阀值t,将这两个峰分开,并且使每一个峰内的方差最小。
简单阈值demo1.py,演示效果和代码如下:
演示原图:

演示效果:

源码如下:
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('../images/7067.jpg', 0)
ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img, 127, 255, cv2.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')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
demo2.py:自适应阈值,演示效果和代码如下
测试原图如下:

测试效果如下:

'''
自适应阈值
Adaptive Method- 指定 算阈值的方法。
– cv2.ADPTIVE_THRESH_MEAN_C 值取自相邻区域的平均值
– cv2.ADPTIVE_THRESH_GAUSSIAN_C 值取值相邻区域 的加权和 ,权重为一个高斯窗口
'''
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('../images/sudoku.jpg', 0)
# 中值滤波
img = cv2.medianBlur(img, 5)
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 11 为 Block size 邻域大小 用来计算阈值的区域大小 ,
# 2 为 C值,常数, 阈值就等于的平均值或者加权平均值减去这个常数。
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.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()
demo3.py:演示效果和代码如下
测试原图如下:

测试效果如下:

import cv2
from matplotlib import pyplot as plt
img = cv2.imread('../images/noisy2.png', 0)
# global thresholding
ret1, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# Otsu's thresholding
ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
# 5,5 为 斯核的大小 0 为标准差
blur = cv2.GaussianBlur(img, (5, 5), 0)
# 阀值一定为 0
ret3, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.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"]
# 使用了 pyplot 中画直方图的方法 plt.hist,
# 注意的是它的参数是一维数组
# 所以使用了 numpy ravel 方法 将多维数组 换成一维 也可以使用 flatten 方法
# ndarray.flat 1-D iterator over an array.
# ndarray.flatten 1-D array copy of the elements of an array in row-major order.
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()
demo4.py:演示效果和代码如下
测试图像为前面的“noisy2.png”
运行结果如下:

代码如下:
import cv2
import numpy as np
img = cv2.imread('../images/noisy2.png', 0)
blur = cv2.GaussianBlur(img, (5, 5), 0)
# find normalized_histogram, and its cumulative distribution function
# 算归一化直方图
# CalcHist(image, accumulate=0, mask=NULL)
hist = cv2.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 range(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 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(thresh, ret)