记2017中国机器人大赛先进视觉项目

主要目标

这个项目的主要目标是识别和测量摄像头拍摄到的照片中的物体。具体要求为:在隔摄像头(罗技170,500万像素)1米外放置一块白板,白板上贴上一张白纸,白纸上用黑色笔标注出一个70x50的矩形框,然后放置一些色块和实物的打印图片粘贴到黑色矩形框内(色块颜色包括:黑色、黄色、绿色、蓝色,形状包括:圆形、椭圆、矩形、正方形,实物包括:绿箭盒、曲曲饼盒、方便面盒、可乐盒)。具体的情况差不多和下图相似(像素不高,和实际略有区别):

image

项目中需要设计图形化界面,以黑色矩形框为标定,标定结束后准确识别出物体的种类并测量出物体的长宽、中心点、面积、偏转角。比赛中声明每个物体有编号,如下:

1
2
3
4
//圆形、正方形、长方形、椭圆形4种(ID依次为1, 2, 3, 4)
//可乐罐、口香糖、方便桶面、饼干盒4种(ID依次为81, 82, 83, 84)
//黑、红、黄、绿、蓝5种(ID依次为1, 2, 3, 4, 5)
//ID[2]:第一个为颜色,第二个为形状

解决方法

技术方案

Opencv+QT:Opencv做摄像头图像采集,QT(QT部分中规中举,后面就不怎么提啦)提供输入输出。

检测的基本流程

  • 1.对拍摄的图像进行高斯过滤,二值化,轮廓查找,然后找轮廓的contours进行判断

    • 图像的二值化就是将图像上的像素点的灰度值设置为0或255,这样将使整个图像呈现出明显的黑白效果。与边缘检测相比,轮廓检测有时能更好的反映图像的内容,而要对图像进行轮廓检测,则必须要先对图像进行二值化,在数字图像处理中,二值图像占有非常重要的地位,图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。
    • threshold 方法是通过遍历灰度图中点,将图像信息二值化,处理过后的图片只有两种色值。
  • 2.将面积小于400的轮廓滤掉(有点利用比赛规则的意思了),进行矩形的判断:

    • 使用多边形逼近的点的个数来判断是否是矩形(如果是则到步骤4)
    • 如果满足凸多边形而且有四个逼近点说明是矩形,这里限制了轮廓面积大于800
    • 计算最小外接矩形,滤掉照片的轮廓矩形框(最外面的标定框)
    • 判断杂色
      • 如果有杂色,按照长宽比可将可乐罐和其他过滤,否则是绿箭
      • 如果没有杂色,通过长宽比(长和宽差值在4个像素内)计算是正方形还是矩形,然后特别计算正方形角度(-45度到+45度)
  • 3.如果前面没有检测到,则检测圆

    • 先求出轮廓的最小外接圆和最小外接椭圆
    • 计算外接的圆的半径(通常大近3个像素)和用面积计算出的半径,差值小于4,外界椭圆的面积差值小于30,说明大致使用圆的形状
    • 检测杂色
      • 如果是杂色,说明是奥利奥,通过最小外接矩形计算
      • 如果不是杂色,计算圆的信息
    • 如果计算出来不符,就进行椭圆的判定,计算的出来的半径大于4最小外接椭圆的面积和轮廓面积差值小于30
    • 判断杂色
      • 如果是杂色,可能是可乐罐(较少),通过可乐罐的比值限定小于或大于某个阈值进行计算
      • 如果不是杂色,计算椭圆的信息
  • 4.如果还没检测到,只可能是可乐罐或者方便面

    • 判断杂色,非杂色不用管,通过可乐罐的小于0.6或者大于1.6计算
    • 否则是方便面直接计算.

杂色判断

在上面这个流程中,判断是色块和实物的方法其实一个简单办法就是判断这个图形中是否有杂色。判断杂色我们使用的是纯RGB进行颜色判断,RGB检测中本来检测颜色的方法使用的是给定一个阈值范围,因为RGB值{R,G,B}(0≤R,G,B≤255)比较明显。但是由于摄像头的色差,最后出来的结果比较不理想。于是我们进行了一些小的改进。

方法思路

传入一个轮廓的中心点。然后从改点往四个方向进行DFS,判断下一个点和这个点之间各个通道的RGB值的差值,如果差值大于20,那么就说明是杂色。如果不是杂色,那么根据中心点的RGB三个通道的差值来进行判断。
找轮廓和中心点的方法就不写了。现在已经有中心点了。具体如图:

中心点

将dfs到的点在二值图上打亮,得到的结果可以看到扫描的区域比较理想。

打亮二值图

经过调试参数之后准确地得到了最后结果:

结果图

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//---------检测是否纯色-----------
int dir[4][2]={
0,1,1,0,0,-1,-1,0
};//往四个方向进行搜索
int check(int x,int y)
{
if(x>0&&x<=cvGetSize(thrImg).width&&y>0&&y<=cvGetSize(thrImg).height)
return 1;
return 0;
}

