当完成一个视觉项目时,被使用的摄像头所拍出来的图像有时候拍摄方向反了或者有时候会有所变形,对于变形,一般的相机会显示出一个凸镜的效果,这样变形的图像在进行特征分析时会带来很大的麻烦,变成一个复杂的图像分析。但幸运的是,人们可以对图像进行一定的修正,如固定旋转、偏移修正、斜偏修正操作来还原图像。
一般情况下,摄像头拍摄的图像和真实的图像会有出入,其主要由环境光线的影响和拍摄的设备性能所决定。为了最大限度地提高图像的质量,严格地说是为了让用户感兴趣的特征点能更好的表现出来,用户会用到图像增强处理,把不感兴趣的区域过滤掉。这里主要介绍图像的二值化处理及腐蚀和膨胀算法。
4.4.4.1 旋转
最简单的图像变换为旋转图像,将捕捉到的视频图像还原成正确的、方向便于观察的图像。这是通过rotate()函数成功变换的,该函数只有一个传递参数,就是角度(单位为度,用来旋转图像)。如果对角度赋负数值,则图像会顺时针旋转;如果对角度赋正数值,则图像会逆时针旋转。以下程序使图像逆时针旋转45°:
from Simple CV import Image
img = Image('jacopo.png')
# Rotate the image counter-clockwise 45 degrees
rot = img.rotate(45)
rot.show()
运行程序产生的旋转图像如图4.4.5所示。
图4.4.5 图像逆时针旋转45°
在默认的情况下,程序都是以图片的中心位置来旋转的。如果要改变旋转的轴位置,那么可以通过point 赋一个值来定一个点,此参数是以(x,y)坐标形式旋转的新点。
from Simple CV import Image
img = Image('jacopo.png')
# Rotate the image around the coordinates +(16, 16)+
rot = img.rotate(45, point=(16, 16))
rot.show()
旋转图像如图4.4.6所示。注意在旋转时该图像被裁剪了一大块,即图片信息有所丢失。
图4.4.6 图片信息丢失
请注意,当图像旋转时,如果超出原始图像尺寸,则该图像部分会被裁剪掉。rotate()函数中有一个名为“fixed”的参数,当fixed被设置为“false”时,该算法将返回一个已调整大小的图像(见图4.4.7):
img = Image('jacopo.png')
# Rotate the image and then resize it so the content isn't cropped
rot = img.rotate(45, fixed=False)
rot.show()
图4.4.7 图像逆时针旋转45°不会丢失图像数据
4.4.4.2 翻转图像
类似于旋转图像,图像还可以被水平或者垂直翻转。可通过flip Horizontal()和flip Vertical()函数的功能,水平和垂直翻转图像:
from Simple CV import Image
img = Image('jacopo.png')
# Flip the image along the horizontal axis and then display the results
flip = img.flip Horizontal()
flip.show()
下面的示例将水平翻转图像(见图4.4.8),这个程序的功能就相当于一面镜子,使得这个图像变为镜像了。
图4.4.8 图像镜像
4.4.4.3 切变和扭曲
整幅图像歪斜或图像中的部分歪斜,会使图形形成另一幅图像。一个常见的例子是,当以一个角度去观察两个物体时发现它们基本上某一部分重叠。当以另一个角度去观察这两个物体时发现不是这样的结果,为了使在某一个角度上的图像适应当前的角度空间,它的边缘必须被调整。在视觉系统内部,它是执行仿射变换技术,人们通常称之为“切变”。
人们使用切变方法来修正比萨斜塔(见图4.4.9),可以显示两种图片来做对比:
from Simple CV import Image
img = Image('pisa.png')
corners = [(0, 0), (450, 0), (500, 600), (50, 600)]
straight = img.shear(corners)
straight.show()
图4.4.9 修正比萨斜塔
除了切变以外,也可以使图像扭曲,这时采用warp ()函数。扭曲也需要一个角点的数组作为参数。类似于切变,它是用来拉伸图像并将其适应到一个矩形空间内。例如,可以将用相机拍摄的图像映射到电视机上,由于相机的图片尺寸、表示方式与电视机有区别,所以需要进行扭曲转换,如图4.4.10所示。
from Simple CV import Camera, Image, Display
tv_original = Image("family.png")
tv_coordinates = [(285, 311), (367, 311), (368, 378), (286, 376)]
tv_mask = Image(tv_original.size()).invert().warp(tv_coordinates)
tv = tv_original - tv_mask
cam = Camera()
disp = Display(tv.size())
# While the window is open, keep updating updating
# the TV with images from the camera
while disp.is Not Done():
bwimage = cam.get Image().grayscale().resize(tv.width, tv.height)
on_tv = tv + bwimage.warp(tv_coordinates)
on_tv.save(disp)
图4.4.10 相机图片映射到TV上所显示的扭曲变换
4.4.4.4 二值化
二值化图像是指进行了二值化处理的图像,即图像中只有黑白两色,而不是灰度图或彩色图。在SimpleCV中可以使用binarize()函数进行二值化处理:
from Simple CV import Image
img = Image('jacopo.png')
img Bin = img.binarize()
img Bin.show()
通过上面的程序变换之后,图片就变成了二值化图片,只有黑色和白色(没有灰度),如图4.4.11所示。
图4.4.11 二值化图片
在使用二值化binarize函数时,也可以让系统知道哪些像素能转换为白色,哪些像素能转换为黑色,这就是所谓“阈值”。任何像素其值在阈值以下的都会被转换为白色,在阈值以上的会被转换为黑色,在默认情况下,SimpleCV框架使用一种叫作Otsu’s的方法来动态地确定阈值。然而binarize()函数可以输入0~255来赋值,下面演示一下使用多个阈值来处理一个图片所得到的效果,如图4.4.12所示。
from Simple CV import Image
img = Image('trees.png')
# Using Otsu's method
otsu = img.binarize()
# Specify a low value
low = img.binarize(75)
# Specify a high value
high = img.binarize(125)
img = img.resize(img.width*.5, img.height*.5)
otsu = otsu.resize(otsu.width*.5, otsu.height*.5)
low = low.resize(low.width*.5, low.height*.5)
high = high.resize(high.width*.5, high.height*.5)
top = img.side By Side(otsu)
bottom = low.side By Side(high)
combined = top.side By Side(bottom, side="bottom")
combined.show()
图4.4.12 用多个阀值处理的图片效果
图4.4.12中左上角为原始图片,左下角为低阈值处理,右上角为用默认的Otsu’s方法处理,右下角为高阈值处理的二值化图片。
4.4.4.5 腐蚀和膨胀
一旦图像被转换为二值化图像,基本上就有四种常用的形态算法:膨胀、腐蚀、Opening和Closing。膨胀与腐蚀的概念相似,膨胀算法的对象的像素为白色,那么和白色相邻区域的黑色像素会被转换为白色像素,使得白色区域变大,扩张了白色像素;腐蚀则恰恰相反,黑色区域会吞噬相邻的白色像素,使得白色区域变窄。
考虑同时有工具和钉板(有许多钉子孔的木板)的情况,在钉板上许多小孔可以混淆工具的特征检测算法。形态学的算法可以帮助突出工具的特征,减少干扰。第一个示例演示了膨胀算法的使用,在这个例子中请注意,二值化处理后,一些工具的部分特征已消失,因为光源不一致,有些地方反光,特征不明显。可以试图让这些不明显的特征被修复,在这里先用膨胀算法来填补一些缺少部分:
from Simple CV import Image
img = Image('pegboard.png')
# Binarize the image so that it's a black and white image
img Bin = img.binarize()
# Show the effects of dilate() on the image
img Bin.dilate().show()
图4.4.13所示为运行了该程序后的效果图,从左到右,第一幅为原图,第二幅为默认情况下二值化的图,第三幅为通过了膨胀算法的效果图。可以看出来通过使用二值化,运行了膨胀算法后虽然工具的特征部位得到了修复,但是其也扩大了钉板上面的钉子孔的干扰。
图4.4.13 膨胀算法后的图片效果
from Simple CV import Image
img = Image('pegboard.png')
img Bin = img.binarize()
# Like the previous example, but erode()
img Bin.erode().show()
如图4.4.14所示,这个效果图和图4.4.13所示的效果图具有相反的效果,处理过后的图像更加破坏了工具的特征,如锯片基本上不明显了。但是从另一方面来说,它消除了大部分钉板上面的孔。
图4.4.14 腐蚀算法处理后的图像
dilate()函数可以帮助填补工具特征,但是也放大了一些噪点(钉板上面的孔)。与此相反, erode()函数消除了许多噪点,但是很多不错的工具特征也被掩盖了。解决的办法是把这两个功能结合在一起。事实上,结合使用是非常普遍的现象,它们有自己的命名函数:morphOpen()和morphClose()。而morphOpen()函数是先腐蚀,然后膨胀该图像。在腐蚀过程中消除了部分噪点,通过膨胀或多或少地恢复工具原有的特征,但是肯定没有在执行腐蚀算法的时候质量好,可以看出来这个算法具有去除噪点的功能。与此相反,morphClose()首先运行膨胀算法,然后运行腐蚀算法,从而首先扩张了工具上面的微细特征值(如果这些微细的特征像素比较少,那么会完全被白色像素给吞噬了),然后执行腐蚀算法,但也不能把这些微细的特征修复,这个算法具有把微细的特征值给掩盖的功能,也就是平滑化功能。两种功能结合使用的目的是减少图像中的噪点量。
from Simple CV import Image
img = Image('pegboard.png')
img Bin = img.binarize()
# +morph Open()+ erodes and then dilates the image
img Bin.morph Open().show()
如图4.4.15所示,采用morphOpen()算法处理图像,先腐蚀减少噪点,然后膨胀突出特征。
图4.4.15 先腐蚀再膨胀算法的效果图
4.4.4.6 举例
本小节中的案例既有趣,又有应用价值。从有趣的角度来说,它显示了如何做一个旋转效果的摄像头,采用了rotate()函数;从实用性的角度来说,它展示了如何切变成在一定的视角中观察物体,然后用校正后的图像进行基本的测量。
1. 例程一:The SpinCam
这是一个非常简单的脚本,它不断地旋转摄像机的输出,还不断地从相机中捕获图像,也逐渐递增旋转的角度,使它看起来好像视频输入在打转一样。
from Simple CV import Camera
cam = Camera()
display = Display()
# This variable saves the last rotation, and is used
# in the while loop to increment the rotation
rotate = 0
while display.is Not Done():
rotate = rotate + 5
cam.get Image().rotate(rotate).save(display)
2. 例程二:扭曲和测量
目标物体的测量是视觉技术一个重要的环节,但这个例子提供了一个最简单的实例,其基本思路是要和一个已知尺寸的对象做对比来进行物体的测量。例如,如果一个物体对象被放在一个A4纸的上面,该物体的相对大小可被用于尺寸计算。但是,如果纸张在相机中成像不是一个长方形的,那么问题就变得较为复杂了。这个例子展示了如何使用warp()函数解决这个问题。图4.4.16显示的图像是在一张A4纸上测量一个小积木块的大小,图4.4.17和图4.4.18所示分别为通过扭曲修正的A4纸张和去除红色背景后得到的图像。
图4.4.16 纸张上面的小积木块
from Simple CV import Image
img = Image('skew.png')
# Warp the picture to straighten the paper
corners = [(0, 0), (480, 0), (336, 237), (147, 237)]
warped = img.warp(corners)
# Find the blob that represents the paper
bgcolor = warped.get Pixel(240, 115)
dist = warped.color Distance(bgcolor)
blobs = dist.invert().find Blobs()
paper = blobs[-1].crop()
# Find the blob that represents the toy
toy Blobs = paper.invert().find Blobs()
toy = toy Blobs[-1].crop()
# Use the toy block/paper ratio to compute the size
paper Size = paper.width
toy Size = toy.width
print float(toy Size) / float(paper Size) * 8.5
图4.4.17 通过扭曲修正的A4 纸张
图4.4.18 去除红色背景后得到的图像
运行这个程序,就可以知道小积木块的大小尺寸了。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。