0%

Opencv-使用DFS确实区域是否杂色

前言

之前在做比赛的时候因为有实物和色块,而两者之间的最明显的区别就是颜色不一样。
而我们使用的是纯RGB进行颜色判断,理论上结合HSV来一起判断效果会更好,但是本方法也优化的还行了,HSV之前用的时候不是很会用。

在比赛中颜色只有5种:黑、红、黄、绿、蓝,差不多如下图吧。按照纯色给出的图片因为打印机和摄像头问题会有一些误差。
原图


前方法思路

本来检测颜色的方法使用的是给定一个阈值范围,因为RGB值{R,G,B}(0≤R,G,B≤255)比较明显。但是由于摄像头的色差,最后出来的结果比较不理想。


方法思路

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

中心点


效果展示

物体的对应编号如下

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]:第一个为颜色,第二个为形状

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

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


详细代码

直接上代码了,thrImg是一个全局变量,用来拿到图的尺寸。这段代码是判断某个轮廓是否是杂色。

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; //都检测不出来返回蓝色
}

后记

dfs这个方法有点像区域生长吧,不过没写过后者,之前在群友的建议下使用的这个方法,效果挺好。后面的RGB差值也要感谢大佬的建议了。单纯范围判断效果不好。

如果对您有帮助,请我喝杯咖啡?