第 9 章

阈值处理迈向数字情报

本章共 5 个小节 · threshold、Otsu、自适应阈值、位平面、数字水印
阈值处理会把像素值与指定临界值比较,再依规则改写像素。本章覆盖 cv2.threshold() 的多种类型、 Otsu 自动阈值、自适应阈值、位平面分解,以及把水印藏入最低有效位的做法。
9-1

threshold() 函数

threshold() 返回两个值:ret 是实际阈值,dst 是处理后的图像。

ret, dst = cv2.threshold(src, thresh, maxval, type)
类型规则
cv2.THRESH_BINARY大于阈值设为 maxval,否则设为 0。
cv2.THRESH_BINARY_INVBINARY 的反向版本。
cv2.THRESH_TRUNC大于阈值的像素被截断为阈值,小于等于阈值则保留。
cv2.THRESH_TOZERO大于阈值保留原值,否则设为 0。
cv2.THRESH_TOZERO_INVTOZERO 的反向版本。

9-1-1 THRESH_BINARY

程序实例 ch9_1.py:随机数组的 BINARY 阈值处理

# ch9_1.py import cv2 import numpy as np src = np.random.randint(0, 256, size=(3, 5), dtype=np.uint8) ret, dst = cv2.threshold(src, 127, 255, cv2.THRESH_BINARY) print(f"src =\n{src}") print(f"ret = {ret}") print(f"dst =\n{dst}")

程序实例 ch9_2.py:灰度图像二值化

# ch9_2.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) ret1, dst1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) ret2, dst2 = cv2.threshold(img, 80, 255, cv2.THRESH_BINARY) cv2.imshow("Original", img) cv2.imshow("threshold 127", dst1) cv2.imshow("threshold 80", dst2) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_2.py 灰度图像二值化结果
灰度图像使用不同阈值的二值化结果,参考原书第 9-3 页。

程序实例 ch9_3.py:彩色图像二值化

# ch9_3.py import cv2 img = cv2.imread("jk.jpg") ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_3.py 彩色图像二值化结果
彩色图像逐通道二值化结果,参考原书第 9-4 页。

程序实例 ch9_4.py:numbers.jpg 数字图形二值化

# ch9_4.py import cv2 img = cv2.imread("numbers.jpg", cv2.IMREAD_GRAYSCALE) ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_4.py 数字图形二值化结果
数字图形的二值化结果,参考原书第 9-5 页。

9-1-2 THRESH_BINARY_INV

程序实例 ch9_5.py:随机数组的 BINARY_INV

# ch9_5.py import cv2 import numpy as np src = np.random.randint(0, 256, size=(3, 5), dtype=np.uint8) ret, dst = cv2.threshold(src, 127, 255, cv2.THRESH_BINARY_INV) print(f"src =\n{src}") print(f"dst =\n{dst}")

程序实例 ch9_6.py:灰度图像 BINARY_INV

# ch9_6.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch9_7.py:彩色图像 BINARY_INV

# ch9_7.py import cv2 img = cv2.imread("jk.jpg") ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch9_8.py:numbers.jpg 使用 BINARY_INV

# ch9_8.py import cv2 img = cv2.imread("numbers.jpg", cv2.IMREAD_GRAYSCALE) ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_8.py 数字图形反二值化结果
数字图形的反二值化结果,参考原书第 9-8 页。

9-1-3 THRESH_TRUNC

程序实例 ch9_9.py:随机数组的 TRUNC

# ch9_9.py import cv2 import numpy as np src = np.random.randint(0, 256, size=(3, 5), dtype=np.uint8) ret, dst = cv2.threshold(src, 127, 255, cv2.THRESH_TRUNC) print(f"src =\n{src}") print(f"dst =\n{dst}")

程序实例 ch9_10.py:灰度图像 TRUNC

# ch9_10.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch9_11.py:彩色图像 TRUNC

# ch9_11.py import cv2 img = cv2.imread("jk.jpg") ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_11.py 彩色图像 TRUNC 结果
彩色图像使用 TRUNC 的结果,参考原书第 9-11 页。

9-1-4 THRESH_TOZERO

程序实例 ch9_12.py:随机数组的 TOZERO

# ch9_12.py import cv2 import numpy as np src = np.random.randint(0, 256, size=(3, 5), dtype=np.uint8) ret, dst = cv2.threshold(src, 127, 255, cv2.THRESH_TOZERO) print(f"src =\n{src}") print(f"dst =\n{dst}")

程序实例 ch9_13.py:灰度图像 TOZERO

# ch9_13.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_13.py 灰度图像 TOZERO 结果
灰度图像使用 TOZERO 的结果,参考原书第 9-13 页。

程序实例 ch9_14.py:彩色图像 TOZERO

# ch9_14.py import cv2 img = cv2.imread("jk.jpg") ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()

9-1-5 THRESH_TOZERO_INV

程序实例 ch9_15.py:随机数组的 TOZERO_INV

# ch9_15.py import cv2 import numpy as np src = np.random.randint(0, 256, size=(3, 5), dtype=np.uint8) ret, dst = cv2.threshold(src, 127, 255, cv2.THRESH_TOZERO_INV) print(f"src =\n{src}") print(f"dst =\n{dst}")

程序实例 ch9_16.py:灰度图像 TOZERO_INV

# ch9_16.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()

程序实例 ch9_17.py:彩色图像 TOZERO_INV

