RM视觉组考核-装甲板识别(Opencv和C++实现)

本套代码的思路


先对图像进行预处理

1.为矫正图像畸变

2.将rgb图像进行通道分离,这样能减少其它光一定的干扰,同时方便进行二值化

3.将分离后的单通道进行二值化

4.将二值化的图片进行形态学操作的开操作,减少噪点,使灯条的位置更加明显

进行灯条识别

5.将预处理好的图片用findcounters进行轮廓识别

6.将识别出的灯条的轮廓存入旋转矩形

7.利用旋转矩形的成员,用面积和宽高比筛选出符合要求的灯条

进行装甲板识别

8.利用旋转矩形的成员角度,匹配两个角度相差不大的灯条矩形作为装甲板的左右两边(经测试,利用角度识别率较高)

9.将匹配好的灯条的中心、宽度、高度、角度作为装甲板矩形的性质

10.用一个新旋转矩形的容器装入识别好的装甲板的性质

坐标获取

11.利用旋转矩形的坐标点的功能,将识别出的装甲板的四个角的坐标装入二维坐标的容器中

自己定义一个三维坐标的容器,要注意顺序对应

画矩形

12.用画直线的方式,分别获取旋转矩形的四个点,进行画线

测距

13.有二维坐标和三维坐标的容器,直接利用solvePnP函数就可以进行测距

14.用putText函数可以将测到的距离显示在图像上

本套代码的优点

1.逻辑比较清晰,利于阅读

2.因为此次任务只用识别红色装甲板,所以直接用 RGB 的红色通道相减,根据设定的阈值得到一张二值图,比起用hsv颜色空间,这种方法操作简洁,耗时低,形成的噪点少。

3.利用opencv自带的旋转矩形类,可以轻松的使用类中的角度、函数等功能,识别率提高。

4.采用面对对象的方法,函数实现较为清晰


源码


include

//detect.cpp

#ifndef _DETECTOR_H_
#define _DETECTOR_H_
#include<opencv2/opencv.hpp>
#include<vector>
#include<cmath>
#include<iostream>
using namespace std;
using namespace cv;

class ArmorDetect
{
    
    public:
    //函数
    //图像预处理
    Mat preprocess(Mat img);
    //寻找轮廓
    vector<RotatedRect> Findcounters(Mat pre);
    //装甲板识别
    vector<RotatedRect> Armordetect(vector<RotatedRect> light);
    //获取二维坐标
    vector<Point2f> point_2d(Mat img, RotatedRect ve);
    //获取三维坐标
    vector<Point3f> point_3d();

    //成员
    //
    Mat cameraMatrix = (Mat_<double>(3, 3) << 1576.70020, 0.000000000000, 635.46084, 0.000000000000, 1575.77707, 529.83878, 0.000000000000, 0.000000000000, 1.000000000000);
    Mat distCoeffs = (Mat_<double>(1, 5) << -0.08325, 0.21277, 0.00022, 0.00033, 0);
    Mat rvec, tvec;
    vector<Point2f> Points2d;
    vector<Point3f> Points3d;
};

#endif

src

//detector.cpp

#include"include/detector.h"


