系列文章

智能车复工日记【1】——菜单索引回顾
智能车复工日记【2】——普通PID、变结构PID、微分先行PID、模糊PID、专家PID
智能车复工日记【3】:图像处理——基本扫线和基本特征提取和十字补线
智能车复工日记【4】:关于图像的上下位机的调整问题总结

起跑线识别

起跑线的图:
起点
思路:

1、设置一个栈,用来存储黑色元素。
2、从此行的第0列开始往左扫,如果遇到黑色元素,入栈,
3、如果遇到白色元素,出栈,出栈的同时统计栈中元素个数,如果栈中元素为4—8个(也就是黑胶的宽度),black_blocks++,否则不加。然后将栈中元素清掉
4、如果此行的black_blocks在8个左右,times++
5、遍历18——23行,如果,times在4个左右,则确定为起跑线。

这里我们只需要使用栈的游标,不需要建立完整的栈。

        #region[起跑线]
        //> 1、设置一个栈,用来存储黑色元素。
        //> 、从此行的第0行开始往左扫
        //> 2、如果遇到黑色元素,入栈,
        //> 3、如果遇到白色元素,出栈,出栈的同时统计栈中元素个数,如果栈中元素为4—8个(也就是黑胶的宽度),black_blocks++,否则不加。然后将栈中元素清掉
        //> 4、如果此行的black_blocks在8个左右,times++
        //> 5、遍历18——23行,如果,times在4个左右,则确定为起跑线。
        byte flag_starting_line = 0;
        void check_starting_line()
        {
            //int[] black_nums_stack = new int[20];
            byte times = 0;
            for (byte y = 18; y <= 23; y++)
            {
                byte black_blocks = 0;
                byte cursor = 0;    //指向栈顶的游标
                for (byte x = 0; x <= 185; x++)
                {
                    if (Pixels[y,x] == 0)
                    {
                        if (cursor >= 20)
                        {
                            //当黑色元素超过栈长度的操作   break;    
                        }
                        else 
                        {
                            cursor++;
                        }
                    }
                    else 
                    {
                        if (cursor >= 4 && cursor <= 8)
                        {
                            black_blocks++;
                            cursor = 0;
                        }
                        else 
                        {
                            cursor = 0;
                        }
                    }
                }
                if (black_blocks >= 6 && black_blocks <= 9) times++;
            }
            if (times >= 3 && times <= 5)
            {
                flag_starting_line = 1;
                SetText("进入起跑线状态");
            }
            else 
            {
                flag_starting_line = 0;
            }
        }
        #endregion

效果:
效果
进入起跑线状态之后可以进行下一步的特殊操作,如入车库等操作。

这里的行数确定了我们距离起跑线多少能识别出起跑线。当距离较远时,可以发现由于反光,起跑线并不明显。

车库补线

识别车库方向

如何识别车库方位:
我们不是从第0列开始扫的吗?
设此时已判断为起跑线,我们所扫的那几行的第0列都是黑色,那就是车库在左边。
反之,如果都是白色,那就是车库在右边

车库补线

在这里插入图片描述
如果是检测到为右边车库(已实现),寻找两个端点,然后连接两个端点。
1、 右上拐点
2、 左下拐点

左下拐点:

1、 如果pixels[0,185]为白,往上找,直到pixels[y1,185]为黑(防止姿态的原因导致车偏向道路左边)
2、 从上面确定的第y1行开始往右边扫,扫到的第一个不为黑的点视为左下拐点。

右上拐点:

1、 从第0行往上找,直到pixels[y2,0]为黑
2、 从上面确定的第y2行开始往左边扫,记录下扫到的第一个为白的点的列坐标
3、 遍历[y2, y2+5],选出6行中列坐标最大(最靠左)的列坐标以及那时的行坐标,作为左上拐点

#region[右车库]
//前一个是行坐标,后一个是列坐标
int[] right_garage_left_turn_down = new int[2];
int[] right_garage_right_turn_up = new int[2];
void check_guaidian()
{
    //首先找左下拐点
    byte y1 = 0;
    byte x1 = 0;
    byte x = 0;
    byte y = 0;
    // 1、	如果pixels[0, 185]为白,往上找,直到pixels[y1, 185]为黑(防止姿态的原因导致车偏向道路左边)
    if (Pixels[0, 185] != 0)   //不为黑,即为白
    {
        
        for (y = 1; y < 70; y++)
        {
            if (Pixels[y, 185] == 0)
            {
                y1 = y;
                break;
            }
        }
    }
    else
    {
        y1 = 0;
    }
    //2、	从上面确定的第y1行开始往右边扫,扫到的第一个不为黑的点视为左下拐点。
   
    for (x = 185;x>1;x--)
    {
        if (Pixels[y1, x] != 0)
        {
            x1 = x;
            break;
        }
    }
    //赋值操作
    right_garage_left_turn_down[0] = y1;
    right_garage_left_turn_down[1] = x1;
    //然后找右上拐点
    //1、	从第0行往上找,直到pixels[y1,0]为黑
    for (y = 0; y < 70; y++)
    {
        if (Pixels[y, 0] == 0)
        {
            y1 = y;
            break;
        }
    }
    //2、	从上面确定的第y1行开始往左边扫,记录下扫到的第一个为白的点的列坐标
    for (x = 0; x < 185; x++)
    {
        if (Pixels[y1, x] != 0)
        {
            x1 = x;
            break;
        }
    }
    //3、	遍历[y2, y2 + 2],选出6行中列坐标最大(最靠左)的列坐标以及那时的行坐标,作为左上拐点
    for (y = (byte)(y1 + 1); y <= y1 + 2; y++)
    {
        byte X=0;
        for (x = 0; x < 185; x++)
        {
            if (Pixels[y1, x] != 0)
            {
                X = x;
                break;
            }
        }
        if (X >= x1)
        {
            x1 = X;
            y1 = y;
        }
    }
    //赋值操作
    right_garage_right_turn_up[0] = y1;
    right_garage_right_turn_up[1] = x1;

    //展示
    SetText("车库右上角"+ right_garage_right_turn_up[0]+"  "+ right_garage_right_turn_up[1]);
    SetText("车库左下角" + right_garage_left_turn_down[0] + "  " + right_garage_left_turn_down[1]);
    //补线程序
    byte j = 0;
    int delta = right_garage_right_turn_up[0] - right_garage_left_turn_down[0];
    if (delta == 0) delta = 1;
    float k = (right_garage_right_turn_up[1] - right_garage_left_turn_down[1]) * 1.0f / delta;
    float b = right_garage_left_turn_down[1];
    for (j = (byte)right_garage_left_turn_down[0]; j <= (byte)right_garage_right_turn_up[0]; j++)
    {
        int jicun = ((int)(k * j + b));
        if (jicun >= 185) jicun = 185;
        else if (jicun <= 0) jicun = 0;
        lefetline[j] = (byte)jicun;
    }
}
#endregion

5.11修改

这几天的一些更加完善的代码,不过还是有许多问题:

#region[起跑线]
//> 1、设置一个栈,用来存储黑色元素。
//> 、从此行的第0行开始往左扫
//> 2、如果遇到黑色元素,入栈,
//> 3、如果遇到白色元素,出栈,出栈的同时统计栈中元素个数,如果栈中元素为4—8个(也就是黑胶的宽度),black_blocks++,否则不加。然后将栈中元素清掉
//> 4、如果此行的black_blocks在8个左右,times++
//> 5、遍历18——23行,如果,times在4个左右,则确定为起跑线。
byte flag_starting_line = 0;
byte garage_direction = 0;      //1为左边,2位右边
void check_starting_line(byte start_point, byte end_point)
{
    //int[] black_nums_stack = new int[20];
    byte times = 0;
    for (byte y = start_point; y <= end_point; y++)
    {
        byte black_blocks = 0;
        byte cursor = 0;    //指向栈顶的游标
        for (byte x = 0; x <= 185; x++)
        {
            if (Pixels[y,x] == 0)
            {
                if (cursor >= 20)
                {
                    //当黑色元素超过栈长度的操作   break;    
                }
                else 
                {
                    cursor++;
                }
            }
            else 
            {
                if (cursor >= 4 && cursor <= 8)
                {
                    black_blocks++;
                    cursor = 0;
                }
                else 
                {
                    cursor = 0;
                }
            }
        }
        if (black_blocks >= 6 && black_blocks <= 9) times++;
    }
    if (times >= 1)
    {
        flag_starting_line = 1;
        SetText("进入起跑线状态");
    }
    else 
    {
        flag_starting_line = 0;
    }
}
void check_garage_direction(byte start_point, byte end_point)
{
    byte times = 0;
    if (flag_starting_line == 1)
    {
        for (int y = start_point; y <= end_point; y++)
        {
            if (Pixels[y, 0] == 0)
            {
                times++;
            }
        }
        if (times >= 3 && times <= 7)
        {
            garage_direction = 1;   //车库在左边
            //SetText("车库在左边");
        }
        else if (times <= 2)
        {
            garage_direction = 2;   //车库在右边
            //SetText("车库在右边");
        }
    }
    SetText("times"+ times);
}
#endregion
#region[右车库]
//前一个是行坐标,后一个是列坐标
int[] right_garage_left_turn_down = new int[2];
int[] right_garage_right_turn_up = new int[2];
void check_guaidian()
{
    //首先找左下拐点
    byte y1 = 0;
    byte x1 = 0;
    byte x = 0;
    byte y = 0;
    // 1、	如果pixels[0, 185]为白,往上找(不超过5行),直到pixels[y1, 185]为黑(防止姿态的原因导致车偏向道路左边)
    if (Pixels[0, 185] != 0)   //不为黑,即为白
    {
        
        for (y = 1; y < 5; y++)
        {
            if (Pixels[y, 185] == 0)
            {
                y1 = y;
                break;
            }
        }
    }
    else
    {
        y1 = 0;
    }
    //2、	从上面确定的第y1行开始往右边扫,扫到的第一个不为黑的点视为左下拐点。
   
    for (x = 185;x>1;x--)
    {
        if (Pixels[y1, x] != 0)
        {
            x1 = x;
            break;
        }
    }
    //赋值操作
    right_garage_left_turn_down[0] = y1;
    right_garage_left_turn_down[1] = x1;
    //然后找右上拐点
   
    if (Pixels[0, 0] == 0 && Pixels[1, 0] == 0 && Pixels[2, 0] == 0 && Pixels[3, 0] == 0 && Pixels[4, 0] == 0)
    {
        for (y = 5; y < 70; y++)
        {
            if (Pixels[y, 0] != 0)//为白
            {
                y1 = y;
                break;
            }

        }
        for (y = y1; y < 70; y++)
        {
            if (Pixels[y, 0] == 0)
            {
                y1 = y;
                break;
            }

        }
    }
    else
    {
        for (y = 10; y < 70; y++)
        {
            if (Pixels[y, 0] == 0)
            {
                y1 = y;
                break;
            }
        }
    }
    //2、	从上面确定的第y1行开始往左边扫,记录下扫到的第一个为白的点的列坐标
    for (x = 0; x < 185; x++)
    {
        if (Pixels[y1, x] != 0)
        {
            x1 = x;
            break;
        }
    }
    byte y_zhengque = 0;
    //3、	遍历[y2, y2 + 2],选出6行中列坐标最大(最靠左)的列坐标以及那时的行坐标,作为左上拐点
    for (y = (byte)(y1 + 1); y <= y1 + 2; y++)
    {
        byte X=0;
        for (x = 0; x < 185; x++)
        {
            if (Pixels[y1, x] != 0)
            {
                X = x;
                break;
            }
        }
        if (X >= x1)
        {
            x1 = X;
            y_zhengque = y;
        }
    }
    y1 = y_zhengque;
    //赋值操作
    right_garage_right_turn_up[0] = y1;
    right_garage_right_turn_up[1] = x1;

    //展示
    SetText("车库右上角"+ right_garage_right_turn_up[0]+"  "+ right_garage_right_turn_up[1]);
    SetText("车库左下角" + right_garage_left_turn_down[0] + "  " + right_garage_left_turn_down[1]);
    //补线程序
    byte j = 0;
    int delta = right_garage_right_turn_up[0] - right_garage_left_turn_down[0];
    if (delta == 0) delta = 1;
    float k = (right_garage_right_turn_up[1] - right_garage_left_turn_down[1]) * 1.0f / delta;
    float b = right_garage_left_turn_down[1];
    for (j = (byte)right_garage_left_turn_down[0]; j <= (byte)right_garage_right_turn_up[0]; j++)
    {
        int jicun = ((int)(k * j + b));
        if (jicun >= 185) jicun = 185;
        else if (jicun <= 0) jicun = 0;
        lefetline[j] = (byte)jicun;
    }
}
#endregion
#region[左车库]
//前一个是行坐标,后一个是列坐标
int[] left_garage_left_turn_up = new int[2];
int[] left_garage_right_turn_down = new int[2];
void check_guaidian1()
{
    //首先找右下拐点
    byte y1 = 0;
    byte x1 = 0;
    byte x = 0;
    byte y = 0;
    // 1、	如果pixels[0, 0]为白,往上找(不可超过5),直到pixels[y1, 0]为黑(防止姿态的原因导致车偏向道路右边)
    if (Pixels[0, 0] != 0)   //不为黑,即为白
    {

        for (y = 1; y < 5; y++)
        {
            if (Pixels[y, 0] == 0)
            {
                y1 = y;
                break;
            }
        }
    }
    else
    {
        y1 = 0;
    }
    //2、	从上面确定的第y1行开始往左边扫,扫到的第一个不为黑的点视为右下拐点。

    for (x = 0; x <185; x++)
    {
        if (Pixels[y1, x] != 0)
        {
            x1 = x;
            break;
        }
    }
    //赋值操作
    left_garage_right_turn_down[0] = y1;
    left_garage_right_turn_down[1] = x1;
    //然后找左上拐点
    //如果前5行为黑,则第二次为黑的行数

    //否则从第10行往上找
    //1、	从第10行往上找,直到pixels[y1,185]为黑
    if (Pixels[0, 185] == 0 && Pixels[1, 185] == 0 && Pixels[2, 185] == 0 && Pixels[3, 185] == 0 && Pixels[4, 185] == 0)
    {
        for (y = 5; y < 70; y++)
        {
            if (Pixels[y, 185] != 0)//为白
            {
                y1 = y;
                break;
            }
            
        }
        for (y = y1; y < 70; y++)
        {
            if (Pixels[y, 185] == 0)
            {
                y1 = y;
                break;
            }
            
        }
    }
    else 
    {
        for (y = 10; y < 70; y++)
        {
            if (Pixels[y, 185] == 0)
            {
                y1 = y;
                break;
            }
        }
    }
    SetText("左上行:"+y1);
    //2、	从上面确定的第y1行开始往右边扫,记录下扫到的第一个为白的点的列坐标
    for (x = 185; x > 1; x--)
    {
        if (Pixels[y1, x] != 0)
        {
            x1 = x;
            break;
        }
    }
    SetText("y1是" + y1);
    //3、	遍历[y2, y2 + 2],选出3行中列坐标最大(最靠左)的列坐标以及那时的行坐标,作为左上拐点
    byte y_zhengque = 0;
    for (y = (byte)(y1 + 1); y <= (byte)(y1 + 3); y++)
    {
        byte X = 0;
        for (x = 185; x > 1; x--)
        {
            if (Pixels[y1, x] != 0)
            {
                X = x;
                break;
            }
        }
        if (X <= x1)
        {
            x1 = X;
            y_zhengque = y;
        }
    }
    y1 = y_zhengque;
    SetText("y1是"+y1);
    //赋值操作
    left_garage_left_turn_up[0] = y1;
    left_garage_left_turn_up[1] = x1;

    //展示
    SetText("车库左上角" + left_garage_left_turn_up[0] + "  " + left_garage_left_turn_up[1]);
    SetText("车库右下角" + left_garage_right_turn_down[0] + "  " + left_garage_right_turn_down[1]);
    //补线程序
    byte j = 0;
    int delta = left_garage_left_turn_up[0] - left_garage_right_turn_down[0];
    if (delta == 0) delta = 1;
    float k = (left_garage_left_turn_up[1] - left_garage_right_turn_down[1]) * 1.0f / delta;
    float b = left_garage_right_turn_down[1];
    for (j = (byte)left_garage_right_turn_down[0]; j <= (byte)left_garage_left_turn_up[0]; j++)
    {
        int jicun = ((int)(k * j + b));
        if (jicun >= 185) jicun = 185;
        else if (jicun <= 0) jicun = 0;
        rightline[j] = (byte)jicun;
    }
}
#endregion