RM-对装甲板灯条进行识别代码分析

最近要完成装甲板灯条识别的任务,但我本身对于OpenCV的了解还是甚微,所以只能是借鉴他人的代码并加以自身的理解。这里我借鉴的是https://blog.csdn.net/weixin_64054906/article/details/126674493
这里我逐段加以自己的分析。

1.引用头文件

#include "stdio.h"
#include<iostream> 
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;

这个我就不多赘述了,所有代码都要有的一块。

2.创建灯条类

由于在本次的代码中主要是根据灯条来进行对装甲板的识别,后续代码也要多次用到灯条相关的性质,所以先创建一个灯条类。

class LightDescriptor
{
public:float width, length, angle, area;  //创建灯条的属性变量,包括宽度,长度,角度,面积
      cv::Point2f center;  //创建灯条中心坐标的变量
public:
    LightDescriptor() {};  //声明灯条的函数,本函数作用实际上就是为主函数中使用灯条主函数的属性变量提供一种简便写法。
    LightDescriptor(const cv::RotatedRect& light) //创建灯条函数
    {
        width = light.size.width;
        length = light.size.height;
        center = light.center;
        angle = light.angle;
        area = light.size.area();
    }
};

3.集中定义变量(从这里开始进入主函数)

    VideoCapture video; //VC类对象化
    video.open("1234567.mp4"); //打开准备好的视频
    //变量集中定义
    Mat frame, channels[3], binary, Gaussian, dilatee; //frame对应录像的每一帧,用于后续读取录像;channels[3]定义图像的RGB通道;binary,Gaussian,dilatee都是定义一张图像
    Mat element = getStructuringElement(MORPH_RECT, Size(5, 5)); //创建一个矩形,大小为5*5的卷积核
    Rect boundRect; //这个变量后面没用过,y1s1,我真不知道在这里有啥用
    RotatedRect box; //这个同上
    vector<vector<Point>> contours;//与下一个变量作为轮廓检测函数的参数
    vector<Vec4i> hierarchy;
    vector<Point2f> boxPts(4);

4.图像预处理

        Rect point_array[20]; //未知
        video >> frame;  //读取每帧
        if (frame.empty()) {
            break;   //如果找不到录像,就跳出程序
        }
        split(frame, channels); //通道分离
        threshold(channels[0], binary, 220, 255, 0);//二值化
        GaussianBlur(binary, Gaussian, Size(5, 5), 0);//滤波
        dilate(Gaussian, dilatee, element); //对灯条进行膨胀
        findContours(dilatee, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);//轮廓检测
        vector<LightDescriptor> lightInfos;//创建一个灯条类的动态数组

5.筛选灯条

for (int i = 0; i < contours.size(); i++) {
            // 求轮廓面积
            double area = contourArea(contours[i]);
            // 去除较小轮廓&fitEllipse的限制条件
            if (area < 5 || contours[i].size() <= 1)
                continue;//相当于就是把这段轮廓去除掉
            // 用椭圆拟合区域得到外接矩形(特殊的处理方式:因为灯条是椭圆型的,所以用椭圆去拟合轮廓,再直接获取旋转外接矩形即可)
            RotatedRect Light_Rec = fitEllipse(contours[i]);
 
            // 长宽比和轮廓面积比限制(由于要考虑灯条的远近都被识别到,所以只需要看比例即可)
            if (Light_Rec.size.width / Light_Rec.size.height > 4)
                continue;
            lightInfos.push_back(LightDescriptor(Light_Rec));
        }

