1 全局固定阈值分割
threshold(image, global, th, 255, CV_THRESH_BINARY_INV);
一副图像包括目标、背景和噪声,设定某一阈值T将图像分成两部分:大于T的像素群和小于T的像素群。
在实际处理时候,为了显示需要一般用255表示背景,用0表示对象物。
由于实际得到的图像目标和背景之间不一定单纯地分布在两个灰度范围内,此时就需要两个或以上的阈值来提取目标。
图像阈值化分割是一种传统的最常用的图像分割方法,因其实现简单、计算量小、性能较稳定而成为图像分割中最基本和应用最广泛的分割技术。它特别适用于目标和背景占据不同灰度级范围的图像。难点在于如何选择一个合适的阈值实现较好的分割。
cv::threshold(image, global, th, 255, CV_THRESH_BINARY_INV);
adaptiveThreshold(image, local, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, blockSize, constValue);
局部自适应阈值则是根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值。这样做的好处在于每个像素位置处的二值化阈值不是固定不变的,而是由其周围邻域像素的分布来决定的。亮度较高的图像区域的二值化阈值通常会较高,而亮度较低的图像区域的二值化阈值则会相适应地变小。不同亮度、对比度、纹理的局部图像区域将会拥有相对应的局部二值化阈值。常用的局部自适应阈值有:1)局部邻域块的均值;2)局部邻域块的高斯加权和。
int main(int argc, char** argv)
{
Mat image = imread("E:/VS2013/face/xuelian/png/1.png", CV_LOAD_IMAGE_GRAYSCALE);
if (image.empty())
{
cout << "read image failure" << endl;
return -1;
}
// 全局二值化
int th = 100;//阈值
Mat global;
threshold(image, global, th, 255, CV_THRESH_BINARY_INV);
// 局部二值化
int blockSize = 7;
int constValue = 11;
Mat local;
adaptiveThreshold(image, local, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, blockSize, constValue);
imshow("globalThreshold", global);
imshow("localThreshold", local);
waitKey(0);
return 0;
}
adaptiveThreshold(
const CvArr* src, // 输入图像.
CvArr* dst, // 输出图像.
double max_value, // max_value:使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值.
int adaptive_method=CV_ADAPTIVE_THRESH_MEAN_C, // adaptive_method:自适应阈值算法使用: //CV_ADAPTIVE_THRESH_MEAN_C 或 CV_ADAPTIVE_THRESH_GAUSSIAN_C
int threshold_type=CV_THRESH_BINARY, // threshold_type:取阈值类型:必须是CV_THRESH_BINARY或者 CV_THRESH_BINARY_INV
int block_size=3,// block_size:用来计算阈值的象素邻域大小: 3, 5, 7, ...
double param=5 )// param:与方法有关的参数。对方法 CV_ADAPTIVE_THRESH_MEAN_C 和 CV_ADAPTIVE_THRESH_GAUSSIAN_C,
它是一个从均值或加权均值提取的常数(见讨论), 尽管它可以是负数。
对于max_value中的两个方式中的T是为每一个象素点单独计算的阈值,即每个像素点的阈值都是不同的,就是将该像素点周围B*B区域内的像素加权平均然后减去一个常数param,从而得到该点的阈值。b由block_size指定,常数由param1指定
对方法 CV_ADAPTIVE_THRESH_MEAN_C,先求出块中的均值,再减掉param。
对方法 CV_ADAPTIVE_THRESH_GAUSSIAN_C ,那么区域中(x,y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算, 再减掉param。
最大方差阈值的基本思想是:把直方图在某一阈值处分割成两组,当被分成的的两组之间方差最大时,决定阈值。
设图像的灰度值为0~m-1级,灰度值i的像素数为ni,此时我们得到像素总数:
然后用T将其分成两组C0={0~T-1},C1={T~m-1},各组的概率如下
平均值为:
两组间的方差用下式求出:
从1~m-1之间改变T,求上式为最大时的T,Tmax即为我们需要的阈值。所以上式称为阈值选择函数。
threshold(g_grayImage, g_ndstImage, 133, 255, THRESH_OTSU);
最大类间方差法是由日本学者大津(Nobuyuki Otsu)于1979年提出的,是一种自适应的阈值确定的方法,又叫大津法,简称OTSU。它是按图像的灰度特性,将图像分成背景和目标2部分。背景和目标之间的类间方差越大,说明构成图像的2部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致2部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均
灰度记为μ,类间方差记为g。假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
ω0=N0/ M×N (1)
ω1=N1/ M×N (2)
N0+N1=M×N (3)
ω0+ω1=1 (4)
μ=ω0*μ0+ω1*μ1 (5)
g=ω0(μ0-μ)^2+ω1(μ1-μ)^2 (6)
将式(5)代入式(6),得到等价公式:
g=ω0ω1(μ0-μ1)^2 (7)
采用遍历的方法得到使类间方差最大的阈值T,即为所求。
由于,当图像在254或255灰度值上没有像素点时,求平均灰度时会出现0/0的情况,为避免抛出异常,可在当出现前景像素数为零时,跳出循环。
采用遍历的方法得到使类间方差最大的阈值,即为所求。
在大津法中,我们定义组内方差为
通过选择使得上述组内方差最小化时的阈值 t,就可以使得图像中的前景和背景尽可能的被区别开(假设我们将最终图像里被分开的两部分称为前景和背景)。w0和w1分别是一个像素可能属于前景或背景的概率,而 σ 表示两个类别的方差。如果一个图像的直方图有L个等级(一般L=256),那么在给定阈值 t的情况下,w0和w1分别定义为
大津展之证明最小化组内方差(intra-class variance)与最大化组间方差(inter-class variance)是等价的,于是有
又因为(其中 μ 表示均值或期望)
可以推出
这个证明仅仅涉及一些算术上的推导,我简单演示如下
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include
using namespace cv;
using namespace std;
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//----------------------------------------------------------------------------------------------
#define WINDOW_NAME "【Shi-Tomasi角点检测】" //为窗口标题定义的宏
//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_grayImage,g_ndstImage;
int main( )
{
//【0】改变console字体颜色
system("color 2F");
//【1】载入源图像并将其转换为灰度图
g_srcImage = imread("S (3).jpg");
imshow("【原始图】", g_srcImage);
cvtColor( g_srcImage, g_grayImage, CV_BGR2GRAY );
imshow("【灰度图】", g_grayImage);
//【方法1】全局固定阈值分割
//threshold(g_grayImage, g_ndstImage, 133, 1, 1); //更新效果图
//imshow("threshold", g_ndstImage*255);
//【方法2】局固自适应阈值分割
//int blockSize = 13;
//int constValue =9;
//Mat local;
//adaptiveThreshold(g_grayImage, local, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, blockSize, constValue);
//imshow("localThreshold", local);
//【方法3】大津法、Otus法阈值分割
//threshold(g_grayImage, g_ndstImage, 133, 255, THRESH_OTSU);
threshold(g_grayImage, g_ndstImage, 100, 255, THRESH_BINARY|THRESH_OTSU);
imshow("THRESH_OTSU", g_ndstImage);
waitKey();
return 0;
}
下面就介绍OpenCV中对图像进行二值化的关键函数——cvThreshold()。
函数功能:采用Canny方法对图像进行边缘检测
函数原型:
void cvThreshold(
const CvArr* src,
CvArr* dst,
double threshold,
double max_value,
int threshold_type
);
函数说明:
第一个参数表示输入图像,必须为单通道灰度图。
第二个参数表示输出的边缘图像,为单通道黑白图。
第三个参数表示阈值
第四个参数表示最大值。
第五个参数表示运算方法。
在OpenCV的imgproc\types_c.h中可以找到运算方法的定义。
/* Threshold types */
enum
{
CV_THRESH_BINARY =0, /* value = value > threshold ? max_value : 0 */
CV_THRESH_BINARY_INV =1, /* value = value > threshold ? 0 : max_value */
CV_THRESH_TRUNC =2, /* value = value > threshold ? threshold : value */
CV_THRESH_TOZERO =3, /* value = value > threshold ? value : 0 */
CV_THRESH_TOZERO_INV =4, /* value = value > threshold ? 0 : value */
CV_THRESH_MASK =7,
CV_THRESH_OTSU =8 /* use Otsu algorithm to choose the optimal threshold value; combine the flag with one of the above CV_THRESH_* values */
};
注释已经写的很清楚了,因此不再用中文来表达了。