void dfs(int x,int y,int watch[3],int depth)
{
//对该点的颜色进行读取,存到watchi[3]中
int watchi[3];
for(int i=0;i<3;i++)
watchi[i] = originMat.at<Vec3b>(y, x)[i];

//是否在二值图上显示dfs到的点
// cout<<x<<":"<<y<<endl<<"0:"<<watch[0]<<" 1:"<<watch[1]<<" 2:"<<watch[2]<<endl;
// cvSetReal2D(thrImg, y, x, 255.0);
// cvShowImage("colorJudge", thrImg);
// cvWaitKey();

//进行判断,对watch值进行比较
for(int i=0;i<3;i++)
{
if(abs(watch[i]-watchi[i])>20)
mix=1;
}
for(int i=0;i<4;i++)
{
int dx=x+dir[i][0];
int dy=y+dir[i][1];
if(check(dx,dy)&&depth<9&&!vis[dy][dx]){
vis[dy][dx]=1;
dfs(dx,dy,watchi,depth+1);
}
}
}

int isColorPure(int x,int y)
{//传值按照x,y
int ans=isColorPure(x,y,0);
return ans;
}
int isColorPure(int x,int y,int depth)
{//传值按照x,y
memset(vis,0,sizeof(vis));
//对该点的颜色进行读取,存到watchi[3]中
mix=0;
int watchi[3];
for(int i=0;i<3;i++)
watchi[i] = originMat.at<Vec3b>(y, x)[i];

vis[y][x]=1;
dfs(x,y,watchi,depth);
// cvWaitKey();
if(mix==1){
return -1;
}else{
return getColor(y,x);
}
}

得到某个点的颜色。首先根据差值来判断(用这个就能有返回值了,后面的基本没作用),如果判断不出来则根据范围判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int getColor(int x,int y)
{ //传值按照y,x
// imshow("mat",originMat);
// 源图像载入及判断
if( !originMat.data )
return -1;
Mat tempImage = originMat.clone();
int watch[3],flag[3];
flag[0]=flag[1]=flag[2]=0;
for(int i=0;i<3;i++)
watch[i] = originMat.at<Vec3b>(x, y)[i];

for(int i=0;i<3;i++){
if(watch[i]>colorRecgnize) //
flag[i]=1;
}//BGR蓝绿红

cvSetReal2D(thrImg, x, y, 255.0);
cvShowImage("colorJudge", thrImg);
// cvSet2D(originImg,x,y, cvScalar(0, 255, 0, 0)); //绘制来查看检测点的位置
// cvShowImage("yanse", originImg);

int colorRange=40;
//通过BGR之间的差值来进行判断
if(abs(watch[0]-watch[1]<colorRange)&&abs(watch[0]-watch[2])<colorRange&&abs(watch[1]-watch[2])<colorRange){
return 1;//黑
}else if(watch[2]-watch[0]>colorRange&&watch[2]-watch[1]>colorRange){
return 2;//红
}else if(watch[2]-watch[0]>colorRange&&watch[1]-watch[0]>colorRange&&abs(watch[1]-watch[2])<colorRange){
return 3;//黄
}else if(watch[1]-watch[0]>colorRange&&watch[1]-watch[2]>colorRange){
return 4;//绿
}else if(watch[0]-watch[1]>colorRange&&watch[0]-watch[2]>colorRange){
return 5;//蓝
}
//-------------以上通过差值进行判断-----------
// cvSetReal2D(thrImg, x, y, 255.0);
// cvShowImage("colorJudge", thrImg);
// cvSet2D(originImg,x,y, cvScalar(0, 255, 0, 0)); //绘制来查看检测点的位置
// cvShowImage("yanse", originImg);
if(flag[0]==0&&flag[1]==1&&flag[2]==1){
return 3;//黄
}else if(flag[0]==1&&flag[1]==0&&flag[2]==0){
return 5;//蓝
}else if(flag[0]==0&&flag[1]==1&&flag[2]==0){
return 4;//绿
}else if(flag[0]==0&&flag[1]==0&&flag[2]==1){
return 2;//红
}else if(flag[0]==0&&flag[1]==0&&flag[2]==0){
return 1;//黑
}
return 5; //都检测不出来返回蓝色
}

学习教程

  1. IplImage, CvMat, Mat 的关系和相互转换
  2. OpenCV函数cvFindContours
  3. 提取轮廓两种方法及绘制轮廓中最大等级分析
  4. OpenCV中寻找轮廓函数cvFindContours的使用说明以及序列cvSeq的用法说明
  5. RGB坐标像素的储存和提取
  6. opencv 连通区域边界坐标提取
  7. OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑
  8. 均值,中值,高斯滤波
  9. 检测矩形的参考&&检测圆,直线
  10. 霍夫圆变换函数HoughCircles
  11. OpenCV霍夫变换识别圆

  12. 使用Mat的opencv中的椭圆拟合

  13. OpenCV画轮廓的外界圆矩形椭圆等
  14. 利用cvMinAreaRect2求取轮廓最小外接矩形
  15. CvBox2D说明

后记

其实整个项目比较简单,但意外的参加这种比赛也算是长了自己的见识,在此也特别感谢为我们提供了帮助的老师。