一、使用OpenCV处理图像
1.不同颜色空间的转换
OpenCV中有数百种关于在不同色彩空间之间转换的方法。当前,在计算机视觉中有三种常用的色彩空间:灰度、BGR以及HSV(Hue, Saturation, Value)
灰度色彩空间是通过去除彩色信息来将其转换为灰阶,灰度色彩空间对中间处理特别有效,比如人脸检测。
BGR,即蓝-绿-红色彩空间,每一个像素点都由一个三元数组来表示,分别代表蓝、绿、红三种颜色。
HSV,H(Hue)是色度,S(Saturation)是饱和度,V(value)表示黑暗的程度(或光谱另一端的明亮程度)。
BGR的简短说明:当第一次处理BGR色彩空间时,可以不要其中的一个色彩分量,比如像素值[0, 255, 255](没有蓝色,绿色分量取最大值,红色分量取最大值)表示黄色。
2. 傅里叶变换
NumPy有快速傅里叶变换(FFT)的包,它包含了fft2()函数,该函数可以计算一幅图像的离散傅里叶变换(DFT)。
下面通过傅里叶变换来介绍图像的幅度谱。图像的幅度谱是另一种图像,幅度谱图像呈现了原始图像在变换方面的一种表示:把一幅图像中最明亮的像素放到图像中央,然后逐渐变暗,在边缘上的像素最暗。这样可以发现图像中有多少亮的像素和暗的像素,以及它们分布的百分比。傅里叶变换的概念是边缘检测或线段和形状检测等图像处理操作的基础。
2.1 高通滤波器
高通滤波器(HPF)是检测图像的某个区域,然后根据像素与周围像素的亮度差值来提升(boost)该像素的亮度的滤波器。
核是指一组权重的集合,它会应用在源图像的一个区域,并由此生成目标图像的一个像素。比如,大小为7的核意味着每49(7 x 7)个源图像的像素会产生目标图像的一个像素。可把核看作一块覆盖在源图像上可移动的毛玻璃片,玻璃片覆盖区域的光线会按某种方式进行扩散混合后透过去。以如下的核(kernal)为例:
[[0, -0.25, 0],
[-0.25, 1, -0.25],
[0, -0.25, 0]]
在计算完中央像素与周围邻近像素的亮度差值之和以后,如果亮度变化很大,中央像素的亮度会增加(反之则不会)。换句话说,如果一个像素比它周围的像素更突出,就会提升它的亮度。这在边缘检测上尤其有效,它会采用一种称为高频提升滤波器(high boost filter)的高通滤波器。
高通和低通滤波器都有一个称为半径(radius)的属性,它决定了多大面积的邻近像素参与滤波计算。下面是一个高通滤波器的例子。
import cv2
import numpy as np
from scipy import ndimage
kernal_3x3 = np.array([[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]])
kernal_5x5 = np.array([[-1, -1, -1, -1, -1],
[-1, 1, 2, 1, -1],
[-1, 2, 4, 2, -1],
[-1, 1, 2, 1, -1],
[-1, -1, -1, -1,-1]])
# 使用函数cv2.imread() 读入图像。这幅图像应该在此程序的工作路径,或者给函数提供完整路径,第二个参数是要告诉函数应该如何读取这幅图片。
# • cv2.IMREAD_COLOR:读入一副彩色图像。图像的透明度会被忽略,这是默认参数。
# • cv2.IMREAD_GRAYSCALE:以灰度模式读入图像
img = cv2.imread('LENA256.bmp',0) # 注:此处后面要加上0,表示已灰度模式读入图像
k3 = ndimage.convolve(img, kernal_3x3) # 注:使用ndimage.convolve()时,滤波核的维度应与原始图像的维度相同,故此采用灰度图
k5 = ndimage.convolve(img, kernal_5x5)
blurred = cv2.GaussianBlur(img, (11, 11), 0)
g_hpf = img - blurred
cv2.imshow("image", img)
cv2.imshow("3x3", k3)
cv2.imshow("5x5", k5)
cv2.imshow("g_hpf", g_hpf)
cv2.waitKey()
cv2.destroyAllWindows()
注:这些滤波器中的所有值加起来为0.
运行结果为:
导入模块之后,我们定义一个3x3和一个5x5的核,然后将读入的图像转换为灰度格式。通常大多数的图像处理会用NumPy来完成,但是这里的情况比较特殊,因为需要用一个给定的核与图像进行“卷积”(convolve),但是NumPy碰巧只接受一维数组。ndimage的convolve()函数支持经典的NumPy数组,cv2模块用这种数组来存储图像。
还有一种方法可实现高通滤波器:通过对图像应用低通滤波器之后,与原始图像计算差值。
2.2 低通滤波器
高通滤波器是根据像素与邻近像素的亮度差值来提升该像素的亮度。低通滤波器(Low Pass Filter, LPF)则是在像素与周围像素的亮度差值小于一个特定值时,平滑该像素的亮度。它主要用于去噪和模糊化,比如说,高斯模糊是最常用的模糊滤波器(平滑滤波器)之一,它是一个削弱高频信号信息强度的低通滤波器。
3. 边缘检测
OpenCV提供了许多边缘检测滤波函数,包括Laplacian()、Sobel()以及Scharr()。这些滤波函数都会将非边缘区域转为黑色,将边缘区域转为白色或其他饱和的颜色。但是,这些函数都很容易将噪声错误地识别为边缘。缓解这个问题的方法是在找到边缘之前对图像进行模糊处理。OpenCV也提供了许多模糊滤波函数,包括blur()(简单的算术平均)、medianBlur()以及GaussianBlur()。边缘检测滤波函数和模糊滤波函数的参数有很多,但总会有一个ksize参数,它是一个奇数,表示滤波器的宽和高(以像素为单位)。
这里使用medianBlur()作为模糊函数,它对去除数字化的视频噪声非常有效,特别是去除彩色图像的噪声;使用Laplacian()作为边缘检测函数,它会产生明显的边缘线条,灰度图像更是如此。在使用medianBlur()函数之后,将要使用Laplacian()函数之前,需要将图像从BGR色彩空间转为灰度色彩空间。
在得到Laplacian()函数的结果之后,需要将其转换成黑色边缘和白色背景的图像。然后将其归一化(使它的像素值在0到1之间),并乘以源图像以便能将边缘变黑。
注意,核的大小可由strokeEdges()函数的参数来指定。blurKsize参数会 作为medianBlur()含糊的ksize参数,edgeKsize参数会作为Laplacian()函数的ksize参数。对于作者的摄像头,将blurKsize值设为7,将edgeKsize值设为5会得到最好的效果。但对于较大的ksize(比如7),使用medianBlur()的代价很高。如果在使用strokeEdges()函数时遇到性能问题,可试着减小blurKsize的值。要关闭模糊效果,可以将blurKsize的值设为3以下。
4. 用定制内核做卷积
OpenCV预定义的许多滤波器(滤波函数)都会使用核。其实核是一组权重,它决定如何通过邻近像素点来计算新的像素点。核也称为卷积矩阵,它对一个区域的像素做调和(mix up)或卷积运算。通常基于核的滤波器(滤波函数)被称为卷积滤波器(滤波函数)。
OpenCV提供了一个非常通用的filter2D()函数,它运用由用户指定的任意核或卷积矩阵。卷积矩阵是一个二维数组,有奇数行、奇数列,中心的元素对应于感兴趣的像素。其他的元素对应于这个像素周围的邻近像素,每个元素都有一个整数或浮点数的值,这些值就是应用在像素值上的权重。如:
kernel = numpy.array([[-1, -1 , -1], [-1, 9, -1], [-1, -1, -1]])
其中感兴趣的像素权重为9,其邻近像素权重为-1。对感兴趣的像素来说,新的像素值使用当前像素值乘以9,然后减去8个邻近像素值。如果感兴趣的像素已经与其邻近像素有一点差别,那么这个差别会增加。这样会让图像锐化,因为该像素的值与邻近像素值之间的差距拉大了。注意权重加起来为1,如果不想改变图像的亮度就应该这样。如果稍微修改一下锐化核使它的权重加起来为0,就会得到一个边缘检测核,把边缘转为白色,把非边缘区域转为黑色。
在源图像和目标图像上分别使用卷积矩阵:cv2.filter2D(src, -1, kernel, dst). 第二个参数指定了目标图像每个通道的位深度(比如,位深度cv2.CV_8U表示每个通道为8位),如果为负值,则表示目标图像和源图像有同样的位深度。
注:对彩色图像来说,filter2D()会对每个通道都用同样的核。如果要对每个通道使用不同的核,就必须用split()函数和merge()函数。
对于模糊滤波器,为了达到模糊效果,通常权重和应该为1,而且邻近像素的权重全为正。
锐化、边缘检测以及模糊等滤波器都是用了高度对称的核。但是有时不对称的核也会得到一些有趣的效果。
# VConvolutionFilter 表示一般的滤波器 class VConvolutionFilter(object): """A filter that applies a convolution to V(or all of BGR).""" def __init__(self, kernel): self._kernel = kernel def apply(self, src, dst): """Apply the filter with a BGR or gray source/destination.""" cv2.filter2D(src, -1, self._kernel, dst) # SharpenFilter 表示特定的锐化滤波器 class SharpenFilter(VConvolutionFilter): """A sharpen filter with a 1-pixel radius.""" def __init__(self): kernel = numpy.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]]) VConvolutionFilter.__init__(self, kernel) # 边缘检测滤波器 class FindEdgesFilter(VConvolutionFilter): """A edge-finding filter with a 1-pixel radius.""" def __init__(self): kernel = numpy.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]) VConvolutionFilter.__init__(self, kernel) # 邻近平均滤波器 class BlurFilter(VConvolutionFilter): """A edge-finding filter with a 1-pixel radius.""" def __init__(self): kernel = numpy.array([[0.04, 0.04, 0.04, 0.04, 0.04], [0.04, 0.04, 0.04, 0.04, 0.04], [0.04, 0.04, 0.04, 0.04, 0.04], [0.04, 0.04, 0.04, 0.04, 0.04], [0.04, 0.04, 0.04, 0.04, 0.04]]) VConvolutionFilter.__init__(self, kernel)
下面介绍一种核,它同时具有模糊(有正的权重)和锐化(有负的权重)的作用。这会产生一种脊状(ridge)或者浮雕(embossed)的效果。
class EmbossFilter(VConvolutionFilter): """A edge-finding filter with a 1-pixel radius.""" def __init__(self): kernel = numpy.array([[-2, -1, 0], [-1, 1, 1], [0, 1, 2]]) VConvolutionFilter.__init__(self, kernel)