Bootstrap

Opencv+ROS实现颜色识别应用

目录

一、工具

二、原理

概念

本质

三、实践

 添加发布话题

主要代码

四、成果

五、总结


一、工具

opencv+ros

ubuntu18.04

摄像头

二、原理

概念

彩色图像:RGB(红,绿,蓝)

HSV图像:H(色调)S(饱和度)V(亮度)

色调(H:hue):用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;
饱和度(S:saturation):取值范围为0.0~1.0,值越大,颜色越饱和。
亮度(V:value):取值范围为0(黑色)~255(白色)。

但是在opencv中引用的范围有所不同,给出下表。

本质

颜色识别本质就是在图像上提取出你想要的颜色阈值,然后通过降噪优化模型,轮廓检测进行框选。

要点:

  • RGB转HSV
  • 所需颜色阈值(hsv),并二值化
  • 腐蚀操作除噪,Canny算法进行边缘检测
  • 最后通过findContours()函数找出轮廓坐标

三、实践

读取摄像头

    VideoCapture cap(video_device);  //dev/video0

RGB转HSV

cvtColor(frame, imghsv, COLOR_BGR2HSV);

直方图均衡化

split(imghsv, hsvSplit);
equalizeHist(hsvSplit[2], hsvSplit[2]);
merge(hsvSplit, imghsv);

直方图均衡化是一种简单有效的图像增强技术,用于增强动态范围偏小的图像的对比度

定义颜色阈值,这里选取红色

    Scalar lower_red(156, 43, 46);
    Scalar upper_red(180, 255, 255); // 定义红色的HSV范围

    inRange(imghsv, lower_red, upper_red, mask);//二值化红色部分

inRange()函数就是检测imghsv内所有像素是否在lower-upper之间,如果是则设为255,也就是白色。输出的是二值图。

用腐蚀,膨胀操作去噪点

  Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
  morphologyEx(mask, mask, MORPH_OPEN, kernel);//开运算
  morphologyEx(mask, mask, MORPH_CLOSE, kernel);//闭运算

腐蚀,膨胀操作的对象是二值化图像

  • 腐蚀:变精细
  • 膨胀:变粗矿
  • 开运算:先腐蚀后膨胀 消去一个黑图中的很多小白点
  • 闭运算:先膨胀后腐蚀 消去一个白图中的很多小黑点
  • 梯度运算:膨胀-腐蚀

 高斯滤波,Canny边缘检测

    GaussianBlur(mask, mask, Size(3, 3), 0);//高斯滤波
    Canny(mask, mask, 100, 250);//canny算子边缘检测

 Canny()函数参数表明:

第一个:InputArray类型的image,输入图像
第二个:OutputArray类型的edges,输出的边缘图
第三个:double类型的threshold1,第一个滞后性阈值
第四个:double类型的threshold2,第二个滞后性阈值

 Canny过程为

  1. 高斯滤波获得平滑图像
  2. 计算每个像素点的梯度强度和方向
  3. 应用非极大值抑制,消除边缘检测带来的杂散响应
  4. 双阈值确定真实或潜在的边缘
  5. 抑制弱化边缘完成边缘检测

然后开始找轮廓

findContours()函数

findContours(mask,contours,hierarchy,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE,Point());  

第一个参数:输入图像

第二个参数:所有轮廓

第三个参数:表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号

第四个参数:RETR_EXTERNAL只检测最外围轮廓

第五个参数:CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息

寻找最大轮廓

 vector<double> Area(contours.size());
                        //寻找最大面积的轮廓
                        for (int i = 1; i < contours.size(); i++) {
                            Area[i] = contourArea(contours[i]);
                            if (Area[i] > Area[max]) {
                                max = i;
                            }   
                        }
            Rect boundRect = boundingRect(Mat(contours[max]));
            circle(frame, Point(boundRect.x + boundRect.width/2, boundRect.y + boundRect.height/2), 5, Scalar(0,0,255), -1);

boundingRect()函数

