findLights

本函数寻找轮廓,遍历每个轮廓,传入isLight()判断是否为灯条,是则判断颜色,将符合的灯条放入candidateLights容器

接下来将会对代码进行拆分讲解

    vector<vector<cv::Point>> contours;
    vector<cv::Vec4i> hierarchy;
    cv::findContours(_binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

#ifdef DRAW_LIGHTS_CONTOURS
    cv::Mat showSrc = _src.clone();
    for(int i=0;i< contours.size();i++)
    cv::drawContours(showSrc,contours,i,Scalar(255,0,0),2,LINE_8);
//    namedWindow("showSrc",WINDOW_NORMAL);
    imshow("showSrc",showSrc);
#endif

这里使用OpenCV的findContours函数寻找每一个轮廓

前面我们通过 setImage 函数对图像已经进行了预处理,这里已经是一个二值化图像

DRAW_LIGHTS_CONTOURS 宏查看匹配到的所有轮廓

注意:在调试过程中查看图片时不要出现同名的窗口

    if (contours.size() < 2)
    {
//        printf("no 2 contours\n");
        return;
    }

如果轮廓的数量小于两个,那自然不必说,不可能存在装甲板

接下来一段代码便是本函数的核心了,如下:

    for (auto & contour : contours)
    {
        RotatedRect r_rect = minAreaRect(contour);
        Light light = Light(r_rect);

        if (isLight(light, contour))
        {
            cv::Rect rect = r_rect.boundingRect();

            if (0 <= rect.x && 0 <= rect.width  && rect.x + rect.width  <= _src.cols &&
                0 <= rect.y && 0 <= rect.height && rect.y + rect.height <= _src.rows)
            {

                cv::Mat roi = _src(rect);
                cv::Mat mask = _binary(rect);

                Scalar sum = cv::mean(roi,mask);

                light.lightColor = sum[2] > sum[0] ? RED : BLUE;

                // 颜色不符合电控发的就不放入 1:RED 2:BLUE
                if(light.lightColor == 2)
                {
                    candidateLights.emplace_back(light);
#ifdef DRAW_LIGHTS_RRT
                    cv::Mat showSrc = _src.clone();
                    Point2f vertice_lights[4];
                    light.points(vertice_lights);
                    for (int i = 0; i < 4; i++) {
                        line(showSrc, vertice_lights[i], vertice_lights[(i + 1) % 4], CV_RGB(255, 0, 0),2,LINE_8);
                    }
                    //circle(showSrc,light.center,5,Scalar(0,0,0),-1);
//                    namedWindow("showSrc",WINDOW_NORMAL);
                    imshow("showLignt", showSrc);
#endif //DRAW_LIGHTS_RRT
                }
            }
        }
    }

接下来将一个一个部分进行介绍

 for (auto & contour : contours)

for ( : )这个特性是C++11引入,这种用法这种其他语言例如 php,Java 等都有

这个写法可以实现对容器的遍历,auto是一个自动推导类型,可以自动判断返回的类型,如果不懂,当一个固定写法即可

    RotatedRect r_rect = minAreaRect(contour);
    Light light = Light(r_rect);

这里使用OpenCV的minAreaRect函数寻找每一个轮廓的最小外接矩形,同时创建一个Light的结构体来保存轮廓数据

Light这个结构体已经移动到 include/robot_struct.h 在 Armor_Detection.h 也保留了注释痕迹方便查看


接下去则是 isLight(light, contour) 函数,这个函数是对灯条的判断,如下:

bool ArmorDetector::isLight(Light& light, vector<Point> &cnt)
{
    double height = light.height;
    double width = light.width;

    if(height <= 0 || width <= 0)
        return false;

    // 高一定要大于宽
    bool standing_ok = height > width;

    // 高宽比条件
    double hw_ratio = height / width;
    bool hw_ratio_ok = light_min_hw_ratio < hw_ratio && hw_ratio < light_max_hw_ratio;

    // 外接矩形面积和像素点面积之比条件
    double area_ratio =  contourArea(cnt) / (height * width);
    bool area_ratio_ok = light_min_area_ratio < area_ratio && area_ratio < light_max_area_ratio;

    // 灯条角度条件
    bool angle_ok = fabs(90.0 - light.angle) < light_max_angle;
    // cout<<"angle: "<<light.angle<<endl;

    // 限制面积条件
    bool area_ok = contourArea(cnt) < light_max_area;

    // 灯条判断的条件总集
    bool is_light = hw_ratio_ok && area_ratio_ok && angle_ok && standing_ok && area_ok;

    if(!is_light)
    {
//        cout<<hw_ratio<<"    "<<contourArea(cnt) / light_max_area<<"    "<<light.angle<<endl;
    }

    return is_light;
}

这个函数判断条件写的很清楚,这里不做解释


下面则使用 boundingRect() 函数获取轮廓矩形边界信息

cv::Rect rect = r_rect.boundingRect();
if (0 <= rect.x && 0 <= rect.width  && rect.x + rect.width  <= _src.cols &&
    0 <= rect.y && 0 <= rect.height && rect.y + rect.height <= _src.rows)
{

}

if判断是一个异常错误验证,防止出现奇怪的数据被处理


                cv::Mat roi = _src(rect);
                cv::Mat mask = _binary(rect);

                Scalar sum = cv::mean(roi,mask);

                light.lightColor = sum[2] > sum[0] ? RED : BLUE;

                // 颜色不符合电控发的就不放入 1:RED 2:BLUE
                if(light.lightColor == 2)
                {
                    candidateLights.emplace_back(light);
#ifdef DRAW_LIGHTS_RRT
                    cv::Mat showSrc = _src.clone();
                    Point2f vertice_lights[4];
                    light.points(vertice_lights);
                    for (int i = 0; i < 4; i++) {
                        line(showSrc, vertice_lights[i], vertice_lights[(i + 1) % 4], CV_RGB(255, 0, 0),2,LINE_8);
                    }
                    //circle(showSrc,light.center,5,Scalar(0,0,0),-1);
//                    namedWindow("showSrc",WINDOW_NORMAL);
                    imshow("showLignt", showSrc);
#endif //DRAW_LIGHTS_RRT
                }

这里是对灯条的颜色进行判断,使用了roi和mask

ROI:感兴趣区域(简单的说就是截图部分)

Mask:掩摸区域(掩摸的部分就较为复杂,掩摸简单的理解就是用来标记要计算的区域)

这里的ROI和Mask都是在原图上取灯条的部分,再使用 mean 平均值函数来计算每一个通道平均值,返回一个长度为3的数组(简单理解)

sum[2] > sum[0] ? RED : BLUE;

c++的三目运算符判断颜色,这里已经计算了每一个通道的平均值,通过判断R通道和B通道哪个大来判断颜色

相信你应该知道RGB模型,也知道RGB模型在OpenCV里面的顺序是BGR

candidateLights.emplace_back(light);

然后将所有符合要求的灯条放入容器中

results matching ""

    No results matching ""