在图像处理中,经常会在经过多种图像处理算法之后,需要决定最终保留哪部分像素,或者在保留其他像素的情况下忽略低于或高于某个值的像素。OpenCV函数threshold()可以完成这些任务。这就是图像阈值处理。其基本思想是给定一个阈值,然后根据灰度值是否低于或高于阈值,来决定保留哪些像素。也可以把阈值看作是一个非常简单的卷积或滤波操作,它使用1×1内核,然后在该像素上执行卷积操作:
double cv::threshold(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
double thresh, // Threshold value
double maxValue, // Max value for upward operations
int thresholdType // Threshold type to use
);
上面是阈值函数的原型。根据像素与阈值之间的关系,目标像素dst可以被设置为0、src或给定的最大值maxValue。
表1。 threshold()的阈值类型选项
阈值类型 | 操作说明 |
THRESH_BINARY | DST = (SRC > thresh) ? MAXVALUE : 0 |
THRESH_BINARY_INV | DST = (SRC > thresh) ? 0 : MAXVALU |
THRESH_TRUNC | DST = (SRC > thresh) ? THRESH : SRC |
THRESH_OTSU | 大律法 |
THRESH_TOZERO | DST = (SRC > thresh) ? SRC : 0 |
THRESH_TOZERO_INV | DST = (SRC > thresh) ? 0 : SRC |
看一个简单的例子。 在例1中,将图像的所有三个通道相加,然后进行各种阈值操作。
例1。 使用threshold()来求一个图像三个通道的和。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void sum_rgb(const cv::Mat& src, cv::Mat& dst_BINARY,cv::Mat& dst_BINARY_INV,cv::Mat& dst_TRUNC, cv::Mat& dst_OTSU, cv::Mat& dst_TOZERO, cv::Mat& dst_TOZERO_INV)
{
vector<Mat> planes;
split(src, planes);
Mat b = planes[0], g = planes[1], r = planes[2], s;
addWeighted(r, 1. / 3., g, 1. / 3., 0.0, s);
addWeighted(s, 1., b, 1. / 3., 0.0, s);
threshold(s, dst_BINARY, 100, 100, cv::THRESH_BINARY);
threshold(s, dst_BINARY_INV, 100, 100, cv::THRESH_BINARY_INV);
threshold(s, dst_TRUNC, 100, 100, cv::THRESH_TRUNC);
threshold(s, dst_OTSU, 100, 100, cv::THRESH_OTSU);
threshold(s, dst_TOZERO, 100, 100, cv::THRESH_TOZERO);
threshold(s, dst_TOZERO_INV, 100, 100, cv::THRESH_TOZERO_INV);
}
int main(int argc, char** argv)
{
Mat src = imread("E:/1.jpg",1);
namedWindow("src", 0);
imshow("src", src);
Mat dst_BINARY,dst_BINARY_INV, dst_TRUNC, dst_OTSU, dst_TOZERO, dst_TOZERO_INV;
sum_rgb(src, dst_BINARY, dst_BINARY_INV, dst_TRUNC, dst_OTSU, dst_TOZERO, dst_TOZERO_INV);
namedWindow("dst_BINARY", 0);
imshow("dst_BINARY", dst_BINARY);
namedWindow("dst_BINARY_INV", 0);
imshow("dst_BINARY_INV", dst_BINARY_INV);
namedWindow("dst_TRUNC", 0);
imshow("dst_TRUNC", dst_TRUNC);
namedWindow("dst_OTSU", 0);
imshow("dst_OTSU", dst_OTSU);
namedWindow("dst_TOZERO", 0);
imshow("dst_TOZERO", dst_TOZERO);
namedWindow("dst_TOZERO_INV", 0);
imshow("dst_TOZERO_INV", dst_TOZERO_INV);
waitKey(0);
return 0;
}
这里没有直接将各个通道的值相加,因为高位会溢出。相反,使用三个颜色通道的加权相加; 那么总和被截断,返回值为100。在例1中我们使用浮点型临时图像,也可以用accumulate()将8位整数图像类型累加到浮点图像中,如下所示。
cv::Mat b = planes[0], g = planes[1], r = planes[2];
cv::Mat s = cv::Mat::zeros(b.size(), CV_32F);
cv::accumulate(b, s);
cv::accumulate(g, s);
cv::accumulate(r, s);
可以通过传递THRESH_OTSU作为thresh的值来实现让threshold()确定阈值的最佳值。这就是大律法二值化算法。简言之,Otsu的算法是考虑所有可能的阈值,并计算类方差。事实证明,以这种方式最小化两个类别的方差与最大化两个类别之间的方差相同。 由于需要详尽搜索可能的阈值空间,因此速度有点受影响。
有一种改进的的阈值方法,称为自适应阈值二值化。AdaptiveThreshold()函数可以实现自适应二值化。该算法阈值本身是可变的。
void cv::adaptiveThreshold(
cv::InputArray src, // Input image
cv::OutputArray dst, // Result image
double maxValue, // Max value for upward operations
int adaptiveMethod, // mean or Gaussian
int thresholdType // Threshold type
int blockSize, // Block size
double C // Constant
);
adaptiveThreshold()允许两种不同的自适应阈值类型,具体取决于adaptiveMethod的设置。在这两种情况下,通过计算每个像素位置周围的b×b区域的加权平均值减去常数来设置自适应阈值T,其中b由blockSize给出,常数由C给出。如果该方法设置为ADAPTIVE_THRESH_MEAN_C,则该区域中的所有像素的权重相等。如果它被设置为c ADAPTIVE_THRESH_GAUSSIAN_C,则区域中的像素根据它们距该中心点的距离的高斯函数来加权。参数thresholdType threshold()相同。
自适应阈值在需要相对于一般强度梯度阈值较强的光照或反射梯度时非常有用。该函数仅处理8位单通道或浮点图像,并且要求源图像和目标图像不同。例2比较adaptiveThreshold()和threshold()。图3说明了使用这两种函数处理图像的结果。
例2。 阈值与自适应阈值对比
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat src = imread("E:/棋盘格.png", 0);
Mat thrImg, adpImg;
cv::threshold(src, thrImg,50,255,THRESH_BINARY);
cv::adaptiveThreshold(src, adpImg,255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY,9,3);
cv::imshow("src", src);
cv::imshow("thrImg", thrImg);
cv::imshow("Adaptive Threshold", adpImg);
cv::waitKey(0);
return 0;
}