前面的简单提取程序只能让小车跑起来,并不能很完美的跑,这也跟提取出来的中线有关,因为那样提取出的中线,不连续,不具备很好的跟随性,这次,介绍一种跟随性较好的中线提取算法。
- 当我们图像失真时,我们可以通过数学的思想来算出他没有显示在图像上的点,也就是说假设我们第三行的右边界丢失,但是我们已经得到了第一行和第二行的坐标,那我们可以通过这两个点来确定一条直线,而第三行的点也会在这个直线附近(利用已知2点计算出补线坐标)代码如下:
int16 Calculate_Add(uint8 i, float Ka, float Kb) // 计算补线坐标
{
float res;
int16 Result;
res = i * Ka + Kb;
Result = range_protect2((int32)res, 1, CAMERA_W-1); //限幅
return Result;
}
这就是简单的y=kx+b的方程
- 有了斜率补线,我们还需要知道哪些需要补线,哪些不需要补线,所以我们还要设置一个补线标志位,所以我们在进行左右遍历时,同时也需要记录下哪些需要补线,哪些不需要补线,代码如下:
void Traversal_Mid_Line(uchar i, uint8 (*data)[CAMERA_W], uchar Mid, uchar Left_Min, uchar Right_Max, uchar *Left_Line, uchar *Right_Line, uchar *Left_Add_Line, uchar *Right_Add_Line)
{
uchar j;
Left_Add_Flag[i] = 1; // 初始化补线标志位
Right_Add_Flag[i] = 1;
Left_Min = range_protect2(Left_Min, 1, 79); // 限幅,防止出错
Right_Max = range_protect2(Right_Max, 1, 79);
Right_Line[i] = Right_Max;
Left_Line[i] = Left_Min; // 给定边界初始值
for (j = Mid; j >= Left_Min; j--) // 以前一行中点为起点向左查找边界
{
if (!data[i][j]) // 检测到黑点
{
Left_Add_Flag[i] = 0; //左边界不需要补线,清除标志位
Left_Line[i] = j+1; //记录当前j值为本行实际左边界
Left_Add_Line[i] = j+1; // 记录实际左边界为补线左边界
break;
}
}
for (j = Mid; j <= Right_Max; j++) // 以前一行中点为起点向右查找右边界
{
if (!data[i][j]) //检测到黑点
{
Right_Add_Flag[i] = 0; //右边界不需要补线,清除标志位
Right_Line[i] = j-1; //记录当前j值为本行右边界
Right_Add_Line[i] = j-1; // 记录实际右边界为补线左边界
break;
}
}
if (Left_Add_Flag[i]) // 左边界需要补线
{
if (!data[(i-2)][Left_Add_Line[i+2]] || !data[(i-4)][Left_Add_Line[i+2]]) // 可能是反光干扰
{
Left_Add_Flag[i] = 0; //左边界不需要补线,清除标志位
Left_Line[i] = Left_Add_Line[i+2]; //记录当前j值为本行实际左边界
Left_Add_Line[i] = Left_Add_Line[i+2]; // 记录实际左边界为补线左边界
}
else
{
if (i >= 55) // 前6行
{
Left_Add_Line[i] = Left_Line[59]; // 使用底行数据
}
else
{
Left_Add_Line[i] = Left_Add_Line[i+2]; // 使用前2行左边界作为本行左边界
}
}
}
if (Right_Add_Flag[i]) // 右边界需要补线
{
if ((!data[(i-2)][Right_Add_Line[i+2]])|| (!data[(i-4)][Right_Add_Line[i+2]])) // 可能是反光干扰
{
Right_Add_Flag[i] = 0; //左边界不需要补线,清除标志位
Right_Line[i] = Right_Add_Line[i+2]; //记录当前j值为本行实际左边界
Right_Add_Line[i] = Right_Add_Line[i+2]; // 记录实际左边界为补线左边界
}
else
{
if (i >= 55) // 前6行
{
Right_Add_Line[i] = Right_Line[59]; // 使用底行数据
}
else
{
Right_Add_Line[i] = Right_Add_Line[i+2]; // 使用前2行右边界作为本行右边界
}
}
}
Width_Real[i] = Right_Line[i] - Left_Line[i]; // 计算实际赛道宽度
Width_Add[i] = Right_Add_Line[i] - Left_Add_Line[i]; // 计算补线赛道宽度
}
- 后面呢,就是检测补线
if (Left_Add_Flag[i]) // 左侧需要补线
{
if (i >= 53) // 前三行补线不算
{
if (!Left_Add_Start)
{
Left_Add_Start = i; // 记录补线开始行
Left_Ka = 0;
Left_Kb = Left_Add_Line[i+2];
}
Left_Add_Line[i] = Calculate_Add(i, Left_Ka, Left_Kb); // 使用前一帧图像左边界斜率补线
}
else
{
if (!Left_Add_Start) // 之前没有补线
{
Left_Add_Start = i; // 记录左侧补线开始行
Curve_Fitting(&Left_Ka, &Left_Kb, &Left_Add_Start, Left_Add_Line, Left_Add_Flag, 1); // 使用两点法拟合直线
}
Left_Add_Line[i] = Calculate_Add(i, Left_Ka, Left_Kb); // 补线完成
}
}
else
{
if (Left_Add_Start) // 已经开始补线
{
if (!Left_Add_Stop && !Left_Add_Flag[i+2])
{
if (Left_Add_Line[i] >= Left_Add_Line[i+2])
{
Left_Add_Stop = i; // 记录左侧补线结束行
}
}
}
}
- 所以的线都补好后就可以开始求中线了
for (i = 59; i >= 9;)
{
i -= 2;
Mid_Line[i] = (Right_Add_Line[i] + Left_Add_Line[i]) / 2; // 计算赛道中点
Width_Add[i] = Right_Add_Line[i] - Left_Add_Line[i]; // 计算赛道宽度
}
由于一些原因代码无法公开,限于作者水平有限,这里只供新手参考思想,更往各位大神指点,讨论^ - ^/…