前言
通过上文的介绍,我们了解了三种图像,二值图像、灰度图像、彩色图像。三种图像的介绍。
在图像处理中,对于同样的操作,处理灰度图像的计算量要远远小于处理彩色图像,而二值图像(只含灰度值0或1)的计算量比前两者更小。因此,二值化操作在图像处理中有着很大的作用。
二值化图像的实现方法有很多。用的最多的方法是利用图像像素点分布规律,设置阈值进行像素点分割,从而得到二值化图像。
一、大津法(OTSU)阈值化
在阈值处理中,最常用的方法就是大津法,因为其计算简单,不受图像亮度和对比度的影响。从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。
1.大津法算法步骤
1)计算图像的直方图,统计每个像素在整幅图像中的个数。
//统计灰度级中每个像素在整幅图像中的个数 for (int i = 0; i < nCols; i++) { for (int j = 0; j < nRows; j++) { nSumPix[(int)Image.at<uchar>(i, j)]++; } }
2)计算每个像素在整幅图像中的占比
//计算每个灰度级占图像中的概率分布 for (int i = 0; i < 256; i++) { nProDis[i] = (float)nSumPix[i] / (nCols * nRows); }
3)对灰度值进行遍历,统计前景像素)所占整幅图像的比例,平均灰度,背景像素所占整幅图像的比例,背景像素的平均灰度。
//遍历灰度级[0,255],计算出最大类间方差下的阈值 float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp; double delta_max = 0.0; for (int i = 0; i < 256; i++) { //初始化相关系数 w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0; for (int j = 0; j < 256; j++) { //背景部分 if (j <= i) { //当前i为分割阈值,第一类总的概率 w0 += nProDis[j]; u0_temp += j * nProDis[j]; } //前景部分 else { w1 += nProDis[j]; u1_temp += j * nProDis[j]; } } //分别计算各类的平均灰度 u0 = u0_temp / w0; u1 = u1_temp / w1; delta_temp = (float)(w0 * w1 * pow((u0 - u1), 2)); }
4)计算图像的全局阈值
//依次找到最大类间方差下的阈值 if (delta_temp > delta_max) { delta_max = delta_temp; threshold = i; }
2、代码演示
整体代码演示:
#include<stdio.h> #include<string> #include <opencv2/highgui/highgui.hpp> #include <opencv2/opencv.hpp> #include <opencv2/imgproc/types_c.h> using namespace cv; using namespace std; int OTSU(cv::Mat srcImage) { int nCols = srcImage.cols; int nRows = srcImage.rows; int threshold = 0; // 初始化统计参数 int nSumPix[256]; float nProDis[256]; for (int i = 0; i < 256; i++) { nSumPix[i] = 0; nProDis[i] = 0; } //统计灰度级中每个像素在整幅图像中的个数 for (int i = 0; i < nRows; i++) { for (int j = 0; j < nCols; j++) { nSumPix[(int)srcImage.at<uchar>(i, j)]++; } } //计算每个灰度级占图像中的概率分布,平均数 for (int i = 0; i < 256; i++) { nProDis[i] = (float)nSumPix[i] / (nCols * nRows); } // 遍历灰度级[0,255],计算出最大类间方差下的阈值 float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp; double delta_max = 0.0; for (int i = 0; i < 256; i++) { // 初始化相关参数 w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0; for (int j = 0; j < 256; j++) { //背景部分 if (j <= i) { //当前i为分割阈值,第一类总的概率 w0 += nProDis[j]; u0_temp += j * nProDis[j]; } //前景部分 else { // 当前i为分割阈值,第一类总的概率 w1 += nProDis[j]; u1_temp += j * nProDis[j]; } } // 分别计算各类的平均灰度 u0 = u0_temp / w0; u1 = u1_temp / w1; delta_temp = (float)(w0 *w1* pow((u0 - u1), 2)); // 依次找到最大类间方差下的阈值 if (delta_temp > delta_max) { delta_max = delta_temp; threshold = i; } } return threshold; } int main() { cv::Mat srcImage = cv::imread("...c.webp"); if (!srcImage.data) return 1; cv::Mat srcGray; cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY); cv::imshow("srcGray", srcGray); //利用 OTSU 二值化算法得到阈值 int ostuThreshold = OTSU(srcGray); std::cout << ostuThreshold << endl; //定义输出结果图像 cv::Mat otsuResultImage = cv::Mat::zeros(srcGray.rows, srcGray.cols, CV_8UC1); for (int i = 0; i < srcGray.rows; i++) { for (int j = 0; j < srcGray.cols; j++) { if (srcGray.at<uchar>(i, j) > ostuThreshold) { otsuResultImage.at<uchar>(i, j) = 255; } else { otsuResultImage.at<uchar>(i, j) = 0; } } } cv::imshow("otsuResultImage", otsuResultImage); cv::waitKey(0); return 0; }
3、运行效果
PS: 处理的有点吓人