介绍
阈值化是一种简单而有效的技术,可以在图像中执行基本分割,并将其二值化(将其转换为二值图像),其中像素是 0
or 1
(或 255
如果您使用整数来表示它们)。
通常,您可以使用阈值在图像中执行简单的背景 - 前景分割,它归结为每个像素的简单技术的变体:
if pixel_value > threshold:
pixel_value = MAX
else:
pixel_value = 0
这个基本过程被称为 二进制阈值. 现在 - 有多种方法可以调整这个总体思路,包括反转操作(切换 >
用一个签名 <
符号),设置 pixel_value
到 threshold
而不是最大值/0(称为截断),保持 pixel_value
如果它高于 threshold
或者如果它低于 threshold
.
所有这些都在 OpenCV 中方便地实现为:
cv2.THRESH_BINARY
cv2.THRESH_BINARY_INV
cv2.THRESH_TRUNC
cv2.THRESH_TOZERO
cv2.THRESH_TOZERO_INV
… 分别。 这些是相对“幼稚”的方法,因为它们相当简单,不考虑图像中的上下文,知道哪些形状是常见的,等等。对于这些属性——我们必须采用更昂贵和更强大的计算技巧。
现在,即使使用“幼稚”的方法—— 一些 可以采用启发式方法来找到好的阈值,其中包括 Otsu 方法和 Triangle 方法:
cv2.THRESH_OTSU
cv2.THRESH_TRIANGLE
请注意: OpenCV 阈值处理是一种初级技术,对光照变化和渐变、颜色异质性等很敏感。它最好应用在相对干净的图片上,经过模糊处理以减少噪点,并且要分割的对象没有太多颜色变化。
使用单个阈值克服基本阈值处理的一些问题的另一种方法是使用 自适应阈值 它对图像中的每个小区域应用阈值,而不是全局。
使用 OpenCV 进行简单阈值处理
OpenCV 的 Python API 中的阈值是通过 cv2.threshold()
方法 – 接受图像(NumPy 数组,用整数表示)、阈值、最大值和阈值方法(如何 threshold
和 maximum_value
被使用):
img = cv2.imread('objects.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
blurred = cv2.GaussianBlur(img, (7, 7), 0)
ret, img_masked = cv2.threshold(blurred, 220, 255, cv2.THRESH_BINARY)
返回码只是应用的阈值:
print(f"Threshold: {ret}")
在这里,由于阈值是 220
我们已经使用了 THRESH_BINARY
方法 - 上面的每个像素值 220
将增加到 255
, 而下面的每个像素值 220
将降至 0
,创建一个带有“蒙版”的黑白图像,覆盖前景物体。
为什么是220? 了解图像的外观可以让您对可以选择的阈值做出一些近似猜测。 在实践中,您很少需要设置手动阈值,稍后我们将介绍自动阈值选择。
让我们绘制结果! OpenCV 窗口可能有点挑剔,所以我们将使用 Matplotlib 绘制原始图像、模糊图像和结果:
fig, ax = plt.subplots(1, 3, figsize=(12, 8))
ax[0].imshow(img)
ax[1].imshow(blurred)
ax[2].imshow(img_masked)
阈值方法
如前所述,有多种方法可以在函数中使用阈值和最大值。 我们最初已经查看了二进制阈值。 让我们创建一个方法列表,并一个一个地应用它们,绘制结果:
methods = [cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV, cv2.THRESH_TRUNC, cv2.THRESH_TOZERO, cv2.THRESH_TOZERO_INV]
names = ['Binary Threshold', 'Inverse Binary Threshold', 'Truncated Threshold', 'To-Zero Threshold', 'Inverse To-Zero Threshold']
def thresh(img_path, method, index):
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
blurred = cv2.GaussianBlur(img, (7, 7), 0)
ret, img_masked = cv2.threshold(blurred, 220, 255, method)
fig, ax = plt.subplots(1, 3, figsize=(12, 4))
fig.suptitle(names[index], fontsize=18)
ax[0].imshow(img)
ax[1].imshow(blurred)
ax[2].imshow(img_masked)
plt.tight_layout()
for index, method in enumerate(methods):
thresh('coins.jpeg', method, index)
THRESH_BINARY
和 THRESH_BINARY_INV
互为逆,并将图像二值化 0
和 255
,将它们分别分配给背景和前景,反之亦然。
THRESH_TRUNC
将图像二值化 threshold
和 255
.
THRESH_TOZERO
和 THRESH_TOZERO_INV
二值化 0
和当前像素值(src(x, y)
)。 让我们看一下生成的图像:
查看我们的 Git 学习实践指南,其中包含最佳实践、行业认可的标准以及随附的备忘单。 停止谷歌搜索 Git 命令,实际上 学习 它!
这些方法足够直观——但是,我们如何才能自动化一个好的阈值,“好的阈值”到底意味着什么? 到目前为止,大多数结果都有不理想的面具,其中有标记和斑点。 发生这种情况是因为硬币的反射表面不同——由于脊反射光的方式不同,它们的颜色不均匀。
在某种程度上,我们可以通过找到一个更好的全球门槛来解决这个问题。
使用 OpenCV 自动/优化阈值
OpenCV 采用了两种有效的全局阈值搜索方法——Otsu 方法和 Triangle 方法。
Otsu 的方法假设它正在工作 双模态 图片。 双模图像是其颜色直方图仅包含两个峰值(即只有两个不同的像素值)的图像。 考虑到每个峰都属于诸如“背景”和“前景”之类的类别——理想的阈值就在它们的中间。
图片来源: https://scipy-lectures.org/
您可以使用高斯模糊使一些图像更具双模态,但不是全部。
另一种通常性能更好的算法是三角形算法,它计算灰度直方图的最大值和最小值之间的距离并绘制一条线。 选择该线与直方图其余部分最远的点作为阈值:
这两个都假设一个灰度图像,所以我们需要将输入图像转换为灰色 cv2.cvtColor()
:
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (7, 7), 0)
ret, mask1 = cv2.threshold(blurred, 0, 255, cv2.THRESH_OTSU)
ret, mask2 = cv2.threshold(blurred, 0, 255, cv2.THRESH_TRIANGLE)
masked = cv2.bitwise_and(img, img, mask=mask1)
让我们使用这两种方法运行图像并可视化结果:
methods = [cv2.THRESH_OTSU, cv2.THRESH_TRIANGLE]
names = ['Otsu Method', 'Triangle Method']
def thresh(img_path, method, index):
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (7, 7), 0)
ret, img_masked = cv2.threshold(blurred, 0, 255, method)
print(f"Threshold: {ret}")
fig, ax = plt.subplots(1, 3, figsize=(12, 5))
fig.suptitle(names[index], fontsize=18)
ax[0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
ax[1].imshow(cv2.cvtColor(gray, cv2.COLOR_BGR2RGB))
ax[2].imshow(cv2.cvtColor(img_masked, cv2.COLOR_BGR2RGB))
for index, method in enumerate(methods):
thresh('coins.jpeg', method, index)
在这里,三角形方法优于 Otsu 的方法,因为图像不是双模态的:
import numpy as np
img = cv2.imread('coins.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (7, 7), 0)
histogram_gray, bin_edges_gray = np.histogram(gray, bins=256, range=(0, 255))
histogram_blurred, bin_edges_blurred = np.histogram(blurred, bins=256, range=(0, 255))
fig, ax = plt.subplots(1, 2, figsize=(12, 4))
ax[0].plot(bin_edges_gray[0:-1], histogram_gray)
ax[1].plot(bin_edges_blurred[0:-1], histogram_blurred)
但是,很清楚三角形方法如何能够处理图像并产生更令人满意的结果。
OpenCV 阈值的限制
使用 OpenCV 设置阈值简单、容易且高效。 然而,它相当有限。 一旦你引入了彩色元素、不均匀的背景和不断变化的照明条件——全局阈值作为一个概念就变得过于死板。
图像通常过于复杂,单个阈值不够用,这可以通过以下方式部分解决 自适应阈值,其中应用了许多局部阈值而不是单个全局阈值。 虽然也是有限的,但自适应阈值比全局阈值更灵活。
结论
近年来,二进制分割(就像我们在这里所做的那样)和多标签分割(你可以对任意数量的类进行编码)已经成功地使用深度学习网络建模,这些网络更加强大和灵活。 此外,他们可以将全局和本地上下文编码到他们正在分割的图像中。 缺点是——你需要数据来训练他们,以及时间和专业知识。
对于即时、简单的阈值处理,您可以使用 OpenCV。 对于准确的生产级分割,您需要使用神经网络。
走得更远——计算机视觉的实用深度学习
你好奇的天性让你想走得更远? 我们建议查看我们的 套餐: “使用 Python 进行计算机视觉的实用深度学习”.
另一个计算机视觉课程?
我们不会对 MNIST 数字或 MNIST 时尚进行分类。 他们很久以前就发挥了作用。 在让高级黑盒架构承担性能负担之前,太多的学习资源专注于基本数据集和基本架构。
我们想专注于 揭秘, 实际性, 理解, 直觉 和 真实项目. 想学 形成一种 你可以有所作为? 我们将带您从大脑处理图像的方式到编写研究级的乳腺癌深度学习分类器,再到“产生幻觉”的深度学习网络,通过实际工作教您原理和理论,为您配备成为应用深度学习解决计算机视觉问题的专家的专业知识和工具。
里面是什么?
- 视觉的首要原则以及如何教计算机“看”
- 计算机视觉的不同任务和应用
- 让您的工作更轻松的交易工具
- 为计算机视觉寻找、创建和利用数据集
- 卷积神经网络的理论与应用
- 处理数据集中的域转移、共现和其他偏差
- 迁移学习并利用他人的训练时间和计算资源为您谋取利益
- 构建和训练最先进的乳腺癌分类器
- 如何将健康的怀疑态度应用于主流思想并理解广泛采用的技术的含义
- 使用 t-SNE 和 PCA 可视化 ConvNet 的“概念空间”
- 公司如何使用计算机视觉技术取得更好结果的案例研究
- 适当的模型评估、潜在空间可视化和识别模型的注意力
- 执行领域研究,处理您自己的数据集并建立模型测试
- 尖端架构、想法的发展、是什么让它们与众不同以及如何实现它们
- KerasCV – 用于创建最先进的管道和模型的 WIP 库
- 如何解析和阅读论文并自己实现它们
- 根据您的应用选择型号
- 创建端到端机器学习管道
- 使用 Faster R-CNN、RetinaNets、SSD 和 YOLO 进行对象检测的景观和直觉
- 实例和语义分割
- 使用 YOLOv5 进行实时对象识别
- 训练 YOLOv5 目标检测器
- 使用 KerasNLP(行业强大的 WIP 库)使用 Transformers
- 将 Transformers 与 ConvNet 集成以生成图像的标题
- 深梦
- 计算机视觉的深度学习模型优化