这里是我用opencv做的简单的灯条识别(特别简单)
- 提取颜色
- 灯条匹配
以下所涉及到的代码,博主已经托管至Github:https://github.com/century-yiwen/ArmorDetect
作为一个初学者,学习opencv,我直接看开源,并没有具体的看相关书籍和资料,看看开源的时候,一行一行的注释,不会的百度百度,一点一点的才开始有了眉目,最后才开始写自己的代码,这页代码写得很简单,但是博主已经觉得很开心了。
一开始拿到下面这张图片的时候:
我的内心是崩溃的,因为人肉眼看到的是红色的,但是摄像头拍到的确是白色的。。。
这个问题困扰了我许久,最后某学长告诉我可以用opencv调低摄像头的曝光时间,让周围的灯光暗下来,这张可以更好的识别,所以我的摄像头参数
是这样的:
VideoCapture capture(0);
//capture.set(CV_CAP_PROP_FRAME_WIDTH, 640);//宽度
//capture.set(CV_CAP_PROP_FRAME_HEIGHT, 480);//高度
//capture.set(CV_CAP_PROP_FPS, 30);//帧率 帧/秒
//capture.set(CV_CAP_PROP_BRIGHTNESS, 14);//亮度
//capture.set(CV_CAP_PROP_CONTRAST, 35);//对比度
//capture.set(CV_CAP_PROP_SATURATION, 128);//饱和度
//capture.set(CV_CAP_PROP_HUE, -13);//色调 50
//capture.set(CV_CAP_PROP_EXPOSURE, -11);//曝光
于是乎画面呈现出来就是这样的:
博主心里美滋滋啊!!!!
但是这样有个缺点,就是摄像头在3m之外就识别不到了。。。(这个问题还有待解决)
接下来就是提取颜色和匹配灯条的问题了
首先来说提取颜色,博主走过的坑
提取颜色
在这块我看了些许开源,有些是直接是进行二值化,但是这样二值化的图片的轮廓特别特别多,对于后来的匹配来说有点些许困难,于是乎,博主进行了大量的百度,哈哈~~~,,,,终于找到了一种提取轮廓的方法,于是乎博主自己写了一个函数 :
void ToHSV(Mat image, Mat result)
{
Mat hsv_image; //转HSV
hsv_image.create(image.size(), image.type());
cvtColor(image, hsv_image, CV_BGR2HSV);
Mat mask1 = Mat(image.size(), CV_8UC1);
Mat mask2 = Mat(image.size(), CV_8UC1);
//result = Mat(image.size(), CV_8UC1);
//cout << hsv_image << endl;
/*vector<Mat> channels;
split(hsv_image, channels);
cout << result.row << ' '<<result.col << endl;
int num_row = image.rows;
int num_col = image.cols;
for (int r = 0; r < num_row; r++)
{
//const Vec3b* curr_r_image = image.ptr<Vec3b>(r);
const uchar* curr_r_hue = channels[0].ptr<uchar>(r);
const uchar* curr_r_satur = channels[1].ptr<uchar>(r);
const uchar* curr_r_value = channels[2].ptr<uchar>(r);
uchar* curr_r_result = result.ptr<uchar>(r);
for (int c = 0; c < num_col; c++)
{
//if (((curr_r_hue[c] <= 10 && curr_r_hue[c] >= 0)|| (curr_r_hue[c] <= 180 && curr_r_hue[c] >= 156)||(curr_r_value[c] <= 30 && curr_r_value[c] >= 0)) &&(( curr_r_value[c]<255 &&curr_r_value[c]>200))&&( curr_r_satur[c]>210 && curr_r_satur[c]<255)) //找颜色
if ( ((curr_r_hue[c] <= 10 && curr_r_hue[c] >= 0)|| (curr_r_hue[c] <= 180 && curr_r_hue[c] >= 156)) && (curr_r_value[c]<255 && curr_r_value[c]>46) && (curr_r_satur[c]>43 && curr_r_satur[c]<255)) //找颜色
{
curr_r_result[c] = 255;
}
else
{
curr_r_result[c] = 0;
}
}
}*/
inRange(hsv_image, Scalar(155, 43, 35), Scalar(180, 255, 255), mask1);
inRange(hsv_image, Scalar(0, 43, 35), Scalar(11, 255, 255), mask2);
result = mask1 + mask2;
}
函数有两个参数,第一个参数是一个三通道的Mat,第二个参数是一个单通道的Mat,
中间注释掉的部分是,这么说的。。
在HSV的色彩模式下,利用红色所在的范围进行像素的遍历,,,如果在红色的范围内,那么在第二个单通道中就设置为白色(255),如果不在这个范围内,那么就设置为黑色(0),如此一来,这个函数就只输出一张单通道的二值化图,对于后续的提取轮廓来说就简单多了。,但是想法总是很美好的,现实总是把你的美好摁在地上摩擦~!!!这种方法一调用摄像头就失败,,偶也不知道这究竟是怎么回事,最后还是用opencv自带的函数inRang函数进行颜色的提取,真香!!!!
灯条匹配
颜色匹配完成后,下来就是灯条的匹配,在一张二值化的图中匹配一组装甲板的灯条,话不多说,上代码:
vector<RotatedRect> Armordetection(Mat image)
{
//imshow("cap", image);
//waitKey(30);
Rect external_rect;
float area;
Mat temp_image = image.clone();
vector<vector<Point>> contour;
findContours(temp_image, contour, RETR_EXTERNAL, CHAIN_APPROX_NONE);
vector<RotatedRect> ellipsee(contour.size());
vector<RotatedRect> ellipsee1, ellipsee2, ellipsee3, ellipsee4, finally;
int count1 = 0;
for (int i0 = 0; i0 < contour.size(); i0++)
{
if (contour[i0].size()>10)
{
ellipsee[i0] = fitEllipse(contour[i0]);
external_rect = ellipsee[i0].boundingRect();
area = external_rect.area();//面积
if (area>30 && external_rect.height>0 && external_rect.width>0 && ((double)external_rect.height / (double)external_rect.width)<6.7 && (ellipsee[i0].angle<rotate_angle || ellipsee[i0].angle>(180 - rotate_angle)))
{
ellipsee1.push_back(ellipsee[i0]);
count1++;
}
}
}
int i2, j2;
int flag = 1;
RotatedRect temp;
for (i2 = 1; i2 < ellipsee1.size() && flag == 1; i2++)
{
flag = 0;
for (j2 = 0; j2 < ellipsee1.size() - i2; j2++)
{
if (ellipsee1[j2].size.height < ellipsee1[j2 + 1].size.height)
{
flag = 1;
temp = ellipsee1[j2];
ellipsee1[j2] = ellipsee1[j2 + 1];
ellipsee1[j2 + 1] = temp;
}
}
}
int count2 = 0;
int horizontal_angle_real;
for (int i1 = 0; i1 < count1; i1++)
{
for (int j1 = i1 + 1; j1 < count1; j1++)
{
horizontal_angle_real = hor_angle(ellipsee1[i1].center, ellipsee1[j1].center);
if (horizontal_angle_real < horizontal_angle && ((ellipsee1[i1].center.x - ellipsee1[j1].center.x)<780 || (ellipsee1[i1].center.x - ellipsee1[j1].center.x)>184) && (ellipsee1[i1].center.y - ellipsee1[j1].center.y)<50)
{
ellipsee2.push_back(ellipsee1[i1]);
ellipsee2.push_back(ellipsee1[j1]);
count2++;
if (ellipsee2.size() == 2)
{
break;
}
}
}
if (ellipsee2.size() == 2)
{
break;
}
}
finally = ellipsee2;
return finally;
}
匹配灯条的过程中,先说寻找轮廓,先说两个函数minAreaRect()和fitEllipse(),在寻找过程中进行拟合,前者是拟合一个矩形,返回的是旋转矩形但是高和宽不分,计算机不认识,而后者拟合的的是椭圆,返回的旋转矩形,矩形的长是椭圆的长轴,宽是椭圆的短轴,此时的高永远比宽长,所以以后建议各位看客老爷,可以试试后者。。
我先用宽高比滤掉了一部分的矩形,,,再后来还用到了冒泡排序,将距离最近的两个灯条返回,,最后根据中心坐标点的角度和宽度进行匹配。最后得到了距离最近的一组装甲板,程序到最后将这两个矩形用线条框起来。。。
如图:
程序很简单,,,官方开源的代码太长,太复杂,反正博主没有看懂,,,各位看客老爷要是看懂的可以和我交流交流。。。
这也是我第一次写博客,内心还是有些小激动,,,大家不喜勿喷啊!
喜欢RM的道友可以交个朋友啊!