基于颜色分割的盲道识别算法
前言
本任务的要求是通过相关算法识别出城市区域中的盲道,通过对各种盲道进行调研,发现目前城市中的盲道基本分为两大部分,一类是具有明显样色和纹理特征的盲道,通常是黄色(当然也有其他颜色),这类盲道最容易通过相关算法识别分割出来,如下图所示。
另一类是没有颜色特征只有纹理特征的盲道,这类盲道基本和普通的人行道没有什么颜色差异,唯一可以识别出它们的特征仅有它们的凸出纹理,如下图所示。
当然,还有很多不合标准以及被破坏的盲道,这类盲道丧失了其原本应有的功能,如下图所示。开发出可以识别这种盲道的算法显然毫无意义,也许将它们拆除或者重修才是我们唯一应该做的事情。
一、问题分析
基于颜色区域分割的盲道识别针对于那些具有明显颜色差异的盲道,由于这类盲道通常为黄色,因此需要通过颜色识别算法将图片中的黄色区域同背景分开。但是在实际情况中,图片中可能还存在其他的黄色背景,如行人的衣服、广告牌等,如下图所示,所以在分割的时候还需要将这些噪声去除。
因此基于颜色区域分割的盲道识别可以转化为颜色识别。数字图像中常采用的模型是RGB模型和HSV模型,RGB广泛应用于彩色监视器和彩色视频摄像机中。而HSV模型更符合人描述和解释颜色的方式,HSV的彩色描述对人来说是自然且非常直观的,HSV模型如下图所示。
HSV模型中颜色的参数分别为:色调(H:hue),饱和度(S:saturation),亮度(V:value)。
色调(H):用角度度量,取值范围为0~360度,从红色开始按逆时针方向计算,红色为0度,绿色为120度,蓝色为240度,它们的补色为:黄色为60度,青色为180度,品红为300度。
饱和度(S):取值范围为0.0~1.0,值越大,颜色越饱和。
亮度(V):取值范围为0(黑色)~255(白色)。
二、RGB模型转HSV模型
颜色识别在HSV色彩空间中实现,由于常规的图片为RGB模型,因此需要将图片转换到HSV空间。对图片中每一个像素执行下列的变换:
上述式子是RGB转HSV的原理,在MATLAB中实现的时候可以直接调用RGB转HSV函数:
%% =====================RGB转HSV===========================
rgb_data=imread('test.jpg');//读取RGB图片
hsv=rgb2hsv(rgb_data); //RGB转HSV
H=hsv(:,:,1)*360; //H范围为0~360
S=hsv(:,:,2)*255; //S范围为0~255
V=hsv(:,:,3)*255; //V范围为0~255
在MATLAB中,RGB转HSV后H、S、V三个参数作了归一化处理,即范围为0~1,因此需要乘以系数来扩大其范围。将图片转换为HSV之后,分别将H、S、V分量通过图形的方式显示出来,如下图所示。从图中可以看出,H分量虽然控制颜色的色度,但是对于白色不能很好的区分,因此还需要借助S和V分量共同进行颜色区分。
三、基于HSV色彩空间的颜色分割
基于HSV模型的颜色分割本质上为阈值分割,通过确定H、S、V三个分量的范围来判断某个像素为什么颜色。设
为图片中某一点像素,
为该点像素的
分量,
为该点像素
分量,
为该点像素
分量。
则当:
令:
否则:
对图片中所有像素执行上述操作,就可以完成基于HSV空间的颜色分割。该部分最关键的地方在于为了识别图片中黄色区域,需要确定每个分量的阈值范围。在本算法中取 。MATLAB程序如下所示:
%% ======================颜色分割===========================
for i = 1 : Height
for j = 1 : Length
if H(i,j) > 30 && H(i,j) < 60 && S(i,j) > 60 && S(i,j) < 255 && V(i,j) > 60 && V(i,j) < 255
FinishData(i,j) = 255;
else
FinishData(i,j) = 0;
end
end
end
对图片执行上述操作,得到结果如下图所示,从图中可以看到,黄色的盲道基本被分割出来,但是其中还是夹杂很多噪声,别急,接下才是好玩的地方^_^。
四、中值滤波和膨胀
为了解决颜色分割后出现的噪声,首先对分割后图像进行中值滤波,目的是去除面积较小的点状噪声,中值滤波的窗口采用11*11大小,中值滤波后结果如下图所示:
%% ==================== 中值滤波 =========================
FinishData = medfilt2(FinishData,[11,11]);
从图中可以看到,经过中值滤波之后很多噪声已经被滤除,但是盲道区域中还是有一些区域没有被分割出来,呈现孔洞状,为了将这些孔洞去除,将盲道变成一个完整的连通区域,采用膨胀的方式对中值滤波后结果进行处理。膨胀的大小采用15*15方块,处理结果如下图所示,从图中可以看到,经过膨胀操作后盲道内部的黑色区域已经被去除,处理后的盲道已经变成了一个完整的连通区域。
%% ========================膨胀==============================
se = strel('square',15);
FinishData = imdilate(FinishData,se);
五、区域生长
经过上述步骤之后,已经可以将盲道完整的分割出来,但是还有需要块状区域的噪声没有被去除,这些区域面积较大,常规的滤波手段无法起到效果。观察这些块状噪声和盲道有一个明显的区别,盲道的像素面积要比这些噪声的面积大的多,因此可以统计分割结果中所有白色连通区域的像素面积,然后设定一个阈值,当连通区域的像素面积小于这个阈值的时候,视为噪声,将其滤除掉,方法采用区域生长。
区域生长是一种串行区域分割的图像分割算法,它的优点是基本思想相对简单,通常能将具有相同特征的连通区域分割出来,并能提供很好的边界信息和分割结果。在没有先验知识可以利用时,可以取得最佳的性能,可以用来分割比较复杂的图像。区域生长是按照事先定义的生长规则将一个像素或者子区域逐渐合成一个完整独立的连通区域的过程。具体先对每个需要分割的区域找到一个种子像素作为生长的起点,然后将种子像素周围领域中与种子像素具有相同或相似性质的像素合并到种子像素所在的区域中,将这些新像素当做新的种子继续上面的过程,直到再没有满足条件的像素可被包括进来。
区域生长算法分为三个步骤实现:
(1)确定生长种子起点,如下图所示:
(2)规定生长规则,当周边像素和种子像素相差2两个灰度以内(这个值视情况而定),则合并到种子像素内,并继续往外扩展,如下图所示:
(3)确定生长停止条件,当所有的边缘像素都比种子像素大2个像素的时候,生长停止,分割完成,如下图所示:
程序实施步骤如下:
(1)对图像进行顺序扫描,找到第一个白色且没有归属的像素,设该点像素为 。
(2)以 为中心,考虑 8邻域像素 ,如果灰度为255,则将 标记为一个类合并到 中,同时将 压入堆栈。在这里还需要考虑当 位于图像边缘的时候,其邻域不能超过图像本身。
(3)从堆栈中取出一个像素,把它当做 返回到步骤2。
(4)当堆栈为空的时候返回步骤1,同时更新分类计数器。
(5)重复步骤1~4,直到图像中所有的白色区域像素都有归属时,算法结束。
ClassNum = 0; %分类计数器
for i = 1 : Height
for j = 1 : Length
if FinishData(i,j) == 0
FinishData(i,j) = 0;
else if FinishData(i,j) == 255
Stack(1,1) = i;
Stack(1,2) = j;
Index = 1; %栈指针
ClassNum = ClassNum + 1; %分类计数器加1
while(Index > 0)
x0 = Stack(Index,1);
y0 = Stack(Index,2);
Index = Index -1;
FinishData(x0,y0) = ClassNum;
if x0 == 1 && y0 == 1 %当x0,y0位于图像四个边角时
if FinishData(x0,y0+1) == 255 %x0,y0+1
FinishData(x0,y0+1) = ClassNum; %标记像素
Index = Index + 1;
Stack(Index,1) = x0; %入栈
Stack(Index,2) = y0+1; %入栈
end
if FinishData(x0+1,y0+1) == 255 %x0+1,y0+1
FinishData(x0+1,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0+1;
end
if FinishData(x0+1,y0) == 255 %x0+1,y0
FinishData(x0+1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0;
end
elseif x0 == 1 && y0 ~= 1 && y0 ~= Length %当x0,y0位于图像边缘时
if FinishData(x0,y0+1) == 255 %x0,y0+1
FinishData(x0,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0+1;
end
if FinishData(x0+1,y0+1) == 255 %x0+1,y0+1
FinishData(x0+1,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0+1;
end
if FinishData(x0+1,y0) == 255 %x0+1,y0
FinishData(x0+1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0;
end
if FinishData(x0,y0-1) == 255 %x0,y0-1
FinishData(x0,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0-1;
end
if FinishData(x0+1,y0-1) == 255 %x0+1,y0-1
FinishData(x0+1,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0-1;
end
elseif y0 == 1 && x0 ~= 1 && x0 ~= Height %当x0,y0位于图像边缘时
if FinishData(x0,y0+1) == 255 %x0,y0+1
FinishData(x0,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0+1;
end
if FinishData(x0+1,y0+1) == 255 %x0+1,y0+1
FinishData(x0+1,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0+1;
end
if FinishData(x0+1,y0) == 255 %x0+1,y0
FinishData(x0+1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0;
end
if FinishData(x0-1,y0+1) == 255 %x0-1,y0+1
FinishData(x0-1,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0+1;
end
if FinishData(x0-1,y0) == 255 %x0-1,y0
FinishData(x0-1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0;
end
elseif y0 == Length && x0 ~= 1 && x0 ~= Height %当x0,y0位于图像边缘时
if FinishData(x0+1,y0) == 255 %x0+1,y0
FinishData(x0+1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0;
end
if FinishData(x0-1,y0) == 255 %x0-1,y0
FinishData(x0-1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0;
end
if FinishData(x0-1,y0-1) == 255 %x0-1,y0-1
FinishData(x0-1,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0-1;
end
if FinishData(x0+1,y0-1) == 255 %x0+1,y0-1
FinishData(x0+1,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0-1;
end
if FinishData(x0,y0-1) == 255 %x0,y0-1
FinishData(x0,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0-1;
end
elseif x0 == Height && y0 ~= 1 && y0 ~= Length %当x0,y0位于图像边缘时
if FinishData(x0-1,y0+1) == 255 %x0-1,y0+1
FinishData(x0-1,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0+1;
end
if FinishData(x0-1,y0) == 255 %x0-1,y0
FinishData(x0-1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0;
end
if FinishData(x0-1,y0-1) == 255 %x0-1,y0-1
FinishData(x0-1,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0-1;
end
if FinishData(x0,y0-1) == 255 %x0,y0-1
FinishData(x0,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0-1;
end
if FinishData(x0,y0+1) == 255 %x0,y0+1
FinishData(x0,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0+1;
end
elseif x0 == 1 && y0 == Length %当x0,y0位于图像四个边角时
if FinishData(x0,y0-1) == 255 %x0,y0-1
FinishData(x0,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0-1;
end
if FinishData(x0+1,y0-1) == 255 %x0+1,y0-1
FinishData(x0+1,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0-1;
end
if FinishData(x0+1,y0) == 255 %x0+1,y0
FinishData(x0+1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0;
end
elseif x0 == Height && y0 == 1 %当x0,y0位于图像四个边角时
if FinishData(x0-1,y0) == 255 %x0-1,y0
FinishData(x0-1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0;
end
if FinishData(x0-1,y0+1) == 255 %x0-1,y0+1
FinishData(x0-1,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0+1;
end
if FinishData(x0,y0+1) == 255 %x0,y0+1
FinishData(x0,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0+1;
end
elseif x0 == Height && y0 == Length %当x0,y0位于图像四个边角时
if FinishData(x0-1,y0) == 255 %x0-1,y0
FinishData(x0-1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0;
end
if FinishData(x0-1,y0-1) == 255 %x0-1,y0-1
FinishData(x0-1,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0-1;
end
if FinishData(x0,y0-1) == 255 %x0,y0-1
FinishData(x0,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0-1;
end
else
if FinishData(x0,y0+1) == 255 %x0,y0+1
FinishData(x0,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0+1;
end
if FinishData(x0+1,y0+1) == 255 %x0+1,y0+1
FinishData(x0+1,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0+1;
end
if FinishData(x0+1,y0) == 255 %x0+1,y0
FinishData(x0+1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0;
end
if FinishData(x0,y0-1) == 255 %x0,y0-1
FinishData(x0,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0;
Stack(Index,2) = y0-1;
end
if FinishData(x0+1,y0-1) == 255 %x0+1,y0-1
FinishData(x0+1,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0+1;
Stack(Index,2) = y0-1;
end
if FinishData(x0-1,y0+1) == 255 %x0-1,y0+1
FinishData(x0-1,y0+1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0+1;
end
if FinishData(x0-1,y0) == 255 %x0-1,y0
FinishData(x0-1,y0) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0;
end
if FinishData(x0-1,y0-1) == 255 %x0-1,y0-1
FinishData(x0-1,y0-1) = ClassNum;
Index = Index + 1;
Stack(Index,1) = x0-1;
Stack(Index,2) = y0-1;
end
end
end
end
end
end
end
通过区域生长算法对膨胀后结果进行处理,可以将图片中所有的白色连通区域进行分类,通过不同的灰度显示,如下图所示:
统计所有连通区域的像素面积大小,如下表所示:
从表中可以看出,盲道的像素面积要远远大于其他的区域,最终选取区分阈值为6000,对所有连通区域的面积进行判断,如果面积小于6000则视为盲道以外的噪声,将其归为背景。处理后效果如下图所示,从图中来看,经过处理后已经完全可以将盲道分离出来,达到了预计的效果。
六、测试结果
为了验证算法的有效性,选用16张图片对算法进行测试,以此来查看算法的最终分割效果。如下图所示,从分割结果来看,算法基本可以完整的将图片中的盲道同背景分离开,达到一个比较好的效果。
七、算法运行时间
针对不同分辨率图像,算法的运行时间也不同,下表列出了各个分辨率下算法的运行时间。从表中可以看出,算法的执行时间范围为0.1120s~27.6410s,图片分辨率越高,算法执行时间越长,同时分辨率也不是影响时间的唯一因素,图片内容的不同也会影响算法的运行时间。
八、总结
基于颜色分割的盲道识别算法对于具有明显颜色特征的盲道具有比较好的分割效果,该算法通过区域生长排除了图片中其他黄色物体的噪声,可以完整的将盲道同背景分离开。但是为了达到一个比较好的效果,算法中运用了大量的迭代,导致算法的运行时间偏长,后期需要进一步优化算法的执行时间。