本篇文章分享全国大学生智能车竞赛中常用的一些摄像头图像处理算法。
一、大津法
//-------------------------------------------------------------------------------------------------------------------
// @brief 快速大津
// @return uint8
// @since v1.1
// Sample usage: OTSU_Threshold = otsuThreshold(mt9v03x_image_dvp[0]);//大津法阈值
//-------------------------------------------------------------------------------------------------------------------
uint8 otsuThreshold(uint8 *image) //注意计算阈值的一定要是原图像
{
#define GrayScale 256
int Pixel_Max=0;
int Pixel_Min=255;
uint16 width = MT9V03X_W;
uint16 height = MT9V03X_H;
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]; //灰度值总和
if(data[i * width + j]>Pixel_Max) Pixel_Max=data[i * width + j];
if(data[i * width + j]<Pixel_Min) Pixel_Min=data[i * width + j];
}
}
//计算每个像素值的点在整幅图像中的比例
for (i = Pixel_Min; i < Pixel_Max; 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 = Pixel_Min; j < Pixel_Max; 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 = (float)(w0 *w1* (u0 - u1)* (u0 - u1)) ;
if (deltaTmp > deltaMax)
{
deltaMax = deltaTmp;
threshold = j;
}
if (deltaTmp < deltaMax)
{
break;
}
}
if(threshold>90 && threshold<130)
last_threshold = threshold;
else
threshold = last_threshold;
return threshold;
}
二、图像压缩
//-------------------------------------------------------------------------------------------------------------------
// @brief 图像压缩
// @return void
// @since v2.0
// Sample usage: void compressimage();
//-------------------------------------------------------------------------------------------------------------------
#define IMAGE_H 50//图像高度
#define IMAGE_W 90//图像宽度
uint8 Image_use_zip[IMAGE_H][IMAGE_W];
void compressimage(void)
{
int i, j, row, line;
const float div_h = MT9V03X_H / IMAGE_H, div_w = MT9V03X_W / IMAGE_W;
for (i = 0; i < IMAGE_H; i++)
{
row = i * div_h + 0.5;
for (j = 0; j < IMAGE_W; j++)
{
line = j * div_w + 0.5;
Image_use_zip[i][j] = mt9v03x_image[row][line];
}
}
}
三、 Soble固定阈值
//-------------------------------------------------------------------------------------------------------------------
// @brief Soble固定阈值
// @return void
// @since v1.2
// Sample usage: sobel(Image_use_zip, Image_use, 60);
//-------------------------------------------------------------------------------------------------------------------
void sobel (uint8 imageIn[IMAGE_H][IMAGE_W], uint8 imageOut[IMAGE_H][IMAGE_W], uint8 Threshold)
{
/** 卷积核大小 */
short KERNEL_SIZE = 3;
short xStart = KERNEL_SIZE / 2;
short xEnd = IMAGE_W - KERNEL_SIZE / 2;
short yStart = KERNEL_SIZE / 2;
short yEnd = IMAGE_H - KERNEL_SIZE / 2;
short i, j;
short temp[2];
for (i = yStart; i < yEnd; i++)
{
for (j = xStart; j < xEnd; j++)
{
/* 计算不同方向梯度幅值 */
temp[0] = -(short) imageIn[i - 1][j - 1] + (short) imageIn[i - 1][j + 1] //{{-1, 0, 1},
- (short) imageIn[i][j - 1] + (short) imageIn[i][j + 1] // {-1, 0, 1},
- (short) imageIn[i + 1][j - 1] + (short) imageIn[i + 1][j + 1]; // {-1, 0, 1}};
temp[1] = -(short) imageIn[i - 1][j - 1] + (short) imageIn[i + 1][j - 1] //{{-1, -1, -1},
- (short) imageIn[i - 1][j] + (short) imageIn[i + 1][j] // { 0, 0, 0},
- (short) imageIn[i - 1][j + 1] + (short) imageIn[i + 1][j + 1]; // { 1, 1, 1}};
temp[0] = abs(temp[0]);
temp[1] = abs(temp[1]);
/* 找出梯度幅值最大值 */
if (temp[0] < temp[1])
temp[0] = temp[1];
if (temp[0] > Threshold) imageOut[i][j] = 0;
else imageOut[i][j] = 255;
}
}
}
四、给图像画黑框为八邻域做准备
//-------------------------------------------------------------------------------------------------------------------
// @brief 给图像画黑框为八邻域做准备
// @return void
// @since v1.0
// Sample usage: image_draw_rectan(Image_use);
//-------------------------------------------------------------------------------------------------------------------
void image_draw_rectan(uint8(*image)[IMAGE_W])
{
uint8 i = 0;
for (i = 0; i < IMAGE_H; i++)
{
image[i][0] = 0;
image[i][1] = 0;
image[i][IMAGE_W - 1] = 0;
image[i][IMAGE_W - 2] = 0;
}
for (i = 0; i < IMAGE_W; i++)
{
image[0][i] = 0;
image[1][i] = 0;
}
}
五、八邻域找边界
/*---------------------------------------------------------------
【函 数】search_neighborhood
【功 能】八邻域找边界
【参 数】无
【返 回 值】无
【注意事项】
----------------------------------------------------------------*/
struct LEFT_EDGE
{
int16 row; //行坐标
int16 col; //列坐标
uint8 flag; //存在边界的标志
};
struct RIGHT_EDGE
{
int16 row; //行坐标
int16 col; //列坐标
uint8 flag; //存在边界的标志
};
struct LEFT_EDGE L_edge[140]; //左边界结构体
struct RIGHT_EDGE R_edge[140]; //右边界结构体
uint8 L_edge_count=0, R_edge_count = 0; //左右边点的个数
uint8 dire_left,dire_right; //记录上一个点的相对位置
uint8 L_search_amount = 140, R_search_amount = 140; //左右边界搜点时最多允许的点
void search_neighborhood(void)
{
L_edge_count = 0;//左边点个数清0
R_edge_count = 0;//右边点个数清0
if(left_findflag)//如果左边界点存在并找到,则开始爬线
{
//变量声明
L_edge[0].row = L_start_y;
L_edge[0].col = L_start_x;
L_edge[0].flag = 1;
int16 curr_row = L_start_y;//初始化行坐标
int16 curr_col = L_start_x;//初始化列坐标
dire_left = 0; //初始化上个边界点的来向
//开始搜线,最多取150个点,不会往下搜,共7个方位
for(int i = 1;i < L_search_amount; i++) //最多搜索150个点
{
越界退出 行越界和列越界(向上向下向左向右)
if(curr_row+1 < Boundary_search_end || curr_row>IMAGE_H-1) break;
//搜线过程
if(dire_left != 2&&image_use[curr_row-1][curr_col-1]==BLACK&&image_use[curr_row-1][curr_col]==WHITE) //左上黑,2,右边白
{
curr_row = curr_row -1;
curr_col = curr_col -1;
L_edge_count = L_edge_count +1;
dire_left = 7;
L_edge[i].row = curr_row;
L_edge[i].col = curr_col;
L_edge[i].flag = 1;
}
else if(dire_left!=3&&image_use[curr_row-1][curr_col+1]==BLACK&&image_use[curr_row][curr_col+1]==WHITE) //右上黑,3,下边白
{
curr_row = curr_row -1;
curr_col = curr_col + 1;
L_edge_count = L_edge_count + 1;
dire_left = 6;
L_edge[i].row = curr_row;
L_edge[i].col = curr_col;
L_edge[i].flag = 1;
}
else if(image_use[curr_row-1][curr_col]==BLACK&&image_use[curr_row-1][curr_col+1]==WHITE) //正上黑,1,右白
{
curr_row = curr_row - 1;
L_edge_count = L_edge_count + 1;
dire_left = 0;
L_edge[i].row = curr_row;
L_edge[i].col = curr_col;
L_edge[i].flag = 1;
}
else if(dire_left!=5&&image_use[curr_row][curr_col-1]==BLACK&&image_use[curr_row-1][curr_col-1]==WHITE) //正左黑,5,上白
{
curr_col = curr_col - 1;
L_edge_count = L_edge_count +1;
dire_left = 4;
L_edge[i].row = curr_row;
L_edge[i].col = curr_col;
L_edge[i].flag = 1;
}
else if(dire_left!=4&&image_use[curr_row][curr_col+1]==BLACK&&image_use[curr_row+1][curr_col+1]==WHITE) //正右黑,4,下白
{
curr_col = curr_col + 1;
L_edge_count = L_edge_count +1;
dire_left = 5;
L_edge[i].row = curr_row;
L_edge[i].col = curr_col;
L_edge[i].flag = 1;
}
else if(dire_left!=6&&image_use[curr_row+1][curr_col-1]==BLACK&&image_use[curr_row][curr_col-1]==WHITE) //左下黑,6,上白
{
curr_row = curr_row + 1;
curr_col = curr_col -1;
L_edge_count = L_edge_count +1;
dire_left = 3;
L_edge[i].row = curr_row;
L_edge[i].col = curr_col;
L_edge[i].flag = 1;
}
else if(dire_left!=7&&image_use[curr_row+1][curr_col+1]==BLACK&&image_use[curr_row+1][curr_col]==WHITE) //右下黑,7,左白
{
curr_row = curr_row + 1;
curr_col = curr_col + 1;
L_edge_count = L_edge_count +1;
dire_left = 2;
L_edge[i].row = curr_row;
L_edge[i].col = curr_col;
L_edge[i].flag = 1;
}
else
break;
}
}
if(right_findflag)//如果右边界存在并搜到
{
R_edge[0].row = R_start_y;
R_edge[0].col = R_start_x;
R_edge[0].flag = 1;
int16 curr_row = R_start_y;
int16 curr_col = R_start_x;
dire_right = 0;
for(int i = 1;i<R_search_amount;i++)
{
越界退出 行越界和列越界(向上向下向左向右)
if(curr_row < Boundary_search_end || curr_row>IMAGE_H-1||curr_row+1<Boundary_search_end) break;
//爬线过程
if(curr_col<IMAGE_W&&dire_right!=3&&image_use[curr_row-1][curr_col+1]==BLACK&&image_use[curr_row-1][curr_col]==WHITE) //右上黑,3,左白
{
curr_row = curr_row - 1;
curr_col = curr_col + 1;
R_edge_count = R_edge_count + 1;
dire_right = 6;
R_edge[i].row = curr_row;
R_edge[i].col = curr_col;
R_edge[i].flag = 1;
}
else if(dire_right!=2&&image_use[curr_row-1][curr_col-1]==BLACK&&image_use[curr_row][curr_col-1]==WHITE) //左上黑,2,下白
{
curr_row = curr_row-1;
curr_col = curr_col-1;
R_edge_count = R_edge_count + 1;
dire_right = 7;
R_edge[i].row = curr_row;
R_edge[i].col = curr_col;
R_edge[i].flag = 1;
}
else if(image_use[curr_row-1][curr_col]==BLACK&&image_use[curr_row-1][curr_col-1]==WHITE) //正上黑,1,左白
{
curr_row = curr_row - 1;
R_edge_count = R_edge_count + 1;
dire_right = 0;
R_edge[i].row = curr_row;
R_edge[i].col = curr_col;
R_edge[i].flag = 1;
}
else if(dire_right!=4&&image_use[curr_row][curr_col+1]==BLACK&&image_use[curr_row-1][curr_col+1]==WHITE) //正右黑,4,上白
{
curr_col = curr_col + 1;
R_edge_count = R_edge_count + 1;
dire_right = 5;
R_edge[i].row = curr_row;
R_edge[i].col = curr_col;
R_edge[i].flag = 1;
}
else if(dire_right!=5&&image_use[curr_row][curr_col-1]==BLACK&&image_use[curr_row+1][curr_col-1]==WHITE) //正左黑,5,下白
{
curr_col = curr_col-1;
R_edge_count = R_edge_count + 1;
dire_right = 4;
R_edge[i].row = curr_row;
R_edge[i].col = curr_col;
R_edge[i].flag = 1;
}
else if(dire_right!=6&&image_use[curr_row+1][curr_col-1]==BLACK&&image_use[curr_row+1][curr_col]==WHITE) //左下黑,6,右白
{
curr_row = curr_row + 1;
curr_col = curr_col - 1;
R_edge_count = R_edge_count + 1;
dire_right = 3;
R_edge[i].row = curr_row;
R_edge[i].col = curr_col;
R_edge[i].flag = 1;
}
else if(dire_right!=7&&image_use[curr_row+1][curr_col+1]==BLACK&&image_use[curr_row][curr_col+1]==WHITE) //右下黑,7,上白
{
curr_row = curr_row + 1;
curr_col = curr_col + 1;
R_edge_count = R_edge_count + 1;
dire_right = 2;
R_edge[i].row = curr_row;
R_edge[i].col = curr_col;
R_edge[i].flag = 1;
}
else
break;
}
}
}