6.二重循环多条件匹配灯条

        for (size_t i = 0; i < lightInfos.size(); i++) {
            for (size_t j = i + 1; (j < lightInfos.size()); j++) {
                LightDescriptor& leftLight = lightInfos[i]; //左灯条特征
                LightDescriptor& rightLight = lightInfos[j]; //右灯条特征
                float angleGap_ = abs(leftLight.angle - rightLight.angle); //获取左右灯条的角度差
                //由于灯条长度会因为远近而受到影响,所以按照比值去匹配灯条,以下就是根据灯条的形态学特征逐条筛选符合条件的灯条
                float LenGap_ratio = abs(leftLight.length - rightLight.length) / max(leftLight.length, rightLight.length);
                float dis = pow(pow((leftLight.center.x - rightLight.center.x), 2) + pow((leftLight.center.y - rightLight.center.y), 2), 0.5);
                //均长
                float meanLen = (leftLight.length + rightLight.length) / 2;
                float lengap_ratio = abs(leftLight.length - rightLight.length) / meanLen;
                float yGap = abs(leftLight.center.y - rightLight.center.y);
                float yGap_ratio = yGap / meanLen;
                float xGap = abs(leftLight.center.x - rightLight.center.x);
                float xGap_ratio = xGap / meanLen;
                float ratio = dis / meanLen;
                //匹配不通过的条件
                if (angleGap_ > 15 ||
                    LenGap_ratio > 1.0 ||
                    lengap_ratio > 0.8 ||
                    yGap_ratio > 1.5 ||
                    xGap_ratio > 2.2 ||
                    xGap_ratio < 0.8 ||
                    ratio > 3 ||
                    ratio < 0.8) {
                    continue;
                }

7.绘制矩形

Point center = Point((leftLight.center.x + rightLight.center.x) / 2, (leftLight.center.y + rightLight.center.y) / 2); //获取两灯条的中心坐标
                RotatedRect rect = RotatedRect(center, Size(dis, meanLen), (leftLight.angle + rightLight.angle) / 2);
                Point2f vertices[4];
                rect.points(vertices);
                for (int i = 0; i < 4; i++) {
                    line(frame, vertices[i], vertices[(i + 1) % 4], Scalar(0, 0, 255), 2.2);
                }

最后附上完整代码

#include "stdio.h"
#include<iostream> 
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "task2.h"
using namespace std;
using namespace cv;
//由于在识别中的核心物体以及相关的物理特性是灯条,所以建一个灯条类
class LightDescriptor
{	    //在识别以及匹配到灯条的功能中需要用到旋转矩形的长宽偏转角面积中心点坐标等
public:float width, length, angle, area;
      cv::Point2f center;
public:
    LightDescriptor() {};
    //让得到的灯条套上一个旋转矩形,以方便之后对角度这个特殊因素作为匹配标准
    LightDescriptor(const cv::RotatedRect& light)
    {
        width = light.size.width;
        length = light.size.height;
        center = light.center;
        angle = light.angle;
        area = light.size.area();
    }
};
int main()
{
    getMarix();
    VideoCapture video; //VC类对象化
    video.open("1234567.mp4");
    //变量集中定义
    Mat frame, channels[3], binary, Gaussian, dilatee;
    Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
    Rect boundRect;
    RotatedRect box;
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    vector<Point2f> boxPts(4);
    //图像预处理
    for (;;) {
        Rect point_array[20];
        video >> frame;  //读取每帧
        if (frame.empty()) {
            break;
        }
        split(frame, channels); //通道分离
        threshold(channels[0], binary, 220, 255, 0);//二值化
        GaussianBlur(binary, Gaussian, Size(5, 5), 0);//滤波
        dilate(Gaussian, dilatee, element);
        // dilate(Gaussian, dilate, element, Point(-1, -1));//膨胀,把滤波得到的细灯条变宽
        findContours(dilatee, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);//轮廓检测
        vector<LightDescriptor> lightInfos;//创建一个灯条类的动态数组
    //筛选灯条
        for (int i = 0; i < contours.size(); i++) {
            // 求轮廓面积
            double area = contourArea(contours[i]);
            // 去除较小轮廓&fitEllipse的限制条件
            if (area < 5 || contours[i].size() <= 1)
                continue;//相当于就是把这段轮廓去除掉
            // 用椭圆拟合区域得到外接矩形(特殊的处理方式:因为灯条是椭圆型的,所以用椭圆去拟合轮廓,再直接获取旋转外接矩形即可)
            RotatedRect Light_Rec = fitEllipse(contours[i]);
 
            // 长宽比和轮廓面积比限制(由于要考虑灯条的远近都被识别到,所以只需要看比例即可)
            if (Light_Rec.size.width / Light_Rec.size.height > 4)
                continue;
            lightInfos.push_back(LightDescriptor(Light_Rec));
        }
        //二重循环多条件匹配灯条
        for (size_t i = 0; i < lightInfos.size(); i++) {
            for (size_t j = i + 1; (j < lightInfos.size()); j++) {
                LightDescriptor& leftLight = lightInfos[i];
                LightDescriptor& rightLight = lightInfos[j];
                float angleGap_ = abs(leftLight.angle - rightLight.angle);
                //由于灯条长度会因为远近而受到影响,所以按照比值去匹配灯条
                float LenGap_ratio = abs(leftLight.length - rightLight.length) / max(leftLight.length, rightLight.length);
                float dis = pow(pow((leftLight.center.x - rightLight.center.x), 2) + pow((leftLight.center.y - rightLight.center.y), 2), 0.5);
                //均长
                float meanLen = (leftLight.length + rightLight.length) / 2;
                float lengap_ratio = abs(leftLight.length - rightLight.length) / meanLen;
                float yGap = abs(leftLight.center.y - rightLight.center.y);
                float yGap_ratio = yGap / meanLen;
                float xGap = abs(leftLight.center.x - rightLight.center.x);
                float xGap_ratio = xGap / meanLen;
                float ratio = dis / meanLen;
                //匹配不通过的条件
                if (angleGap_ > 15 ||
                    LenGap_ratio > 1.0 ||
                    lengap_ratio > 0.8 ||
                    yGap_ratio > 1.5 ||
                    xGap_ratio > 2.2 ||
                    xGap_ratio < 0.8 ||
                    ratio > 3 ||
                    ratio < 0.8) {
                    continue;
                }
                //绘制矩形
                Point center = Point((leftLight.center.x + rightLight.center.x) / 2, (leftLight.center.y + rightLight.center.y) / 2);
                RotatedRect rect = RotatedRect(center, Size(dis, meanLen), (leftLight.angle + rightLight.angle) / 2);
                Point2f vertices[4];
                rect.points(vertices);
                for (int i = 0; i < 4; i++) {
                    line(frame, vertices[i], vertices[(i + 1) % 4], Scalar(0, 0, 255), 2.2);
                }
            }
        }
 
        namedWindow("video", WINDOW_FREERATIO);
        imshow("video", frame);
        waitKey(5);
    }
    video.release();
    cv::destroyAllWindows();
    return 0;
}

image

posted @ 2023-04-07 14:22  XnobodyT  阅读(634)  评论(0)    收藏  举报