图像的黑白二值化处理
OK,前文说到哪儿了?对,说到摄像头已经采集好原始的图像数据了。那么采集到的数据我们要做什么用呢?分析数据,提取赛道元素。是直线,就一一定速度继续前行,是弯道,就给电机两边输出不同脉冲宽度的PWM波,实现差速转向。
对我们采集到的图像进行黑白处理。灰度图像的灰度级0~255,共256个灰度级。0为最黑,255为最白。再看看赛道,它有三个sai,蓝色,黑色,以及白色。蓝色的是赛道之外的区域,黑色的是赛道边缘贴着的黑色胶带,白色则是赛道区域。对于我们采集到的188*120个像素点来说,赛道区域的灰度级绝对是最高的。而我们二值化的目的呢,让采集到的图像中,赛道区域全白,非赛道区域全黑。当然,这只是其中一种处理方式,至少我还知道另一种:边沿提取——赛道边沿全白,其他地方全黑。
对于二值化有几种简单的实现方式,最简单的一种是什么呢?是对所有像素点的灰度值做个累加,遍历二维数组中的每一个像素点,将像素点的灰度值求和再取平均数。然后,接着遍历一遍数组,灰度值大于这个平均值的就是白,小于这个平均值的就是黑。这里的这个平均值呢,我们称之为阈值。(yu第四声)
不过这种手法实在是过于粗糙了,虽然没试过,但基本上想必很难拿出手。不过无所谓了,作为一名高级的CV工程师,你应该懂得搬迁和借鉴。
下面给出一段大津法的代码:
/************************************************************************
函数名:获取阈值
返回值:阈值
功能:大津法确定阈值
************************************************************************/
uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row)
{
#define GrayScaleMAX 170//110
#define GrayScaleMIN 100 //50
uint16 width = col;
uint16 height = row;
int pixelCount[GrayScaleMAX];
float pixelPro[GrayScaleMAX];
int i, j, pixelSum = width * height/8;
uint8 threshold = 0;
uint8* data = image; //指向像素数据的指针
for (i =GrayScaleMIN; i < GrayScaleMAX; i++)
{
pixelCount[i] = 0;
pixelPro[i] = 0;
}
uint32 gray_sum=0;
//统计灰度级中每个像素在整幅图像中的个数
for (i = 0; i < height; i+=2)
{
for (j = 0; j < width; j+=4)
{
if(data[i * width + j]>=GrayScaleMAX) data[i * width + j]=GrayScaleMAX;
else if(data[i * width + j]<=GrayScaleMIN) data[i * width + j]=GrayScaleMIN;
pixelCount[(int)data[i * width + j]]++; //将当前的点的像素值作为计数数组的下标
gray_sum+=(int)data[i * width + j]; //灰度值总和
}
}
//计算每个像素值的点在整幅图像中的比例
for (i = GrayScaleMIN; i < GrayScaleMAX; 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 = GrayScaleMIN; j < GrayScaleMAX; 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;
}
这个算法如果记得没错的话,好像是一个日本人提出来的,不过无所*谓,该用用,有更好的并且是你使得出来的,那你就大可以换掉它。另外,这种灰度图像求阈值的算法,无论是在matlab还是opencv里都有很多现成的算法。opencv那边我没找到底层的代码,而matlab这边的两三个算法我试着改写成c语言代码。改是改了,编译一下也是0error,0warnning,但是跑出来放在显示屏上,它就是依托答辩。现在想来大抵是卷积计算那里出了点问题,因为matlab可以直接调用卷积这边的函数,而我C语言代码中的卷积运算是自己凑的。想想当时复变函数学得也是依托答辩,那卷积运算的代码多半食油点大饼,导致最后算出的那个阈值,大于图像每一个像素点的灰度值。所以,结果就是全黑图像。
图像二值化
宏定义
宏定义黑和白,这样做没有别的目的,单纯就是看起来,无论是自己阅读还是别人阅读都舒服点。不然整个代码中全是255和0这样的数字,看起来多不爽啊。当然,爽不爽还是写代码的人说的算,但在我写着的时候,我是觉得不爽的,因为这个255和0出现的频率太高了,什么时候代指颜色,什么时候代指其他的,并不确定。当然,另一方面代指白sai的不一定要255,只要是1~255之间的数字就行。
#define black 0 //定义黑sai
#define white 255 //宏定义白sai,单纯就是为了看起来舒服,其实不顶啥用
既然黑白分清楚了,阈值有了,那就是将图像二值化,这部分可以自己单独封装成一个函数,到时候再放进主函数中使用。
还得定义个存储二值化图像的数组
uint8 image[120][188];
示例:
void 二值化函数名(void) //毕竟不需要返回值嘛
{
//先放大津法得到阈值
uint16 Threshold=otsuThreshold(mt9v03x_image[0],MT9V03X_W,MT9V03X_H);
//遍历二维数组图像
uint16 i,j;
for(i=0;i<120;i++)
{
for(j=0;j<188;j++)
{
if(原图像数组(i,j)这个点的灰度值大于阈值)
定义存储二值化图像的数组这个位置的点就是白点
else
定义存储二值化图像的数组这个位置的点就是黑点
}
}
}
后续就是分析处理过的图像,提取我们需要的信息了。