OpenCV图像处理——二值化原理与代码实现(C++/Python)
概述
在 OpenCV 中,二值化(Binarization)是一种图像处理操作,它的目的是将一幅灰度图像转换为仅包含两种像素值(通常为 0 和 255,分别代表黑色和白色)的二值图像。通过设定一个合适的阈值(Threshold),根据像素点的灰度值与该阈值的比较结果来确定其最终是被置为 0 还是 255,以此来突出图像中的目标物体,简化后续的图像分析、目标检测、特征提取等操作。
1 全局阈值法
原理:
这是最简单的二值化方法,对于给定的灰度图像中的每个像素点,将其灰度值与一个预先设定的固定阈值进行比较。如果像素的灰度值大于等于这个阈值,就将其赋值为一个较高的值(比如 255,表示白色);如果像素的灰度值小于阈值,就将其赋值为一个较低的值(比如 0,表示黑色)。
函数: cv2.threshold(),其基本语法如下:
ret, thresh = cv2.threshold(src, thresh, maxval, type)
参数:
- src:输入的源灰度图像,必须是单通道图像,比如通过将彩色图像转换为灰度图像后得到的数据(使用cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)进行转换)。
- thresh:设定的阈值数值,例如可以设为 127 等不同的值,具体根据图像的灰度分布和想要的效果来定。
- maxval:当像素值大于等于阈值时要赋予的最大值,一般设为 255(对应 8 位图像的白色)。
- type:阈值处理的类型,常用的有以下几种:
- cv2.THRESH_BINARY:大于等于阈值设为maxval,小于阈值设为 0,也就是常规的二值化操作。
- cv2.THRESH_BINARY_INV:与THRESH_BINARY相反,大于等于阈值设为 0,小于阈值设为maxval,实现反二值化效果。
- cv2.THRESH_TRUNC:大于阈值的像素点截断为阈值,小于阈值的保持不变。
- cv2.THRESH_TOZERO:小于阈值的像素点设为 0,大于阈值的保持不变。
- cv2.THRESH_TOZERO_INV:与THRESH_TOZERO相反,大于阈值的像素点设为 0,小于阈值的保持不变。
- ret:返回实际使用的阈值(当采用自动阈值算法时,返回自动计算出的阈值,手动指定阈值时返回传入的阈值)。
- thresh:输出的二值化后的图像数据。
2 自适应阈值法
原理:
全局阈值法中阈值是固定的,对于光照不均匀等情况效果可能不佳。自适应阈值法则会根据图像局部区域的灰度特征自动计算每个小区域的阈值,这样不同区域可以有不同的阈值,能更好地应对光照变化等情况。例如在图像较亮的区域阈值可以自动设高些,较暗区域阈值设低些。
函数: cv2.adaptiveThreshold(),语法如下:
dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
参数:
- src:同样是输入的灰度图像。
- maxValue:与全局阈值法中的maxval类似,当像素值满足条件时赋予的最大值,常为 255。
- adaptiveMethod:自适应阈值计算的方法,有两种:
- cv2.ADAPTIVE_THRESH_MEAN_C:阈值是邻域块的平均灰度值减去常数C。
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域块的高斯加权平均灰度值减去常数C,通常对有噪声的图像效果更好。
- thresholdType:阈值处理的类型,比如cv2.THRESH_BINARY等,和全局阈值法中类似。
- blockSize:邻域块的大小,一般取奇数,例如 3、5、7 等,它决定了计算阈值时考虑的局部区域范围。
- C:从计算出的自适应阈值中减去的常数,可根据实际情况调整,用于微调阈值的大小。
- dst:输出的经过自适应阈值二值化后的图像。
3 OTSU阈值法
这里单独把OTSU阈值法做一个小节。其实只需要在全局阈值法中修改type即可。需要强调:把thresh设置为0。
3.1 图像灰度直方图:
原理:
它是一种对图像中像素灰度分布情况进行统计可视化的工具。它展示了图像中各个灰度级上像素出现的频数(或者频率),横坐标表示灰度级(通常对于 8 位灰度图像,灰度级范围是 0 - 255),纵坐标表示对应灰度级上像素的数量或者占总像素数的比例。
例如,一幅简单的灰度图像经过统计其灰度直方图后,我们就能直观地看出图像中哪种灰度值的像素比较多,哪种比较少,进而了解图像的整体明暗程度、对比度等特征。
根据图像灰度直方图,如何分析呢?
(1)如果直方图中大部分像素集中在灰度值较低的区域(接近 0),说明图像整体偏暗;反之,如果集中在较高灰度值区域(接近 255),则图像整体偏亮。
(2)均匀分布的直方图意味着图像在各个灰度级上的像素数量比较均匀,图像对比度通常较高,看起来细节更丰富;而若直方图集中在某几个灰度级上,图像对比度往往较低,显得比较 “平淡”,缺乏层次感。
(3)在图像预处理阶段,通过观察直方图可以确定是否需要进行灰度拉伸、直方图均衡化等操作来改善图像质量。例如,若图像偏暗,可以根据直方图调整其灰度分布,使图像变亮且细节更清晰。
函数:
cv2.calcHist(images, channels, mask, histSize, ranges, accumulate=False)
参数:
- images: 输入图像的列表(通常是 [img])。图像应为 numpy.ndarray 类型。
- channels: 表示要统计的通道,对于灰度图像只有一个通道,所以传入 [0];如果是彩色图像(如 BGR 格式有 3 个通道),可以分别传入 [0]、[1]、[2] 来统计蓝色、绿色、红色通道各自的直方图。
- mask: 表示图像的掩码(Mask),如果不需要使用掩码(通常情况),则传入 None;掩码可以用于只统计图像中特定区域的直方图,例如只统计图像中某个感兴趣区域内像素的灰度分布情况。
- histSize: 每个通道的直方图的维度(通常设为 [256])。
- ranges: 灰度级的范围,通常为 [0, 256]。
- accumulate: 是否累加直方图,默认为 False。
灰度图像直方图:
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 读取灰度图像
image = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# 计算灰度直方图
hist = cv2.calcHist([image], [0], None, [256], [0, 256])
# 绘制直方图
plt.plot(hist)
plt.title('Grayscale Histogram')
plt.xlabel('Pixel Value')
plt.ylabel('Frequency')
plt.show()
彩色图像直方图:
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 读取彩色图像
color_image = cv2.imread('image.jpg')
# 分别计算 B, G, R 通道直方图
colors = ('b', 'g', 'r')
for i, color in enumerate(colors):
hist = cv2.calcHist([color_image], [i], None, [256], [0, 256])
plt.plot(hist, color=color)
plt.title('Color Histogram')
plt.xlabel('Pixel Value')
plt.ylabel('Frequency')
plt.show()
使用掩码计算直方图:
import cv2
import numpy as np
from matplotlib import pyplot as plt
image = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# 创建一个圆形掩码
mask = np.zeros(image.shape, dtype=np.uint8)
center = (image.shape[1] // 2, image.shape[0] // 2)
radius = min(image.shape[:2]) // 4
cv2.circle(mask, center, radius, 255, -1)
# 计算带掩码的直方图
masked_hist = cv2.calcHist([image], [0], mask, [256], [0, 256])
# 显示掩码和直方图
masked_image = cv2.bitwise_and(image, image, mask=mask)
cv2.imshow('Masked Image', masked_image)
plt.plot(masked_hist)
plt.title('Masked Grayscale Histogram')
plt.xlabel('Pixel Value')
plt.ylabel('Frequency')
plt.show()
cv2.waitKey(0)
cv2.destroyAllWindows()
3.2 Otsu’s 方法
Otsu’s 方法,也被称为大津阈值法,是一种基于图像灰度直方图的自动阈值选取方法,由日本学者大津展之(Nobuyuki Otsu)提出。其核心目标是自动找到一个最佳阈值,将图像分为前景(目标物体)和背景两部分,使得两部分之间的类间方差最大,以此来达到最好的二值化效果。它可以通过最小化类内方差或最大化类间方差来工作。
这里只总结类间方差。
原理:
- 类间方差的概念:
- 对于一幅灰度图像,假设通过阈值 t 将其像素分为两类,一类像素灰度值小于 t,归为背景类 C 0 C_0 C0;另一类像素灰度值大于等于 t,归为前景类 C 1 C_1 C1。
- 类间方差的计算公式涉及到多个因素,主要通过计算每类像素出现的概率、平均灰度值等参数来综合得到。设图像总像素数为 N,灰度值范围是 0到 L-1(对于 8 位图像 L=256),背景类 C 0 C_0 C0 像素点数为 n 0 n_0 n0,平均灰度值为 m 0 m_0 m0;前景类 C 1 C_1 C1像素点数为 n n n_n nn,平均灰度值为 m 1 m_1 m1,图像的平均灰度值为 m G m_G mG。
- 类间方差
σ
b
2
\sigma_{b}^{2}
σb2 的计算公式为:
σ b 2 = w 0 × ( m 0 − m G ) 2 + w 1 × ( m 1 − m G ) 2 \sigma_{b}^{2} = w_{0} \times (m_{0} - m_{G})^{2} + w_{1} \times (m_{1} - m_{G})^{2} σb2=w0×(m0−mG)2+w1×(m1−mG)2 - 其中, w 0 w_{0} w0 和 w 1 w_{1} w1 分别是背景类和前景类像素出现的概率, w 0 = n 0 N w_{0} = \frac{n_{0}}{N} w0=Nn0, w 1 = n 1 N w_{1} = \frac{n_{1}}{N} w1=Nn1,且 w 0 + w 1 = 1 w_{0} + w_{1} = 1 w0+w1=1。
- 寻找最佳阈值的过程:
- 遍历所有可能的灰度阈值(从 0 到 L-1),针对每个阈值按照上述分类方式计算类间方差。
- 最终选择使得类间方差最大的那个阈值作为最佳阈值,因为当类间方差最大时,意味着前景和背景两类的区分度最大,也就是二值化后能最清晰地分离出目标物体和背景。
4 代码实现
Python:
import cv2
import numpy as np
import matplotlib.pyplot as plt
image = cv2.imread('your_image.jpg', 0)
# 全局阈值二值化
ret, simple_thresh = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
# 自适应阈值二值化(采用均值自适应方法)
adaptive_thresh = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 11, 2)
# Otsu阈值二值化
ret_otsu, otsu_thresh = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
images = [image, simple_thresh, adaptive_thresh, otsu_thresh]
titles = ['Original Image', 'Simple Thresholding', 'Adaptive Thresholding', 'Otsu Thresholding']
for i in range(len(images)):
plt.subplot(2, 2, i + 1)
plt.imshow(images[i], cmap='gray')
plt.title(titles[i])
plt.axis('off')
plt.show()
C++:
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 读取图像(灰度图)
cv::Mat image = cv::imread("your_image.jpg", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cout << "Could not open or find the image!" << std::endl;
return -1;
}
// 全局阈值二值化
cv::Mat simple_thresh;
cv::threshold(image, simple_thresh, 127, 255, cv::THRESH_BINARY);
// 自适应阈值二值化(均值自适应方法)
cv::Mat adaptive_thresh;
cv::adaptiveThreshold(image, adaptive_thresh, 255, cv::ADAPTIVE_THRESH_MEAN_C,
cv::THRESH_BINARY, 11, 2);
// Otsu阈值二值化
cv::Mat otsu_thresh;
double otsu_thresh_val;
otsu_thresh_val = cv::threshold(image, otsu_thresh, 0, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);
// 显示图像
cv::namedWindow("Original Image", cv::WINDOW_NORMAL);
cv::namedWindow("Simple Thresholding", cv::WINDOW_NORMAL);
cv::namedWindow("Adaptive Thresholding", cv::WINDOW_NORMAL);
cv::namedWindow("Otsu Thresholding", cv::WINDOW_NORMAL);
cv::imshow("Original Image", image);
cv::imshow("Simple Thresholding", simple_thresh);
cv::imshow("Adaptive Thresholding", adaptive_thresh);
cv::imshow("Otsu Thresholding", otsu_thresh);
// 打印Otsu阈值
std::cout << "Otsu's Threshold: " << otsu_thresh_val << std::endl;
cv::waitKey(0); // 等待用户按键
return 0;
}
原文地址:https://blog.csdn.net/MariLN/article/details/144015706
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!