图像二值化
应用场景 二值图像定义 阈值获取的方法 手动阈值法 自动阈值法 灰度均值法 基于直方图均值法
自适应均值阈值分割方法
总结
应用场景
二值图像处理与分析 在机器视觉与机器人视觉中非常重要,涉及到非常多的图像处理相关的知识,常见的二值图像分析包括轮廓分析、对象测量、轮廓匹配与识别、形态学处理与分割、各种形状检测与拟合、投影与逻辑操作、轮廓特征提取与编码等。二值化方法
是一种应用广泛的图像分割方法,恰当的二值化结果对于 文档图像分析、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 ;
}
基于直方图均值法
此方法是根据直方图的特征进行阈值的选择和求取,在此之前,我们先获取下灰度图像的直方图分布图如图所示,关键代码为如下,这个过程在使用直方图均值法是不需要的,为了说明问题,单独显示下直方图:
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} ;
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 ) ;
int hpt = saturate_cast< int> ( 0.9 * size) ;
for ( int i = 0 ; i < 256 ; i++ )
{
float binValue = dstHist. at< float> ( i) ;
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 - 最大值,取 255
int 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模糊均值与高斯模糊均值,其一般步骤为:
灰度化
高斯模糊或box模糊
原图像-均值图像
自适应分割
彩色图像
灰度图像
均值图像
差值图像
二值图像
在opencv中有相关的API可以调用,如下:
void cv: : adaptiveThreshold ( InputArray src, OutputArray dst, double maxValue, int
adaptiveMethod, int thresholdType, int blockSize, double C )
其中:
参数 取值 blockSize 取值必须是奇数,如果输入图像较大,取127左右,对于小图像取25左右 C 取值多少与效果有很大关系,不能取高,也不能取低,一般取值在10/15/25 adaptiveMethod ADAPTIVE_THRESH_GAUSSIAN_C = 1 , ADAPTIVE_THRESH_MEAN_C = 0 thresholdType THRESH_BINARY 二值图像 = 原图 – 均值图像 > -C ? 255 : 0 ,THRESH_BINARY_INV 二值图像 = 原图 – 均值图像 > -C ? 0 : 255
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开发实战》
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)