本套代码的思路
先对图像进行预处理
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;
}