// 图像预处理
Mat ArmorDetect::preprocess(Mat img)
{

    // 定义相机内参矩阵3*3和畸变参数矩阵1*5
    Mat cameraMatrix = (Mat_<double>(3, 3) << 1576.70020, 0.000000000000, 635.46084, 0.000000000000, 1575.77707, 529.83878, 0.000000000000, 0.000000000000, 1.000000000000);
    Mat distCoeffs = (Mat_<double>(1, 5) << -0.08325, 0.21277, 0.00022, 0.00033, 0);
    Mat img_clone;
    // 对图像进行矫正畸变
    // 第一个参数为原图像,第二个参数为输出图像,第三个参数为相机内参矩阵,第四个参数为畸变参数
    undistort(img, img_clone, cameraMatrix, distCoeffs);

    // 通道分离
    vector<Mat> channels; // 创建存储分离出来的多通道的Mat
    // 通道分离函数split,将多通道的矩阵分离成单通道矩阵
    split(img_clone, channels); // 第一个参数为传入图像,第二个参数为存储通道的Mat


    Mat img_gray;              // 存储分离出来的单通道
    img_gray = channels.at(2); // 因为只用识别红色,所以只用使用红色通道的
    int value = 230;           // 设置阈值
    // 二值化处理
    Mat dst;
    threshold(img_gray, dst, value, 255, THRESH_BINARY); // 处理图像,输出图像,阈值,最大值,二值化类型
    
    // 对图像进行形态学操作
    Mat pre;
    // 用矩阵获取结构元素,第一个参数返回椭圆卷积核,第二个参数为结构元素的尺寸,一般尺寸越大腐蚀越明显,第三个参数设置为中心点的位置
    Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3), Point(-1, -1));
    morphologyEx(dst, pre, MORPH_OPEN, kernel); // 输入图像,输出图像,开操作,结构元素
    return pre;
}
//寻找轮廓
vector<RotatedRect> ArmorDetect::Findcounters(Mat pre)
{
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    // 第一个参数为预处理后的单通道图像,第二个参数为轮廓数量(是一个双重向量),第三个参数为轮廓的结构关系
    // 第四个参数为定义轮廓的检索模式,检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略.
    // 第五个参数为定义轮廓的近似方法,仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内
    findContours(pre, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(-1, -1));
    // 用旋转矩阵实现灯条筛选
    vector<RotatedRect> light;
    for (size_t i = 0; i < contours.size(); ++i)
    {

        RotatedRect light_rect = minAreaRect(contours[i]); // 求点集最小的矩形
        double width = light_rect.size.width;              // 求矩形的宽度
        double height = light_rect.size.height;            // 求矩形的高度
        double area = width * height;                      // 求矩形的面积
        if (area < 10)
            continue; // 如果面积太小,忽略
        if (width / height > 0.4)
            continue; // 如果长宽比不符合,忽略
        else
            light.push_back(light_rect); // 将符合要求的矩形筛选出来放入旋转矩形类
    }
    return light;
}
// 装甲板识别
vector<RotatedRect> ArmorDetect::Armordetect(vector<RotatedRect> light)
{
    vector<RotatedRect> armor_final; // 用来放找出的旋转矩形
    RotatedRect armor;               // 旋转矩形,定义找出的矩形
    double angle_differ;             // 定义两个灯条角度的差值
    if (light.size() < 2)
        return armor_final; // 如果检测到灯条数量小于两个,则直接返回
    for (size_t i = 0; i < light.size() - 1; i++)
    {
        for (size_t j = i+1; j < light.size(); j++)
        {
            angle_differ = abs(light[i].angle - light[j].angle);

            bool if1 = (angle_differ < 5);                                                   // 判断角度差值是否大于10
            //bool if2 = (abs(light[i].center.y - light[j].center.y < 20));                     // 判断中心点高度差是否大于20
            //bool if3 = ((light[i].center.x + light[j].center.x) * light[i].size.height > 20); // 判断长宽比是否大于24
            if (if1)
            {
                armor.center.x = (light[i].center.x + light[j].center.x) / 2; // 装甲中心的x坐标
                armor.center.y = (light[i].center.y + light[j].center.y) / 2; // 装甲中心的y坐标
                armor.angle = (light[i].angle + light[j].angle) / 2;
                armor.size.width = abs(light[i].center.x - light[j].center.x); // 装甲板的宽度
                armor.size.height = light[i].size.height;                 // 装甲板的高度
                armor_final.push_back(armor);                             // 将符合要求的矩形放进容器里
            }
        }
    }
    return armor_final;
}