表示包围轮廓的最大矩形

返回四个参数

第一个:boundRect.x

第二个:boundRect.y

第三个:boundRect.width

第四个:boundRect.hight

左上角顶点的像素坐标值及矩形边界的宽和高

然后将矩形在原画面画出即可

ROS_INFO("x:%d,y:%d",boundRect.x+ boundRect.width/2, boundRect.y + boundRect.height/2);
rectangle(frame, Point(boundRect.x, boundRect.y), Point(boundRect.x + boundRect.width, boundRect.y + boundRect.height), Scalar( 0, 0, 255), 2);

 添加发布话题

毕竟是在ros下编写的,我们要把像素坐标发布出去,这里自定义一个消息类型

boundingbox.msg

用来表示类和坐标值

主要代码

 while (ros::ok()) 
    {  
        cap >> frame;  //摄像头画面赋给frame
        if(!frame.empty()) //画面是否正常
        {  
            /*对图片二次处理*/

            cvtColor(frame, imghsv, COLOR_BGR2HSV);// 将图像转换为HSV颜色空间

            split(imghsv, hsvSplit);
		    equalizeHist(hsvSplit[2], hsvSplit[2]);
		    merge(hsvSplit, imghsv);

            inRange(imghsv, lower_red, upper_red, mask);//二值化红色部分

            Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
            morphologyEx(mask, mask, MORPH_OPEN, kernel);//开运算
            morphologyEx(mask, mask, MORPH_CLOSE, kernel);//闭运算
            
            GaussianBlur(mask, mask, Size(5, 5), 0);//高斯滤波
            Canny(mask, mask, 150, 100);//canny算子边缘检测

            vector<vector<Point> > contours;
            vector<Vec4i> hierarchy;
            findContours(mask,contours,hierarchy,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE,Point());  
            //ROS_INFO("个数为%d",int(contours.size()));
            vector<double> Area(contours.size());
            if(contours.size() > 0 )
            {
                       //寻找最大面积的轮廓
                        for (int i = 1; i < contours.size(); i++) {
                            Area[i] = contourArea(contours[i]);
                            if (Area[i] > Area[max]) {
                                max = i;
                            }   
                        }
            Rect boundRect = boundingRect(Mat(contours[max]));
            circle(frame, Point(boundRect.x + boundRect.width/2, boundRect.y + boundRect.height/2), 5, Scalar(0,0,255), -1);
            ROS_INFO("x:%d,y:%d",boundRect.x+ boundRect.width/2, boundRect.y + boundRect.height/2);
            rectangle(frame, Point(boundRect.x, boundRect.y), Point(boundRect.x + boundRect.width, boundRect.y + boundRect.height), Scalar( 0, 0, 255), 2);
            detect_msg.Class = "red";
            detect_msg.xmin = boundRect.x;
            detect_msg.xmax=boundRect.x + boundRect.width;
            detect_msg.ymin=boundRect.y;
            detect_msg.ymax= boundRect.y + boundRect.height;
            }

四、成果

运行画面

 查看话题

这里识别画面内所有红色区域

五、总结

写代码过程中还是遇到很多问题的,不知道是opencv版本不兼容的问题还是哪里我编写不细致,节点总是挂掉。

但还是能完成基本需求。

这里把报错留下,希望有大佬能帮帮我

OpenCV Error: Assertion failed (npoints >= 0 && (depth == CV_32F || depth == CV_32S)) in pointSetBoundingRect, file /build/opencv-L2vuMj/opencv-3.2.0+dfsg/modules/imgproc/src/shapedescr.cpp, line 466
terminate called after throwing an instance of 'cv::Exception'
  what():  /build/opencv-L2vuMj/opencv-3.2.0+dfsg/modules/imgproc/src/shapedescr.cpp:466: error: (-215) npoints >= 0 && (depth == CV_32F || depth == CV_32S) in function pointSetBoundingRect

应该是boundingRect()函数的问题,但不知道问题在哪

欢迎评论区指正。

;