# ch9_17.py import cv2 img = cv2.imread("jk.jpg") ret, dst = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV) cv2.imshow("Original", img) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_17.py 彩色图像 TOZERO_INV 结果
彩色图像使用 TOZERO_INV 的结果,参考原书第 9-17 页。
9-2

Otsu 算法

Otsu 方法会自动寻找适合的阈值。使用时把阈值参数设为 0,并把 cv2.THRESH_OTSU 与阈值类型相加。

程序实例 ch9_18.py:列出原始数组与测试数组

# ch9_18.py import cv2 import numpy as np src = np.random.randint(0, 256, size=(5, 5), dtype=np.uint8) print(f"src =\n{src}") ret, dst = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) print(f"ret = {ret}") print(f"dst =\n{dst}")

程序实例 ch9_19.py:测试 Otsu 回传阈值与数组

# ch9_19.py import cv2 import numpy as np src = np.array([[0, 0, 0, 50, 50], [50, 50, 100, 100, 100], [150, 150, 200, 200, 255]], dtype=np.uint8) ret, dst = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) print(f"ret = {ret}") print(f"dst =\n{dst}")

程序实例 ch9_20.py:比较固定阈值 127 与 Otsu 方法

# ch9_20.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) ret1, dst1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) ret2, dst2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) cv2.imshow("Original", img) cv2.imshow("threshold 127", dst1) cv2.imshow("Otsu", dst2) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_20.py Otsu 阈值结果
固定阈值与 Otsu 自动阈值对比,参考原书第 9-20 页。
9-3

自适应阈值方法

自适应阈值会根据像素附近区域动态计算阈值,适合光照不均匀的图像。

dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)

程序实例 ch9_21.py:建筑物图像的自适应阈值

# ch9_21.py import cv2 img = cv2.imread("minnesota.jpg", cv2.IMREAD_GRAYSCALE) ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) 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) cv2.imshow("Original", img) cv2.imshow("threshold", th1) cv2.imshow("adaptive mean", th2) cv2.imshow("adaptive gaussian", th3) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_21.py 自适应阈值结果
普通阈值与自适应阈值对比,参考原书第 9-21 页。
9-4

平面图的分解

一个 8-bit 灰度像素由 8 个位组成,从最低位 u0 到最高位 u7。把每个位拆出来显示,就得到位平面。 高位平面对视觉影响较大,最低位平面影响较小。

程序实例 ch9_22.py:将原始图像分解为 8 个位平面

# ch9_22.py import cv2 import numpy as np img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) planes = [] for i in range(8): plane = cv2.bitwise_and(img, 2 ** i) plane = np.where(plane > 0, 255, 0).astype(np.uint8) planes.append(plane) cv2.imshow(f"u{i}", plane) cv2.waitKey(0) cv2.destroyAllWindows()
高位平面
ch9_22.py 高位平面
较高位平面保留较明显轮廓,参考原书第 9-22 页。
低位平面
ch9_22.py 低位平面
低位平面多为细节和噪声,参考原书第 9-22 页。

程序实例 ch9_23.py:验证最低有效位对视觉影响小

# ch9_23.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) img_lsb0 = cv2.bitwise_and(img, 254) cv2.imshow("Original", img) cv2.imshow("LSB set 0", img_lsb0) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_23.py 最低有效位影响
清除最低有效位后视觉差异很小,参考原书第 9-23 页。
9-5

数字水印

数字水印可利用最低有效位实现:先把原图最低位清为 0,再把二值化后的水印压缩到 0 或 1,最后加到原图中。 提取时只要取最低位再放大到 255 即可。

程序实例 ch9_24.py:将水印嵌入原始图像

# ch9_24.py import cv2 img = cv2.imread("jk.jpg", cv2.IMREAD_GRAYSCALE) watermark = cv2.imread("copyright.jpg", cv2.IMREAD_GRAYSCALE) ret, wm = cv2.threshold(watermark, 127, 1, cv2.THRESH_BINARY) img_clear = cv2.bitwise_and(img, 254) embedded = cv2.add(img_clear, wm) cv2.imshow("embedded", embedded) cv2.imwrite("watermark_image.jpg", embedded) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_24.py 嵌入水印结果
水印嵌入最低有效位后的图像,参考原书第 9-24 页。

程序实例 ch9_25.py:提取数字水印

# ch9_25.py import cv2 import numpy as np img = cv2.imread("watermark_image.jpg", cv2.IMREAD_GRAYSCALE) watermark = cv2.bitwise_and(img, 1) watermark = np.where(watermark > 0, 255, 0).astype(np.uint8) cv2.imshow("watermark", watermark) cv2.waitKey(0) cv2.destroyAllWindows()
执行结果
ch9_25.py 提取水印结果
从最低有效位提取出的水印,参考原书第 9-25 页。
习题

1:参考 ch9_4.py,处理 ex9_1.jpg 得到指定二值化结果。

习题 1 执行结果
习题 1 ex9_1.jpg 二值化结果
参考原书第 9-27 页。

2:参考 ch9_8.py,处理 ex9_1.jpg 得到反二值化结果。

习题 2 执行结果
习题 2 ex9_1.jpg 反二值化结果
参考原书第 9-27 页。

3:扩充 ch9_21.py,加入 THRESH_TOZEROTHRESH_TOZERO_INV 的对比。

习题 3 执行结果
习题 3 THRESH_BINARY 对比结果 习题 3 THRESH_TOZERO 与 THRESH_TOZERO_INV 对比结果 习题 3 自适应阈值对比结果
参考原书第 9-279-28 页。