1. 前言
今天来讲讲如何手动实现Canny边缘检测。由于要实现这个算法的需要的先验知识比较多,所以在学习这个算法的实现之前我们先来学习一下用于图像二值化的OSTU大津法。
2. OTSU算法原理
Ostu方法又名最大类间差方法,通过统计整个图像的直方图特性来实现全局阈值的自动选取。
它将图像的像素按灰度级分成个部分,使得个部分之间的灰度值差异最小,通过方差的计算来寻找一个合适的灰度级别来划分。
OTSU算法的计算很简单,不受到图像亮度和对比度的影响,因此使得类间方差最大的分割意味着错分概率最小。
设为灰度级阈值,从(一般为)个灰度级遍历,使得为某个值的时候,前景和背景的方差最大,这个值便是我们要求的灰度级阈值。
设表示分开后前景像素点占图像像素比例,表示分开后前景像素点的平均灰度,表示分开后背景像素点数占图像比例,表示分开后背景像素点的平均灰度。
图像的总灰度被定义为:
图像的方差计算公式为:
并且有:
所以可以把公式(2)展开化简为:
公式推导完毕,我们来看一下OTSU算法的详细步骤。
3. OTSU算法流程
- 先计算图像的直方图,即将图像所有的像素点按照共个bin,统计落在每个bin的像素点数量。
- 归一化直方图,也即将每个bin中像素点数量除以总的像素点。
- 表示分类的阈值,也即一个灰度级,从开始迭代。
- 通过归一化的直方图,统计,灰度级的像素(假设像素值在此范围的像素叫做前景像素) 所占整幅图像的比例,并统计前景像素的平均灰度。然后统计,灰度级的像素(假设像素值在此范围的像素叫做背景像素) 所占整幅图像的比例,并统计背景像素的平均灰度。
- 计算前景像素和背景像素的方差(已经推导了)。
- 不断,转到第个步骤。
- 将最大相应的值作为图像的全局阈值。
4. OTSU算法代码实现
int OTSU(Mat src){
int row = src.rows;
int col = src.cols;
int PixelCount[256]={0};
float PixelPro[256]={0};
int total_Pixel = row * col;
float threshold = 0;
//统计灰度级中每个像素在整副图中的个数
for(int i = 0; i < row; i++){
for(int j = 0; j < col; j++){
PixelCount[src.at(i, j)]++;
}
}
//计算每个像素在整副图中的个数
for(int i = 0; i < 256; i++){
PixelPro[i] = (float)(PixelCount[i]) / (float)(total_Pixel);
}
//遍历灰度级[0,255],计算出方差最大的灰度值,为最佳阈值
float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
for(int i = 0; i < 256; i++){
w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
for(int j = 0; j < 256; j++){
if(j <= i){//以i为阈值分类,第一类总的概率
w0 += PixelPro[j];
u0tmp += j * PixelPro[j];
}else{//前景部分
w1 += PixelPro[j];
u1tmp += j * PixelPro[j];
}
}
u0 = u0tmp / w0; //第一类的平均灰度
u1 = u1tmp / w1; //第二类的平均灰度
u = u0 + u1; //整副图像的平均灰度
//计算类间方差
deltaTmp = w0 * (u0 - u) * (u0 - u) + w1 * (u1 - u) * (u1 - u);
//找出最大类间方差以及对应阈值
if(deltaTmp > deltaMax){
deltaMax = deltaTmp;
threshold = i;
}
}
return threshold;
}
5. 边缘检测的一般标准
边缘检测有下面几个标准:(1) 以低的错误率检测边缘,也即意味着需要尽可能准确的捕获图像中尽可能多的边缘。(2) 检测到的边缘应精确定位在真实边缘的中心。(3) 图像中给定的边缘应只被标记一次,并且在可能的情况下,图像的噪声不应产生假的边缘。
<