形状识别基本上有基于色彩特征的识别技术、基于纹理特征的识别技术以及基于形状特征的识别技术这几大类。
4.4.8.1 边缘直线和圆形物体
1. 边缘直线
线性特征是指一幅图像中的线性边缘,通常表示一个目标的边界。边缘是关于(x,y)坐标系的一个列表,且任意两坐标都可以被一条直线所连接。举例来说,图4.4.33表示了4个坐标点,以及用直线连接这些坐标点的两种不同的方式。很难说哪种是对的(或者它们都对),虽然也有其他可能的解决方式。解决这一类问题的一种幕后方式是使用Hough转换技术。这项技术可以有效地着重于连接这些点的可能直线并描绘出最可能的线。
图4.4.33 示例
(a)4个坐标点;(b)一种可能的连接4个点的线;(c)一种二选一的方案
线是最常见的也是最接近的一种实际特征。
若要在图像中找到线特征,可以使用findLines()函数。这个函数使用Hough转换并把找到的线特征放在FeatureSet参数中返回。这个函数在不关心返回的特征所包含的类型时很有用。然而,在处理线特征的时候,有一些FeatureSet函数可能用得上,以下例举这些函数:
(1)coorDinates():
返回线的初始点坐标。
(2)Width():
返回线的宽度,也就是线在x坐标中起始和终止位置的差值。
(3)Height():
返回线的高度,也就是线在y坐标中起始和终止位置的差值。
(4)Length():
返回线在像素中的长度。
以下代码示范如何在一幅图像中找到并显示那些线条,这个例子演示如何在一块木头中找线:
from Simple CV import Image
img = Image("block.png")
lines = img.find Lines() ①
lines.draw(width=3) ②
img.show()
① findLines()函数在FeatureSet参数中返回线特征。
② 在图像中会以绿色、3像素的宽度画出这些线。
在图像中找到的线会在图4.4.34中显示出来。
图4.4.34 在木头上检测边缘线
由于这只是一个简单的在采光良好环境下的木头图像的例子,findLines()函数只是使用了其默认的参数值就找出了合理的线特征。在大多数情况下,需要整理一下findLines()函数才能得到想要的结果。就例子来说,其找到了自上而下的线特征,却没有找到两侧的线特征。
findLines()包括5个不同的调整参数,它们是用来帮助用户改善返回结果的质量的。它们分别是:
(1)Threshold:
这里表示要对比多强烈的边缘才会被识别为线特征。
(2)Minlinelength:
此参数表示要识别成线特征的最短线长度。
(3)Maxlinegap:
此参数表示线特征间可以允许的最大间隙差值。
(4)Cannyth1:
这个参数用于边缘探测的开始阶段,这个参数决定边缘对比度的最小值。
(5)Cannyth2:
这是边缘探测的第二个参数,用于设定“边缘持续度”。
findLines()的初始阈值参数与findBlobs()函数的参数差不多。如果用户没有给定这些参数,那么这些值的参数会被设定为默认的“80”。如果阈值被设定得更低,那么它会找出更多的线特征;反之亦然。下面的代码调低地给定了阈值:
from Simple CV import Image
img = Image("block.png")
# Set a low threshold
lines = img.find Lines(threshold=10)
lines.draw(width=3)
img.show()
图4.4.35所示为在图像中找到了更多的线特征。注意,其找到了一侧的线特征,但是也在木板中间找到了几条多余的线特征。
图4.4.35 在低阈值下的线特征探测
(注意:如果findLines()的参数值被设置得过高,那么将检测不到任何线特征。)
一种去除干扰线的方法是设置“minlinelength”参数来提出过短的线。以像素为准测量线段的长度,这个参数的默认值是30像素长。下面的例程增加最小线段的长度以剔除一些线段:
from Simple CV import Image
img = Image("block.png")
lines = img.find Lines(threshold=10, minlinelength=50)
lines.draw(width=3)
img.show()
其结果在图4.4.36中显示出来。注意其已经剔除了一堆额外的线,但是也同样剔除了两侧的线特征。
改变线的长度不能解决这个问题,图片中木头的两个末端还是没有找到。事实上,这可能提出一个对立的问题,有时算法可以识别出很细小的线段,但是必须判断这些细小线段是否是由一个大的连续线段中分离出来的。
图4.4.36 增加最小允许线长后的线探测结果
findLines()函数可以忽略线中一些小的间隔,并将这些线连成一整条识别。如果两条线段的间距小于10个像素,它们就会被合并。使用“Maxlinegap”参数可以更好地调节函数工作。以下例程将允许更大的线段间隙,这样可能在建立边缘的时候发现一些细小的线段:
from Simple CV import Image
img = Image("block.png")
lines = img.find Lines(threshold=10, maxlinegap=20)
lines.draw(width=3)
img.show()
返回的结果说明它又一次找到了右侧的边缘,但也又一次找到了一堆多余的线特征,如图4.4.37所示。
图4.4.37 允许大的线段间隙后的线探测结果
当设置最小线长后,找到的线段数量减少了,而增加线段允许的间隙后,找到的线的数量显著增加了。在更大的线间距下,分割开来的线段可以联立组成需要的线段长度,并且会识别出更多的线特征。最后两个参数“Cannyth1”和“Cannyth2”作为Canny边缘检测算法的阈值。大体上说,图像上亮度的剧烈改变被探测为边缘,第一个参数代表亮度变化多剧烈才会被识别为边缘,第二个参数控制把复数的边缘连接到一起。这两个参数和前面所讲的 3个参数扮演着同样的角色,过小的值代表会探测出更多的线特征,这可能只是徒增噪声而已;反之亦然,但是也可能代表有价值的信息没有被检测出来。对于所有的这些参数,应不停地调整它们,直到在此应用中能探测出所有用户需要的信息。
当然,有时候简单地修改图片就能比调整参数更加简单地去除检测出的噪声。下面的例程在那块木头中找到了需要的线特征:
from Simple CV import Image
img = Image('block.png')
dist = img.color Distance((150, 90, 50))
bin = dist.binarize(70).morph Close()
lines = bin.find Lines(threshold=10, minlinelength=15)
lines.draw(width=3)
# Move the lines drawn on the binary image back to the main image
img.add Drawing Layer(bin.dl())
img.show()
对图像进行二值化处理,这剔除了不少不应该出现的噪声,结果如图4.4.38所示。
图4.4.38 木头的所有线边缘
2. 圆形物体
检测圆特征的方法是使用findCircle()函数,它与findLines()函数相似,同样把找到的圆特征返回在FeatureSet函数中,也同样有参数来调整其灵敏性。这些参数包括:
(1)Canny:
这是Canny边缘提取的一个阈值参数,默认是“100”,如果设置的值过低,会找到大量的圆特征;反之亦然。
(2)Thresh:
这个参数等价于findLines()函数中的“threshold”参数,代表了多强的边缘变化会被识别为圆,默认值是“350”。
(3)Distance:
它与findLines()函数中的“maxlinegap”参数相似,决定一个圆需要多封闭才会被视为一个圆,如果用户未定义,系统会在分析出来的图像中尝试找一个最佳的值。
也和findLines()函数一样,有一些FeatureSet函数更加适合处理圆特征,这些函数包括:
(1)Radius():
圆的半径。
(2)Diameter():
圆的直径。
(3)Perimeter():
返回特征圆的周长,可能有时候它不叫作圆周,但是周长在处理圆度不太好的特征时更加直观。这里使用的周长是一个标准化的命名转换。
下面举例演示findCircle()函数的使用,拿一张咖啡杯的图片,其中一个咖啡杯里还放着一个乒乓球。下面给出例程:
from Simple CV import Image
img = Image("pong.png")
circles = img.find Circle(canny=200,thresh=250,distance=15)①
circles = circles.sort Area()②
circles.draw(width=4)③
circles[0].draw(color=Color.RED, width=4)④
img_with_circles = img.apply Layers()⑤
edges_in_image = img.edges(t2=200)⑥
final = img.side By Side(edges_in_image.side By Side(img_with_circles)).scale(0.5)⑦
final.show()
① 找到图中的圆特征。测试这些参数值时,只需注意那些感兴趣的圆特征。
② sortArea()函数用来分类在大的圆中找到的包含圆,这样可以让用户识别在杯子中的乒乓球。当然也可以把这一步和前一步联合起来,那么代码会变成:“circles = img.findCircle (canny=200,thresh=250,distance=15).sort Area().”,现在只是把步骤分开来处理,以便于理解。
③ 这里讲识别出来的圆用绿色的线画出来,由于默认的线难以看清,所以这里把线宽加到4个像素。
④ 用红色的线画出包含圆,也就是那个乒乓球的轮廓。
⑤ draw()函数用来在原图像上增加一个画图层,用来绘画不同的圆。
⑥ 为了演示findCircle()函数是怎样工作的,使用edges()函数把图像中所有要找的边缘显示出来,为了使找出来的边缘一致,在edges()函数中使用相同的阈值——“200”。对于edges()函数,阈值参数为“t2”,其实就是find Circle()函数里的“Canny”参数。
⑦ 最后把这些图层叠起来形成一幅图像显示出来,如图4.4.39所示。
图4.4.39 圆特征的提取结果
下面的例子同时包含了寻找线和圆的特征,在一个转盘中读取线。使用图4.4.40中的4种不同设置的转盘,首先寻找图中的转盘,然后再寻找每个转盘中的线,最后测量线的角度,并将其在图片中显示出来:
from Simple CV import Image
# Load the images of four dials with different settings
img0 = Image("dial1.png")
img1 = Image("dial2.png")
img2 = Image("dial3.png")
img3 = Image("dial4.png")
# Store them in an array of images
images = (img0,img1,img2,img3)
# This stores the dial-only part of the images
dials = []
for img in images:
circles = img.find Circle().sort Area()①
dial = circles[-1].crop()②
lines = dial.find Lines(threshold=40,cannyth1=270,cannyth2=400)③
lines = lines.sort Length()④
lines[-1].draw(color=Color.RED)
line Angle = lines[-1].angle()⑤
if (lines[-1].x 〈 (dial.width / 2)):⑥
if (lines[-1].y 〈 (dial.width / 2)):
line Angle = line Angle - 180
else:
line Angle = 180 + line Angle
dial.draw Text(str(line Angle),10,10)
dial = dial.apply Layers()
dials.append(dial)
result = dials[0].side By Side(dials[1].side By Side(dials[2].side By Side(dials[3])))⑦
result.show()
① 第一步使用findCircle()函数在图片中找到这些转盘。sortArea()函数将圆分类,由于转盘是最大的圆,因此知道它会是circles[-1],也就是列表中的最后一个圆。
图4.4.40 一个温度调节装置上的转盘
② 调用crop()函数在特征中分割图像以调节圆的区域,并在dial变量中储存分割出来图像。
③ 调用findLines()函数来分割图像(储存在dial变量中)。
④ sortLength()函数对线特征及其长度进行分类。由于转盘的指示线是图像中最长的线,所以很容易被识别出来。
⑤ angle()函数计算线的夹角,也就是线与水平轴之间的夹角。
⑥ 角度的计算在转盘在右侧的时候正确,在左侧的时候不正确。这是由于角度的计算是从线段最左侧的点开始,而非转盘的中心。这一段代码对指示线在左端的情况进行了补偿,并在显示的时候加上或减去了180°,作为在转盘中心为基准计算的替代。
⑦ 在对所有转盘进行循环计算后,创建一个单独的图像,将结果一个一个地显示出来,如图4.4.41所示。
图4.4.41 分析的结果
4.4.8.2 检测角点特征
粗略地讲,棱角在图像中两条线相交的地方。举例来说,当分析一个正方形的时候,一条垂直的线通常也意味着正方形的左端或右端。同样的,水平线也被认为是正方形的顶端或底端。另一方面来说,每一个棱角都是特殊的。比如说,左上角不可能被错误地看右下角,反之亦然。这使得棱角可以帮助人们去识别特征中一些比较特殊的部分。
findCorners()函数用来分析图像并返回它所有能够找到的棱角的位置。需要注意的是,所谓棱角并不一定要有一个刚好 90°的夹角。两根线可以以任意角度组成一个棱角。如同findLines()和findCircle()一样,findCorners()函数同样将找到的所有棱角返回在FeatureSet函数中。然而,棱角的FeatureSet和其他函数的FeatureSet不太一样,有一部分函数在棱角的FeatureSet中没有意义。比如,尝试找到棱角的宽度、长度或者棱角的区域。从学术上来说,这些函数使用起来还是会有返回值,但是它们返回的值通常是默认值而非棱角的真实数据。
也同findLines()和findCircle()一样,findCorners()函数同样也有参数来帮助算法更好地找到图像中的棱角。下面用一张支架图作为例子,从而说明有用的参数:
from Simple CV import Image
img = Image('corners.png')
img.find Corners.show()
一些小的绿色的圆在图4.4.42中作为棱角被描绘出来了,注意在例子中函数找到了过多的棱角,最少也有50个,其中一些很明显是不该被检测出来的点。基于视觉的检测,应该有4个主要的棱角。为了限制棱角数量的返回值,可以使用“maxnum”参数:
图4.4.42 在零件中找到的棱角
from Simple CV import Image
img = Image('corners.png')
img.find Corners.(maxnum=9).show()
findCorners()方法在返回结果前会对找到的结果进行归类,所以在限制了最大返回棱角的数量后,它只会返回最优点(见图4.4.43)。同样的,“minquality”参数设置了返回棱角的最小质量。
图4.4.43 限制最大返回棱角数量为9个后的结果
算法在其中一个位置找到了两个棱角,这两个棱角其实是同一个地方,这是因为光照或颜色的因素干扰了算法。为了防止相近的棱角被识别为两个单独的棱角,可以设置“mindistance”参数,这个参数限制了两个棱角间相隔的最小像素点的个数。
from Simple CV import Image
img = Image('corners.png')
img.find Corners.(maxnum=9, mindistance=10).show()
限制了最小距离后的结果如图4.4.44所示。
图4.4.44 设置棱角间距最少在10个像素以上后的结果
4.4.8.3 举例
在下面的例子中,拿一张硬币的照片并计算硬币的总价值。为了实现这个目的,用斑点区域来表示每个硬币,然后比对每个区域的直径来判断这些硬币的价值。照片如图4.4.45所示。
图4.4.45 硬币
from Simple CV import Image, Blob
import numpy as np
img = Image("coins.png")
coins = img.invert().find Blobs(minsize = 500)
value = 0.0
# The value of the coins in order of their size
# http://www.usmint.gov/about_the_mint/?action=coin_specifications
coin_diameter_values = np.array([
[ 19.05, 0.10],
[ 21.21, 0.01],
[ 17.91, 0.05],
[ 24.26, 0.25]]);
# Use a quarter to calibrate (in this example we must have one)
px2mm = coin_diameter_values[3,0] / max([c.radius()*2 for c in coins])
for c in coins:
diameter_in_mm = c.radius() * 2 * px2mm
distance = np.abs(diameter_in_mm - coin_diameter_values[:,0])
index = np.where(distance == np.min(distance))[0][0]
value += coin_diameter_values[index, 1]
print "The total value of the coins is $", value
当使用那张图片执行完这段代码后,图中硬币的总价值应该是0.91美分。在此例中,基础的特征探测很直观清楚,但是本书花了大量的时间来分析从图像中得到的数据。在接下来的内容中,本书会更加深入地讨论这些方法。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。