谈不上真正的阳光算法,但是对于一些光干扰的场景和一些有噪点的图像还是可以进行处理。
在之前主流的摄像头主要是ov7725这个摄像头的优点就是硬件二值化,它的处理速度比较快,但是它输出只能是0和1,即就是黑和白,这对于阳光处理,反而成了缺点,我们只能人为的修改阈值,来适应部分场景,但是在光线较恶劣的场景下,我们不可能满足所有的光线要求,所以基于此,灰度摄像头由此诞生。灰度摄像头的灵活之处在于它返回的值不在仅仅是0和1,而是0-255之间,这样我们就可以根据图像,来人为进行一些光线处理,图像处理的操作。目前智能车主流的灰度摄像头有总钻风和神眼,一个是龙丘的一个是逐飞的。下面我们以逐飞的总钻风为例,给大家讲解一下摄像头的一些阳光处理。
上一篇文章里面我们说到了,如何获取图像和解压图像,但是我们主要针对的是ov7725,这里我们在给大家讲一下总钻风的处理。
首先就是正常的图像获取,但是今天我们用的是软件二值化的摄像头,就是灰度摄像头,所以我们首先就要对采集的数组进行处理。
void photo_get()
{
int num=0;
for(num=0;num<120;num++)
{
memcpy(dis_image[num],mt9v03x_csi_image[num],160);
}
}
这里我们首先进行了一个操作就是想摄像头的图像数组进行复制,在这里复制的目的是:比如摄像头在进行图像采集的过程中,我们就要对图像进行操作,这样就会对原数据有一定的影响,因此我们首先是将图像进行复制,然后在对复制过来的图像进行操作。这样我们还可以在后面用原数据进行对比。这是第一步核心操作,接下来就是图像二值化了。一般图像二值化有几种常见的:大津法、和均值二值化。均值二值化是龙丘的库里面就会有,当然他的库里面也会有大津法,不过这个都需要去优化,因为一般大津法耗时比较长,所以软件二值化对芯片的要求就比较高,比如我们目前用的总钻风,就用k60带不起来,就要用主频更高的rt1064这个主频500M的芯片来配套使用。
下面给大家分享已经优化的大津法:
#define GrayScale 256
uint16 Image_Width = col;
uint16 Image_Height = row;
int X; uint16 Y;
uint8* data = image;
int HistGram[GrayScale];
for (Y = 0; Y < GrayScale; Y++)
{
HistGram[Y] = 0; //初始化灰度直方图
}
for (Y = 0; Y <Image_Height; Y++) //Y
{
//Y=Image_Height;
for (X = 0; X < Image_Width; X++)
{
HistGram[(int)data[Y*Image_Width + X]]++; //统计每个灰度值的个数信息
}
}
uint32 Amount = 0;
uint32 PixelBack = 0;
uint32 PixelIntegralBack = 0;
uint32 PixelIntegral = 0;
int32 PixelIntegralFore = 0;
int32 PixelFore = 0;
double OmegaBack=0, OmegaFore=0, MicroBack=0, MicroFore=0, SigmaB=0, Sigma=0; // 类间方差;
int16 MinValue=0, MaxValue=0;
uint8 Threshold = 0;
for (MinValue = 0; MinValue < 256 && HistGram[MinValue] == 0; MinValue++) ; //获取最小灰度的值
for (MaxValue = 255; MaxValue > MinValue && HistGram[MinValue] == 0; MaxValue--) ; //获取最大灰度的值
if (MaxValue == MinValue)
{
return MaxValue; // 图像中只有一个颜色
}
if (MinValue + 1 == MaxValue)
{
return MinValue; // 图像中只有二个颜色
}
for (Y = MinValue; Y <= MaxValue; Y++)
{
Amount += HistGram[Y]; // 像素总数
}
PixelIntegral = 0;
for (Y = MinValue; Y <= MaxValue; Y++)
{
PixelIntegral += HistGram[Y] * Y;//灰度值总数
}
SigmaB = -1;
for (Y = MinValue; Y < MaxValue; Y++)
{
PixelBack = PixelBack + HistGram[Y]; //前景像素点数
PixelFore = Amount - PixelBack; //背景像素点数
OmegaBack = (double)PixelBack / Amount;//前景像素百分比
OmegaFore = (double)PixelFore / Amount;//背景像素百分比
PixelIntegralBack += HistGram[Y] * Y; //前景灰度值
PixelIntegralFore = PixelIntegral - PixelIntegralBack;//背景灰度值
MicroBack = (double)PixelIntegralBack / PixelBack;//前景灰度百分比
MicroFore = (double)PixelIntegralFore / PixelFore;//背景灰度百分比
Sigma = OmegaBack * OmegaFore * (MicroBack - MicroFore) * (MicroBack - MicroFore);//g
if (Sigma > SigmaB)//遍历最大的类间方差g
{
SigmaB = Sigma;
Threshold = Y;
}
}
return Threshold;
当然还有另外一种大津法,如果感兴趣的同学可以给我留言,我和大家分享第二种方法,但是此时效果已经差不多了。
敲黑板!!!不论是硬件二值化还是软件二值化,最终我们的有效数据都是二值化的图像,所以我们最终都是以二值化的图像为准!
void img_conversion(uint8 (*src)[COL],uint8 (*dst)[COL],uint8 val)
{
uint8 line=0,col=0;
for(line=0;line<ROW;line++)
{
for(col=0;col<COL;col++)
{
if(src[line][col]>val) dst[line][col]=250; //!!!!敲黑板 划重点 250 不是255
else dst[line][col]=0;
}
}
}
!!!在这里有一个地方很重要,我们将黑点都变成了250,而不是255。这里是为后面埋下伏笔。因为我们一般都是0和255,也就是黑和白,但是在这里我们先将黑点都标位疑似点,也就是在这里我不能确定这个点就是黑点,所以后面我们还是要继续判断,所以这也是一个重点。
这就是我们的第三点,就是我们刚才上述的疑似点,这里我们就将刚才的疑似点进行处理。
void find_borders()
{
int16 i,j,temp_centere; //临时中点
/**********先初始化边界数组********************/
for(i=0;i<ROW;i++) //64行
{
for(j=0;j<3;j++)
{
fb_image[i][j]=0;
}
}
/**********找119行的中心检测点********************/
temp_centere=COL/2; // 上一次中点位置
for(int i=0;i<=30;i++)
{
if(tv_image[119][temp_centere+i]==250 || tv_image[119][temp_centere+i]==255)
{
temp_centere=temp_centere+i;
break;
}
if(tv_image[119][temp_centere-i]==250 || tv_image[119][temp_centere-i]==255)
{
temp_centere=temp_centere-i;
break;
}
}
/**********最后一行向左扫描********************/
for(i=temp_centere;i>=0;i--)
{
if(tv_image[119][i] !=0 )
{
tv_image[119][i] =255;
}
else break;
}
if(i==-1) //for循环中1-2循环 到达0时 值已经为-1了
{
fb_image[119][0] = 0;
}
else
{
fb_image[119][0] = i+1; //存储数组 假设左边界是20
}
/**********最后一行向右扫描********************/
for(i=temp_centere; i<COL; i++)
{
if(tv_image[119][i] !=0 )
{
tv_image[119][i] =255;
}
else break;
}
if(i == 160)
{
fb_image[119][2] = 159;
}
else
{
fb_image[119][2] = i-1; //存储数组
}
/****************自下往上,扫描一次 筛选跳变点*****************/
for(i=119;i>=tv_min_row;i--) //根据119行的255遍历找本行的250或者255 及边界
{
for(j=0;j<160;j++)
{
if( tv_image[i+1][j] == 255 ) //根据上一行的赛道来确定本行的赛道
{
if( tv_image[i][j]== 250 || tv_image[i][j] == 255 )
{
tv_image[i][j] = 255;
}
if( j-1 >=0 &&( tv_image[i][j-1] == 250 || tv_image[i][j-1] == 255 ))
{
tv_image[i][j-1] =255;
}
if( j+1<=159 &&(tv_image[i][j+1] == 250 || tv_image[i][j+1] == 255))
{
tv_image[i][j+1]= 255;
}
}
}
/**********向左扫描,寻找第一个255点********************/
for(j=0;j<=159;j++)
{
if(tv_image[i][j] == 255)
{
break;
}
}
if(j==160) //本行无赛道,跳出
{
break;
}
for(;j>=0;j--) //根据上面找到的255,向回找255 或者 250 核心亮点 for循环还能这样玩
{
if(tv_image[i][j] == 250 || tv_image[i][j] == 255)
{
tv_image[i][j] =255;
}
else
{
fb_image[i][0] = j+1;
break;
}
}
if(j==-1) //本行无赛道,跳出
{
fb_image[i][0] = 0;
}
/**********向左扫描,寻找第一个255点********************/
for(j=159;j>=0; j--) //同上
{
if(tv_image[i][j] == 255)
{
break;
}
}
if(j==-1)
{
break;
}
for(;j<=159;j++) //同上
{
if(tv_image[i][j] == 250 || tv_image[i][j] == 255)
{
tv_image[i][j] = 255;
}
else
{
fb_image[i][2] = j-1;
break;
}
}
if(j==160)
{
fb_image[i][2] = 159;
}
}
/*******************************二次扫描是为了处理特殊情况****************************************************/
#if 1
if(i >= tv_min_row ) //代表图像未扫描完
{
temp_centere=COL/2; // 上一次中点位置
for(int j=0;j<=30;j++)
{
if(tv_image[i][temp_centere+j]==250 || tv_image[i][temp_centere+j]==255)
{
temp_centere=temp_centere+j;
break;
}
if(tv_image[i][temp_centere-j]==250 || tv_image[i][temp_centere-j]==255)
{
temp_centere=temp_centere-j;
break;
}
}
if( temp_centere != COL/2) //不相等说明找到了该行的赛道
{
//自下往上,扫描一次
for(i=temp_centere;i>=tv_min_row;i--) //根据159行的255遍历找本行的250或者255 及边界
{
for(j=0;j<159;j++)
{
if( tv_image[i+1][j] == 255 ) //根据上一行的赛道来确定本行的赛道
{
if( tv_image[i][j]== 250 || tv_image[i][j] == 255)
{
tv_image[i][j] = 255;
}
if( j-1 >=0 &&( tv_image[i][j-1] == 250 || tv_image[i][j-1] == 255 ))
{
tv_image[i][j-1] =255;
}
if( j+1<=159 &&(tv_image[i][j+1] == 250 || tv_image[i][j+1] == 255))
{
tv_image[i][j+1]= 255;
}
}
}
for(j=0;j<=159;j++) //向左扫描,寻找第一个255点
{
if(tv_image[i][j] == 255)
{
break;
}
}
if(j==160) //本行无赛道,跳出
{
break;
}
for(;j>=0;j--) //根据上面找到的255,向回找255 或者 250
{
if(tv_image[i][j] == 250 || tv_image[i][j] == 255)
{
tv_image[i][j] =255;
}
else
{
fb_image[i][0] = j+1;
break;
}
}
if(j==-1) //本行无赛道,跳出
{
fb_image[i][0] = 0;
}
for(j=159;j>=0; j--) //同上
{
if(tv_image[i][j] == 255)
{
break;
}
}
if(j==-1)
{
break;
}
for(;j<=159;j++) //同上
{
if(tv_image[i][j] == 250 || tv_image[i][j] == 255)
{
tv_image[i][j] = 255;
}
else
{
fb_image[i][2] = j-1;
break;
}
}
if(j==160)
{
fb_image[i][2] = 159;
}
}
}
}
#endif
}
下面就是我们的二次判定,主要是对刚才的疑似点再次进行判定,这样我们就可以将刚才的疑似点再次进行处理,这样就有一定的图像干扰处理。
void twoBJ()
{
int i,j,k,temp_centere;
//寻找63行的中点
temp_centere=COL/2; // 上一次中点位置
for(int i=0;i<=30;i++)
{
if(tv_image[119][temp_centere+i]==255)
{
temp_centere=temp_centere+i;
break;
}
if( tv_image[119][temp_centere-i]==255)
{
temp_centere=temp_centere-i;
break;
}
}
for(i=ROW-1;i>=0;i--)
{
for(j=temp_centere;j<COL;j++) //前一行中点位置作为本行的向右侧扫描的起点
{
if(tv_image[i][j]==0)
{
if(j==temp_centere) //第一个点就等于0,说明此行丢赛道,跳过
{
return ;
}
fb_image[i][2]=j-1; //右侧扫描到第一个黑点,判定为赛道的边界
break;
}
else //改写赛道,消除干扰
{
tv_image[i][j]=255;
}
}
if(j==COL)
{
fb_image[i][2]=COL-1;
}
for(k=temp_centere;k>=0;k--) //同上
{
if(tv_image[i][k]==0)
{
if(k==temp_centere) 第一个点就等于0,说明此行丢赛道,跳过
{
return ;
}
fb_image[i][0]=k+1;
break;
}
else
{
tv_image[i][k]=255;
}
}
if(k==-1)
{
fb_image[i][0]=0;
}
temp_centere=(j+k)/2; //保存本行的中线,为下一行做参考
fb_image[i][1]=(fb_image[i][0]+fb_image[i][2])/2;
if((k==(COL-1)/2&&j==(COL-1)/2)||(k==0&& j==0))
{
break;
}
if( k == 119 && j == 119)
{
break;
}
}
}
上述就是部分的图像干扰处理,可以将部分噪点进行处理,同时也能让图像的数据更加有效,在后期我们进行赛道分析的时候,也会有很大的帮助。如果有写的不好的地方,请大佬见谅。仅自己个人观点。