首先请出我们的主角
我们今天要识别的就是这张图片,然后首先我们来看看最后的识别效果:
虽然识别出来之后画的标记不是很好,但是只要位置信息出来了,画标记其实是次要的了。
预备知识
这里先介绍一下用到的OpenCV
中的名词(有函数有类)
Mat
:类名,用于储存图像
namedWindow
:使imshow
函数展示的图片窗口大小可调
imshow
:展示图片,并自定义图片窗口名
RotatedRect
:旋转矩形,类名
Point2f
:一个点,点的两个坐标为float型变量
points
:返回旋转矩形的四个顶点
line
:连线函数,参数的意义为:绘制的图片,点1,点2,线的颜色,线的宽度
cvtColor
:将图片转化为灰度图,三个参数的含义为:输入图片,输出图片,转换的细节(我也刚学,建议csdn)
threshold
:将图片转化为灰度图:五个参数的含义为:输入图片,输出图片,亮度下界,亮度上界(上下界之中的像素会被置为白色,其余为黑色),转化细节(不了解,同上^ 3^)
split
:人如其名,将一张图片的RGB
分裂开来,以BGR
的顺序存在Mat
数组中(便于后面的寻找颜色位置)
subtract
:做减法,不过对象为两张图片,三个参数为:被减数,减数,差
不过这里需要注意的是,RoboMaster里面只有两种装甲板,红色和蓝色,红色我们就红减蓝,蓝色我们就蓝减红
getStructuringElement
:的意义是创建一个用于膨胀操作的图形,两个参数的含义是:图形类型(有矩形,椭圆啥的),大小
dilate
:膨胀操作,三个参数的含义为:输入图像,输出图像,膨胀用的图形
Point
:表示一个点
findContours
:根据二值图片,寻找二值位置的轮廓,四个参数的含义为:输入图片,储存轮廓容器,后面两个是找轮廓的选项(我也不是很了解)
auto
:这个不用多说哈
minAreaRect
:找到能够装下这个轮廓的最小矩形
waitKey
:设置展示图片多久自动关闭窗口,单位为毫秒,若为0,则为手动关闭
然后我们一步步介绍识别步骤
(所有实现基于VS2017+OpenCV4.1.1
)
首先我们需要读入图片,用库函数imread
读入,我们的图片文件需要与main
文件在同一位置,不然我这样读取会报错
然后对其进行预处理(传入pretreat
函数中),预处理的目的是得到一张经过膨胀操作后的二值图,再用后续的操作得到轮廓并画出
现在我们先介绍pretreat
函数:
首先我们将传入的图片转换为一个灰度图cvtColor(raw, grey, COLOR_BGR2GRAY);
然后转化为二值图threshold(grey, bigrey, 150, 255, THRESH_BINARY);
再将原图RGB分离split(raw, channel);
将B通道减去R通道subtract(channel[0], channel[2], sub_br);
将相减之后的图片二值化threshold(sub_br, sub_br, 110, 255, THRESH_BINARY);
创建一个用于膨胀的图形Mat element1 = getStructuringElement(MORPH_RECT, Size(3, 3));
进行膨胀操作dilate(sub_br, sub_br, element1);
ret = sub_br & bigrey;
这一句,我们用原图 灰度二值 之后的图片与(&)上原图 通道相减二值膨胀 之后的图片,提取出较为可信的候选区域后,再进行膨胀
创建另一个用于膨胀的图片Mat element2 = getStructuringElement(MORPH_RECT, Size(2, 2));
对我们与操作之后的结果进行膨胀操作dilate(ret, ret, element2);
然后将ret
作为结果传出
可能会很疑惑,这些参数都是怎么来的没错都是一个个试出来的,换了图片就不一定管用了
然后介绍一下fixarmor
函数(后续的展示基于原图)
其实这张图经过预处理之后还会剩余很多的不需要的白色色块,这个时候就需要我们用匹配进行取舍,只有能够配对的我们才将其保留下来
还有就是,这里的匹配操作的时间复杂度是 O(n2),我说实话我不知道有没有更好的匹配方法
contour
:我们储存轮廓的容器,但是我不知道为什么需要这么创建(指模板参数)考ccf差点坑死我(指创建方式)
rect
:存我们匹配后的所有矩形
ret
:最后配对成功时,会以pair的形式存在ret中
findContours(img, contour, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
找轮廓
然后将轮廓弄成矩形后,存在rect
中
(这里用找到的temp
画的图)
然后双重for
,匹配灯条(匹配完之后就是我们最后的结果,所以不展示了)
我这里用了角度,面积,长宽比进行筛选
(除了长宽比需要用取出的长除取出的宽之外,其余两个都是成员变量)
后面就是匹配,没错参数也是我一个个试出来的
最后是源码:(这个没有分文件编写
#include<opencv2/opencv.hpp>
#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
using namespace cv;
void show(Mat img, string name)
{
namedWindow(name, 0);
imshow(name, img);
return;
}
void drawrect(Mat &img, RotatedRect ob)
{
Point2f v[4];
ob.points(v);
for (int i = 0; i < 4; i++)
line(img, v[i], v[(i + 1) % 4], Scalar(0, 255, 0), 1);
return;
}
void drawtarget(Mat &img, RotatedRect ob1, RotatedRect ob2)
{
Point2f v1[4], v2[4];
ob1.points(v1);
ob2.points(v2);
line(img, v1[0], v1[1], Scalar(0, 255, 0), 1);
line(img, v1[0], v2[3], Scalar(0, 255, 0), 1);
line(img, v2[2], v1[1], Scalar(0, 255, 0), 1);
line(img, v2[2], v2[3], Scalar(0, 255, 0), 1);
return;
}
Mat pretreat(Mat raw)
{
Mat grey;
cvtColor(raw, grey, COLOR_BGR2GRAY);
Mat bigrey;
threshold(grey, bigrey, 150, 255, THRESH_BINARY);
Mat channel[3];
split(raw, channel);
Mat sub_br;
subtract(channel[0], channel[2], sub_br);
threshold(sub_br, sub_br, 110, 255, THRESH_BINARY);
Mat element1 = getStructuringElement(MORPH_RECT, Size(3, 3));
dilate(sub_br, sub_br, element1);
Mat ret;
ret = sub_br & bigrey;
Mat element2 = getStructuringElement(MORPH_RECT, Size(2, 2));
dilate(ret, ret, element2);
return ret;
}
void fixarmor(Mat &raw,Mat img)
{
vector<vector<Point>> contour;
vector<RotatedRect> rect,finall;
vector<pair<RotatedRect, RotatedRect>> ret;
findContours(img, contour, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (auto x : contour)
{
RotatedRect temp = minAreaRect(x);
rect.push_back(temp);
}
for (auto x : rect)
{
for (auto y : rect)
{
double xangle, yangle, xarea, yarea, xrate, yrate;
xarea = x.size.area();
xangle = x.angle;
yarea = y.size.area();
yangle = y.angle;
xrate = x.size.height / x.size.width;
yrate = y.size.height / y.size.width;
if (xarea == yarea) continue;
if (xangle > 10 || yangle > 10) continue;
if (xarea < 15 || yarea < 15) continue;
if (fabs(xarea - yarea) < 20 && fabs(xangle - yangle) < 2
&& fabs(xrate - yrate) < 0.1&&fabs(x.size.height - y.size.height) < 5
&& fabs(x.size.width - y.size.width) < 5)
ret.push_back({ x,y });
}
}
//cout << ret.size() << endl;
for (auto x : ret) { drawrect(raw, x.first); drawrect(raw, x.second);}
for (auto x : ret) drawtarget(raw, x.first, x.second);
return;
}
int main()
{
Mat hero = imread("hero.jpg");
Mat temp = pretreat(hero);
fixarmor(hero,temp);
show(hero, "hero");
waitKey(0);
//system("pause");
return 0;
}
然后按道理是可以将蓝减红改成红减蓝来识别红色灯条的我没试过,如果识别不了,可以评论私信找我击剑
截中间图好麻烦不要问我为什么用绿色画图,我做题看不到绿色,所以我要多看看绿色