http://www.cnblogs.com/CoverCat/p/5043833.html
Visual Studio Community 2015 工程和代码:http://pan.baidu.com/s/1o7lxYSM
内容
在这篇文章中将提到以下内容:
- 全局阈值
- 自适应阈值
- Otsu's二值化
在图像处理中,会希望忽略掉一些灰度细节,只保留主体的轮廓,对灰度图像进行阈值化处理能达到这个目的。
“其基本的思想是,给定一个数组和一个阈值,然后根据数组中的每个元素的值是低于还是高于阈值而进行一些处理”——《学习OpenCV(中文版)》,这里说的“数组”即为图像数据,
而“进行一些处理”说的是进行分类,只有两类,根据值不同分到不同的类中。
准备工作
- 创建工程——参考创建一个名为“Threshold”WinForm项目
- 在Form1.cs中引用命名空间:
1 using Emgu.CV;2 using Emgu.CV.Structure;3 using Emgu.CV.CvEnum;
- 界面:在Form1中添加1行2列的TableLayoutPanel容器,然后添加2个Emgu.CV.UI.ImageBox控件(),添加后界面如下:
- 设置imageBox1和imageBox2的SizeMode属性为StretchImage
全局阈值
全局阈值指的是整个图像数据使用一个阈值进行筛选分类,OpenCV提供cvThreshold()方法进行阈值化操作,在Emgu中对应的方法名称为Threshold。
Threshold方法接受一个类型为ThresholdType的参数,ThresholdType是一个枚举类型,其枚举值为:
1 Binary = 0,2 BinaryInv = 1,3 Trunc = 2,4 ToZero = 3,5 ToZeroInv = 4,6 Mask = 7,//这里不做介绍7 Otsu = 8 //后面会做介绍
- Binary(二进制阈值化)——二进制阈值化是指将大于阀值的像素点设置为最大值,而小于阀值的像素点设置为0,即:
value = value > threshold ? max_value : 0
- BinaryInv(反向二进制阈值化)——与二进制阈值化正好相反,反向二进制阈值化是在像素点的值大于阀值时像素点设置为0,反之则设置为最大值,即:
value = value > threshold ? 0 : max_value
- Trunc(截断阈值化)——截断阈值化是指当像素点的值大于阀值时,像素点的值设置为阀值,反之则保留像素值本身,即:
value = value > threshold ? threshold : value
- ToZero(超阈值归零化)——超阈值归零化是指当像素点的值大于阀值时,像素点保留原值,反之则设置为0,即:
value = value > threshold ? value : 0
- ToZeroINV(低于阈值归零化)——与超阈值归零化相反,低于阈值归零化是指当像素点的值大于阀值时,像素点值被设置为0,反之则保留像素点原值,即:
value = value > threshold ? 0 : value
从上边描述我们可以看出二进制阈值化/反二进制阈值化需要一个max_value(最大值)的参数,同时,二进制阈值化/反二进制阈值化处理后图像数据中只存在两种
可能的值:0和max_value,这种图像称为二值化图像。
“在中,二值图像占有非常重要的地位,首先,图像的二值化有利于图像的进一步处理,使图像变得简单,而且数据量减小,能凸显出感兴趣的目标的轮
廓。其次,要进行二值图像的处理与分析,首先要把灰度图像二值化,得到二值化图像。所有灰度大于或等于阈值的像素被判定为属于特定物体,其灰度值为255表示,
否则这些像素点被排除在物体区域以外,灰度值为0,表示背景或者例外的物体区域。”——百度百科
下面的代码显示了如何使用二进制阈值化处理,如果你要使用不是二进制阈值化处理,那么max_value可以不用在意设什么值。
1 private void Form1_Load(object sender, EventArgs e) 2 { 3 using (var image = new Image(Properties.Resources.chess3)) 4 { 5 var grayImage = image.Convert ();//转为灰度图 6 var threshImage = grayImage.CopyBlank(); 7 8 CvInvoke.Threshold( 9 grayImage,10 threshImage, 11 150, //阀值12 255, //最大值13 ThresholdType.Binary);//二进制阈值化14 15 imageBox1.Image = grayImage;16 imageBox2.Image = threshImage;17 }18 }
效果如下: Binary -> BinaryInv -> Trunc -> ToZero -> ToZeroInv
自适应阈值
全局阈值是整幅图像使用一个阀值,这并不能适应所有的情况。自适应阈值是图像的不同的区域使用不同的阀值,而阀值是对这个区域计算得来的,OpenCV提供cvAdaptiveThreshold()
函数进行自适应阈值化处理,这个函数提供两种计算阀值的方法,分别为CV_ADAPTIVE_THRESH_MEAN_C和CV_ADAPTIVE_THRESH_GAUSSIAN_C。
“在这两种情况下,自适应阈值T(x,y)在每个像素点都不同。通过计算像素点周围的b x b区域的加权平均,然后减去一个常数来得到自适应阈值, b有参数block_size指定,常数有param1
指定。如果使用CV_ADAPTIVE_THRESH_MEAN_C方法,那么对区域的所有像素平均加权。如果使用了CV_ADAPTIVE_THRESH_GAUSSIAN_C放,那么区域中的(x,y)周围的像素
根据高斯函数按照它们离中心点的距离进行加权计算。”——《学习OpenCV(中文版)》
Emgu中CVInvoke类提供了AdaptiveThreshold静态方法进行自适应阈值处理,这个方法的原型为:
1 public static void AdaptiveThreshold(2 IInputArray src, //原图像3 IOutputArray dst, //结果图像4 double maxValue, //二进制阈值化/反二进制阈值化处理使用到的最大值5 CvEnum.AdaptiveThresholdType adaptiveType, // 自适应阈值计算方式:MeanC或GaussianC6 CvEnum.ThresholdType thresholdType, //阈值化方式,必须为二进制阈值化和反二进制阈值化之一(Binary / BinaryInv)7 int blockSize, //计算使用的区域矩阵大小:3,5,7,9...8 double param1) //常数
同时,Image类也提供了一个封装了AdaptiveThreshold方法的ThresholdAdaptive方法,下面的代码中使用的几位Image类的ThresholdAdaptive方法:
1 private void Form1_Load(object sender, EventArgs e) 2 { 3 var grayImage = new Image(Properties.Resources.chess3); 4 //CvInvoke.AdaptiveThreshold(grayImage, threshImage, 255, AdaptiveThresholdType.GaussianC, ThresholdType.BinaryInv, 12, 5); 5 var threshImage = grayImage.ThresholdAdaptive( 6 new Gray(255), 7 AdaptiveThresholdType.MeanC, 8 ThresholdType.Binary, 9 9, 10 new Gray(5));11 12 imageBox1.Image = grayImage;13 imageBox2.Image = threshImage;14 }
执行效果为:MeanC -> GaussianC
Otsu二值化
在全局阈值的代码中,我们使用150作为二进制阈值化的阀值,但是这个阀值是我随意选择的,我并不确定这个阀值是否是合适的阈值。而如果使用Otsu二值化方法,它可以根据
图像的直方图计算出一个阀值。使用Otsu方法用到的是全局阈值提到过的Threshold方法,只是在传入的ThresholdType值为Otsu,代码如下:
1 var grayImage = new Image(Properties.Resources.chess3); 2 var threshImage = grayImage.CopyBlank(); 3 CvInvoke.Threshold( 4 grayImage, 5 threshImage, 6 0, 7 255, 8 ThresholdType.Otsu); 9 10 imageBox1.Image = grayImage;11 imageBox2.Image = threshImage;
运行效果: