东方红B类农业机器人一直都存在一个识别的作业环节,就这项竞赛而言最为简便的方式固然是识别颜色,只因其模型往往是不同作业要求模型颜色不同且差别较大,同一种作业要求的模型颜色一致,因此利用颜色识别是非常好用的。

整个过程为:将RGB转化到HSV色域中获取更大色彩空间,根据不同颜色的阈值二值化获得二值图像,紧接着通过比对识别出不同色块的大小选择面积最大的为识别出的颜色,然后绘制最小外接矩形,计算得到中心坐标,一般情况下摄像头都是固定在机器上的,因此可以利用计算得到的识别出的色块在图像中的坐标进行定位。这样就完成了最简单的处理方式。下面以识别红绿黄作为例子。

定义红绿黄颜色阈值并进行二值化

要识别的模型原图为

int main()
{
    //定义相关Mat变量
    Mat green,red1,red2,yellow,hsv;

    //绿红黄色阈值,因红色在HSV色域中H值有0-10和150-180两段,所以要分别识别
    Scalar lower_green(35,43,46);
    Scalar upper_green(77,255,255);
    Scalar lower_red1(0,43,46);
    Scalar upper_red1(10,255,255);
    Scalar lower_red2(0,150,63);
    Scalar upper_red2(180,255,255);
    Scalar lower_yellow(20,100,100);
    Scalar upper_yellow(30,255,255);

    //读取图像
    Mat img = imread("/home/wch/模板/cv_1/red.png");

    //将图像转换为HSV色域
    cvtColor(img, hsv, COLOR_RGB2HSV);

    //根据各颜色的阈值对图像进行二值化
    inRange(hsv, lower_green, upper_green, green);
    inRange(hsv, lower_yellow, upper_yellow, yellow);
    inRange(hsv, lower_red1, upper_red1, red1);
    inRange(hsv, lower_red2, upper_red2, red2);
    //因红色有两段,进行或操作
    bitwise_or(red1,red2,red1);
    imshow("green",green);
    imshow("red", red1);
    imshow("yellow", yellow);

    waitKey(0);
    destroyAllWindows();
    return 0;
}

经过上面的处理可以得到如下图所示

可以看到很好的识别。

我们再试一次绿色的模型:

效果:

可以看到此时绿色识别得很好,但是红色也被识别出部分。所以需要用到开头的处理办法选取最大面积的为当前颜色。

寻找最大识别面积色块并获取最小外接矩形中心坐标

寻找最大色块需要分两部分,首先在识别出单一色块时寻找单一颜色内的最大面积,这样既可以寻找到识别主体还能替代滤波的作用,免去滤波增快运行效率。第二部分再是识别不同颜色的识别得到的面积,找到面积最大的是识别得到的颜色。

//定义三个颜色识别到的最大色块相关信息结构体
struct colardate
{
    cv::Rect rect; //最小外接矩形
    int whsum;     //面积
    std::string name;  //颜色
};

//实例化三个颜色识别到的最大色块相关信息结构体
colardate sgreen,sred,syellow, *detected;

//寻找最大色块函数(并将信息存入相关结构体)
void findmaxInclass(Mat* input, colardate* scolar, string na)
{
    vector<vector<Point>> contours;
    int max_size = 0, max_index = 0;
    input->at<uchar>(Point(1,1)) = 255;
    findContours(*input, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    Rect rect_cache[contours.size()];
    for(size_t i = 0; i < contours.size(); i++)
    {
        rect_cache[i] = boundingRect(contours[i]);
        //int sum = rect_cache[i].width + rect_cache[i].height;
        int sum = rect_cache[i].area();
        if(max_size < sum)
        {
            max_size = sum;
            max_index = i;
        }
    }
    scolar->whsum = max_size;
    scolar->rect = rect_cache[max_index];
    scolar->name = na;
}

int main()
{
    //定义相关Mat变量
    Mat green,red1,red2,yellow,hsv;

    //绿红黄色阈值,因红色在HSV色域中H值有0-10和150-180两段,所以要分别识别
    Scalar lower_green(35,43,46);
    Scalar upper_green(77,255,255);
    Scalar lower_red1(0,43,46);
    Scalar upper_red1(10,255,255);
    Scalar lower_red2(0,150,63);
    Scalar upper_red2(180,255,255);
    Scalar lower_yellow(20,100,100);
    Scalar upper_yellow(30,255,255);

    //读取图像
    Mat img = imread("/home/wch/模板/cv_1/green.jpg");

    //将图像转换为HSV色域
    cvtColor(img, hsv, COLOR_RGB2HSV);

    //根据各颜色的阈值对图像进行二值化
    inRange(hsv, lower_green, upper_green, green);
    inRange(hsv, lower_yellow, upper_yellow, yellow);
    inRange(hsv, lower_red1, upper_red1, red1);
    inRange(hsv, lower_red2, upper_red2, red2);
    //因红色有两段,进行或操作
    bitwise_or(red1,red2,red1);

    //寻找单一颜色内的最大色块
    findmaxInclass(&green, &sgreen, "green");
    findmaxInclass(&red1, &sred, "red");
    findmaxInclass(&yellow, &syellow, "yellow");

    //寻找三个颜色间的最大色块
    detected = &sgreen;
    if(detected->whsum < sred.whsum)
    {
        detected = &sred;
    }
    if(detected->whsum < syellow.whsum)
    {
        detected = &syellow;
    }

    //绘制最小外接矩形
    rectangle(img, detected->rect, Scalar(0, 255, 0), 2);

    //计算中心坐标并绘制出来
    Point center = detected->rect.tl();
    center.x += detected->rect.width*0.5;
    center.y += detected->rect.height*0.5;
    circle(img, center, 3, Scalar(255,0,0),-1);

    imshow("test", img);
    cout << detected->name<<"\n"<<"center"<<center << endl;

    waitKey(0);
    destroyAllWindows();
    return 0;
}

识别结果:

可以看到能够识别出较为准确的颜色和定位。我们再试一次红色的识别

总体来说,识别效果还是不错的。

在最后的使用中将图片的读取换成摄像头的视频流即可,摄像头的使用可以参考下面代码;

VideoCapture video(1);
if(!video.isOpened())
{
  cout<<"open capture fail"<<endl;
  return -1;
}

video >> img;
if(img.empty())
{
  cout<<"get picture fail"<<endl;
  return -1;
}

while(1)
{
  video.read(img);
  ......
}

因为视频可以视为多张图片连在一起,所以要让代码工作在while循环内。