图像二值化
应用场景二值图像定义阈值获取的方法手动阈值法自动阈值法灰度均值法基于直方图均值法OTSUTriangle自适应均值阈值分割方法总结参考文献应用场景
二值图像处理与分析在机器视觉与机器人视觉中非常重要,涉及到非常多的图像处理相关的知识,常见的二值图像分析包括轮廓分析、对象测量、轮廓匹配与识别、形态学处理与分割、各种形状检测与拟合、投影与逻辑操作、轮廓特征提取与编码等。二值化方法
是一种应用广泛的图像分割方法,恰当的二值化结果对于 文档图像分析、OCR以及医学图像中对 DNA 点阵图像分析等起着至关重要的作用。
通常可以分为全局二值化和局部二值化方法两类,前者将一个固定的阈值应用于整幅图像,简单易行,但在光照不均匀的条件难以应用,如 Otsu 法;后者则针对图像的不同部分采用不同的阈值来解决光照问题,其阈值实际是一个随像素变化的曲面。本文将着重介绍如何从一幅图像中获取有目的性的二值图像。
二值图像定义
二值图像就是只有黑白两种颜色表示的图像,在数字上用0 表示黑色(0),1表示白色(255) 。在实际场景中,二值图像的获得一般需要经过如下过程。
从灰度图像到二值图像,本质上是对数据的二分类分割,所以很多数据处理的方法都可以使用,但是图像是特殊类型的数据,它有很多限制条件,决定了只有一些合适的方法才会取得比较好的效果。这些算法的最主要的一个任务就是寻找合理的分割阈值T。
阈值获取的方法
二值化分割可分为手动阈值分割和自动阈值分割,前者是根据整幅图像的特征进行分析进而确定,目前常用的方法包括手动阈值法及自动阈值法,
手动阈值分割自动阈值分割基于灰度图均值的自动分割基于直方图的自动分割OTSU(直方图出现双峰)Triangle(直方图出现单峰)自适应阈值分割
下面以下图为例,对上述几种阈值分割方法进行分析和对比。
手动阈值法
该阈值T需要人为给定,取值范围为0-255,常规的定义为灰度图像上某点像素值 P(x, y) > T ? 255 : 0,除此之外,还有几种变式如下:
P(x, y) > T ? 0: 255,表示像素值大于阈值时,为0,否则为255;P(x, y) > T ? T : 0,表示像素值大于阈值时,为0,否则为255;P(x, y) > T ? T : P(x, y),表示像素值大于阈值时,为阈值,否则为原像素;P(x, y) > T ? P(x, y) : 0,表示像素值大于阈值时,为原像素,否则为0;P(x, y) > T ? 0 : P(x, y),表示像素值大于阈值时,为0,否则为原像素;
图解表示为:
在OPENCV中有相应的API可以使用,可以根据任务需求,选择不同的type值。
函数原型:double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type) 参数含义: InputArray src -输入原图像,需为灰度图像 OutputArray dst -输出图像 double thresh-阈值大小 (即阈值T)double maxval-最大值 (一般指定为255,也可指定为其他数值)int type-阈值模式(很重要,它决定这个函数的变式,0为常规型的,1-4均为变式) 其中,type可以取0-4中的任一个值,对应的含义如下: 0: THRESH_BINARY -当前点值大于阈值时,取Maxval,否则设置为0 ; 1: THRESH_BINARY_INV -当前点值大于阈值时,设置为0,否则设置为Maxval; 2: THRESH_TRUNC -当前点值大于阈值时,设置为阈值,否则不改变; 3: THRESH_TOZERO -当前点值大于阈值时,不改变,否则设置为0 ;4: THRESH_TOZERO_INV -当前点值大于阈值时,设置为0,否则不改变。
设阈值T = 120 ,type分别取0-4,可以得到以下结果:
1) type = 0
2)type = 1
3)type = 2
4) type = 3
5) type = 4
总结:
要使用手动阈值分割法获取理想的二值图,很关键的两个点是T与type,需要多次尝试,以经验加枚举的方式挨个测试,最终确定一个合适的阈值。但此阈值仅适用于这一特定的场景,光照等因素的改变可能导致阈值不再适用。所以,此方法适用与场景单一、固定的场合,如工业车间、流水线等机器人视觉上。
自动阈值法
可以看出手阈值法需要多次尝试且应用场景单一,局限性较大。自动阈值分割方法可以根据环境进行阈值调整,适应性也更广。自动阈值分割法包括基于灰度均值的自动分割、基于直方图的自动分割和自适应阈值分割。下面分别对这几种方法进行分析:
灰度均值法
此方法较为简单,即将灰度图像的像素均值作为阈值T,得到的图像如下图所示,关键代码为:
Mat src = imread("E:/images/aaa.jpg");Mat gray, gray_mean;cvtColor(src, gray, COLOR_BGR2GRAY);meanStdDev(gray, gray_mean, mat_stddev);double m;m = mat_mean.at<double>(0, 0);binary = Mat::zeros(src.size(), CV_8UC1);int height = gray.rows;int width = gray.cols;for (int row = 0; row < height; row++) {for (int col = 0; col < width; col++) {int pv = gray.at<uchar>(row, col);if (pv > m) {binary.at<uchar>(row, col) = 255;}else {binary.at<uchar>(row, col) = 0;}}}imshow("binary", binary);waitKey(0);return 0;}
基于直方图均值法
此方法是根据直方图的特征进行阈值的选择和求取,在此之前,我们先获取下灰度图像的直方图分布图如图所示,关键代码为如下,这个过程在使用直方图均值法是不需要的,为了说明问题,单独显示下直方图:
// 本段代码,在二值化中不需要,opencv中API中已包括,只是为了说明问题Mat src = imread("E:/images/aaa.jpg");Mat gray, gray_mean;cvtColor(src, gray, COLOR_BGR2GRAY);//定义变量Mat dstHist;int dims = 1;float hranges[] = {0, 256};const float *ranges[] = {hranges}; // 这里需要为const类型int size = 256;int channels = 0; //计算图像的直方图calcHist(&gray, 1, &channels, Mat(), dstHist, dims, &size, ranges);Mat dstImage(size, size, CV_8U, Scalar(0)); //获取最大值和最小值double minValue = 0;double maxValue = 0;minMaxLoc(dstHist,&minValue, &maxValue, 0, 0); // 在cv中用的是cvGetMinMaxHistValue //绘制出直方图 //saturate_cast函数的作用即是:当运算完之后,结果为负,则转为0,结果超出255,则为255。int hpt = saturate_cast<int>(0.9 * size);for(int i = 0; i < 256; i++){float binValue = dstHist.at<float>(i); // 注意hist中是float类型 int realValue = saturate_cast<int>(binValue * hpt/maxValue);line(dstImage,Point(i, size - 1),Point(i, size - realValue),Scalar(255));}imshow("一维直方图", dstImage);}
可以从直方图分布图中看出,灰度多集中在靠近白色的一端,即只有一个明显的峰值,整体图像偏白。在opencv中基于直方图获取阈值并分割的功能,实现方式仍然是使用threshold函数,只是将type类型声明为THRESH_OTSU或THRESH_RRIANGLE即可。
函数原型:double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type) 参数含义: InputArray src -输入原图像,需为灰度图像 OutputArray dst -输出图像 double thresh-阈值大小,取 0 double maxval-最大值,取 255int type-阈值模式其中,type可以取0-4中的任一个值,对应的含义如下: THRESH_BINARY | THRESH_OTSU 使用最大类间差分法获取阈值,然后再用该阈值进行二分 ; THRESH_BINARY | THRESH_TRIANGLE 使用三角法获取阈值,然后再用该阈值进行二分 ;
OTSU算法对直方图有两个峰,中间有明显波谷的直方图对应图像二值化效果比较好,而对于只有一个单峰的直方图对应的图像分割效果没有双峰的好。
OTSU
OTSU的是通过计算类间最大方差来确定分割阈值的阈值选择算法,它的原理是不断地求前景和背景的类件方差:
如图所示,在灰度直方图中,先将0,1,2三个灰度作为背景,求取背景类内方差,将3,4,5作为前景,求取前景类内方差。然后根据两个类内方差,求取两个类间的类间方差。如此,依次求 0 与1,2,3,4,5之间,0,1,与2,3,4,5之间,…直到遍历完整个灰度组数。然后找类间方差最大的,则灰度分界也就找到了,此分界即为所要的阈值。在opencv中实现的方法如下:
#include <opencv2/opencv.hpp>#include <iostream>using namespace cv;using namespace std;int main(){Mat src = imread("E:/images/aaa.jpg");Mat gray, binary;cvtColor(src, gray, COLOR_BGR2GRAY);double T = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);cout<<"threshold : %.2f\n"<< T <<endl;imshow("binary", binary);waitKey(0);return 0;}
此时返回的阈值T = 157,即采用阈值157进行常规的二值分割,其分割的结果如下图所示:
Triangle
在但是有时候图像的直方图只有一个波峰,这个时候使用TRIANGLE方法寻找阈值是比较好的一个选择。
OpenCV中TRIANGLE算法使用只需要在 threshold函数的type类型声明THRESH_TRIANGLE即可。程序如下:
#include <opencv2/opencv.hpp>#include <iostream>using namespace cv;using namespace std;int main(){Mat src = imread("E:/images/aaa.jpg");Mat gray, binary;cvtColor(src, gray, COLOR_BGR2GRAY);double T = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_TRIANGLE);cout<<"threshold : %.2f\n"<< T <<endl;imshow("binary", binary);waitKey(0);return 0;}
此时返回的阈值T = 215,即采用阈值215进行常规的二值分割,其分割的结果如下图所示:
自适应均值阈值分割方法
OpenCV中的自适应阈值算法主要是基于均值实现,根据计算均值的方法不同分为box-filter模糊均值与高斯模糊均值,其一般步骤为:
在opencv中有相关的API可以调用,如下:
void cv::adaptiveThreshold( InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C )
其中:
Mat src = imread("E:/images/aaa.jpg");Mat gray, binary;cvtColor(src, gray, COLOR_BGR2GRAY);adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 25, 10);imshow("binary", binary);waitKey(0);return 0;
运行效果如下:
总结
在实际二值化应用中,阈值要根据实际场景和目标物特征进行选择,如果对于光照不均匀的场合应该选择自适应阈值分割,对于直方图出现明显双峰的应该选择OTSU,对与直方图出现单峰的应该选择Triangle,双峰还是单峰的分辨,可以看偏黑或偏白的占比,也可尝试运行两种方法,观察效果。
参考文献
[1]: 贾志刚,《OpenCV Android开发实战》
如果觉得《图像二值化方法及适用场景分析(OTSU Trangle 自适应阈值分割)》对你有帮助,请点赞、收藏,并留下你的观点哦!