此优化算法是本人前两年参加智能车比赛,在总钻风摄像头例程基础上优化的(分辨率为188*70,工程文件:含优化算法的小车程序)。移植方便,亲测只需2ms(逐飞给的例程是很常规的,网上都能找到,需要40ms不止吧,根本无法直接使用,不知道现在优化了没,手动滑稽)。
与鹰眼摄像头的硬件二值化不同,总钻风需要软件二值化。而大津法便是最为常见的一种自适应算法,效果也不错。网上资料蛮多,简单来说就是遍历0~255的灰度值,用该灰度值将一副图像分割为前景和背景(黑和白),用类间方差所定义的算式得到类间方差(deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);详见下面程序),当其最大时,即为最佳阈值(即得到的黑白图像区分最佳)
下面先来看看常规大津法为何如此缓慢
uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row)
{
#define GrayScale 256
uint16 width = col;
uint16 height = row;
int pixelCount[GrayScale];
float pixelPro[GrayScale];
int i, j, pixelSum = width * height;
uint8 threshold = 0;
uint8* data = image; //指向像素数据的指针
for (i = 0; i < GrayScale; i++)
{
pixelCount[i] = 0;
pixelPro[i] = 0;
}
//统计灰度级中每个像素在整幅图像中的个数
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
pixelCount[(int)data[i * width + j]]++; //将像素值作为计数数组的下标
}
}
//计算每个像素值的点在整幅图像中的比例
float maxPro = 0.0;
for (i = 0; i < GrayScale; i++)
{
pixelPro[i] = (float)pixelCount[i] / pixelSum;
if (pixelPro[i] > maxPro)
{
maxPro = pixelPro[i];
}
}
//遍历灰度级[0,255]
float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
for (i = 0; i < GrayScale; i++) // i作为阈值
{
w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
for (j = 0; j < GrayScale; j++)
{
if (j <= i) //背景部分
{
w0 += pixelPro[j]; //背景部分每个灰度值的像素点所占比例之和 即背景部分的比例
u0tmp += j * pixelPro[j]; //背景部分 每个灰度值*比例
}
else //前景部分
{
w1 += pixelPro[j];
u1tmp += j * pixelPro[j];
}
}
u0 = u0tmp / w0; //背景平均灰度
u1 = u1tmp / w1; //前景平均灰度
u = u0tmp + u1tmp; //全局平均灰度
deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);
if (deltaTmp > deltaMax)
{
deltaMax = deltaTmp;
threshold = i;
}
}
return threshold;
}
其中的两层for循环便为最为耗时的一部分:外循环–假设256个灰度值中的一个是最佳阈值;内循环–在对每个假设的最佳阈值进行0-255的灰度遍历算前景背景像素点所占比例,其繁琐可想而知
那么,应该从何处对大津法进行优化呢
1.去内外层的256次循环—可通过先算出每幅图像的总灰度,则接下来只用算前景,不用在算背景的多次重复遍历(背景用总的减去前景即可)
此步骤为最主要的,执行一次程序所需时间将大幅度减少!!
2.大家知道相邻两个像素点的灰度值其实是相近甚至相等的,尤其是分辨率高的时候,所以我们可以相间取点,但不可相间太远以免影响效果
3.一副图像的最佳阈值处的类间方差为最大值,那么如果以0-255的灰度值为横坐标,相应的类间方差为纵坐标,那么最佳阈值附近的灰度值所对应的类间方差又是如何变化的呢?凭着直觉,我猜测是二次的,后来也通过实验证明了这点。所以,当我们找到了最大类间方差,即类间方差有减小的趋势,便不用在继续遍历下去(对应程序末尾)
优化大津法如下,如果有不当之处望斧正,有需要完整小车程序的可在评论区留邮箱
uint8 my_adapt_threshold(uint8 *image, uint16 col, uint16 row) //注意计算阈值的一定要是原图像
{
#define GrayScale 256
uint16 width = col;
uint16 height = row;
int pixelCount[GrayScale];
float pixelPro[GrayScale];
int i, j, pixelSum = width * height/4;
uint8 threshold = 0;
uint8* data = image; //指向像素数据的指针
for (i = 0; i < GrayScale; i++)
{
pixelCount[i] = 0;
pixelPro[i] = 0;
}
uint32 gray_sum=0;
//统计灰度级中每个像素在整幅图像中的个数
for (i = 0; i < height; i+=2)
{
for (j = 0; j < width; j+=2)
{
pixelCount[(int)data[i * width + j]]++; //将当前的点的像素值作为计数数组的下标
gray_sum+=(int)data[i * width + j]; //灰度值总和
}
}
//计算每个像素值的点在整幅图像中的比例
for (i = 0; i < GrayScale; i++)
{
pixelPro[i] = (float)pixelCount[i] / pixelSum;
}
//遍历灰度级[0,255]
float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
for (j = 0; j < GrayScale; j++)
{
w0 += pixelPro[j]; //背景部分每个灰度值的像素点所占比例之和 即背景部分的比例
u0tmp += j * pixelPro[j]; //背景部分 每个灰度值的点的比例 *灰度值
w1=1-w0;
u1tmp=gray_sum/pixelSum-u0tmp;
u0 = u0tmp / w0; //背景平均灰度
u1 = u1tmp / w1; //前景平均灰度
u = u0tmp + u1tmp; //全局平均灰度
deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);
if (deltaTmp > deltaMax)
{
deltaMax = deltaTmp;
threshold = j;
}
if (deltaTmp < deltaMax)
{
break;
}
}
return threshold;
}