// 二维坐标点加画矩形
vector<Point2f> ArmorDetect::point_2d(Mat img, RotatedRect ve)
{
    Point2f pt[4];
    vector<Point2f> point2d;
    int i;
    //初始化
    for (i = 0; i < 4; i++)
    {
        pt[i].x = 0;
        pt[i].y = 0;
    }
    ve.points(pt);
    //画出矩形
    //第一个参数为要绘制线段的图像,第二个参数为线段的起点,第三个参数为线段的终点
    //第四个参数为线段的颜色,第五个参数为线段的宽度,第六个参数为线段的类型
    line(img, pt[0], pt[1], Scalar(0, 0, 255), 2, 4, 0);  
    line(img, pt[1], pt[2], Scalar(0, 0, 255), 2, 4, 0);  
    line(img, pt[2], pt[3], Scalar(0, 0, 255), 2, 4, 0);  
    line(img, pt[3], pt[0], Scalar(0, 0, 255), 2, 4, 0);  
    //将获取到的二维坐标存入vector  
    point2d.push_back(pt[0]); //左下角
    point2d.push_back(pt[1]); //左上角
    point2d.push_back(pt[2]); //右上角
    point2d.push_back(pt[3]); //右下角
    return point2d;           
}
//获取三维坐标点
vector<Point3f> ArmorDetect:: point_3d()
{
    vector<Point3f> Points3d;
    Point3f point3f;
    // 原点(左下角)
    point3f.x = 0;
    point3f.y = 0;
    point3f.z = 0;
    Points3d.push_back(point3f);
    // 左上角
    point3f.x = 0;
    point3f.y = 5.5;
    point3f.z = 0;
    Points3d.push_back(point3f);
    // 右上角
    point3f.x = 14.0;
    point3f.y = 5.5;
    point3f.z = 0.0;
    Points3d.push_back(point3f);
    // 右下角
    point3f.x = 14;
    point3f.y = 0;
    point3f.z = 0;
    Points3d.push_back(point3f);
    return Points3d;
}

main

#include"include/detector.h"
int main()
{
    //定义要传参的变量
    vector<RotatedRect> light; //用于接收寻找轮廓后的返回值
    vector<RotatedRect> armor_final;  //用于接收装甲板识别后接收的返回值
    
    ArmorDetect detect;    //类的实例化
    //读入相片
    for(int i = 48;i<132;i++){
    Mat img = imread("low/"+to_string(i)+".jpg");
    if (!img.data)
    {
        cout << "404" << endl;
        continue;
    }
    //克隆原图像方便传参进行预处理
    Mat img_clone = img.clone();
    //预处理返回pre
    Mat pre = detect.preprocess(img_clone);
    //寻找灯条轮廓
    light = detect.Findcounters(pre);
    //选择装甲板
    armor_final = detect.Armordetect(light);
    //获取每个大矩形的二维坐标点
    for (size_t i = 0; i < armor_final.size(); i++)
    {
        detect.Points2d = detect.point_2d(img, armor_final[i]);
    }
    //获取每个大矩形的三维坐标点
    detect.Points3d = detect.point_3d();
    //测距
    //第一个参数是三维坐标点的vector,第二个参数是二维坐标点的vector,第三个参数是相机的内参矩阵
    //第四个参数是畸变矩阵,第五个参数是输出的旋转向量,第六个参数是输出的平移向量,第七个为用于SOLVEPNP迭代的参数,第八个为pnp的计算方法
    solvePnP(detect.Points3d, detect.Points2d, detect.cameraMatrix, detect.distCoeffs, detect.rvec, detect.tvec, false, SOLVEPNP_ITERATIVE);
    //显示距离
    string d = to_string(detect.tvec.at<double>(2, 0)) + "cm";
    //第一个参数为待绘制的图像,第二个参数为待绘制的文字,第三个参数为文本框的左下角
    //第四个参数为字体,第五个参数为字体大小,第六个参数为字体颜色
    putText(img, d, detect.Points2d[2], 2, 1, Scalar(18, 195, 127));

    imshow("out", img);
    //imwrite("output/"+to_string(i)+".jpg",img);
    waitKey(100);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_72546212/article/details/128667475