前一篇已经讲解了构建一个图片搜索引擎的基本步骤.
下面我们分步实现:
定义图像描述符
图像描述符其实就是指能描述一副图片特征的数据.
基于一副图像几何元素,我们可以提取点和块,线,或边缘特征.当然具体要看我们需要分析的问题,采用哪种描述符更简洁明了,更容易实现.
这里我们采用一个简单且有效的图像描述符:颜色直方图.
通过将颜色直方图作为我们的图像描述符,可以根据图像的色彩分布提取特征.
这里我们可以做一个假设:如果图像含有相似的色彩分布,那么这两幅图像就认为是相似的.即使图像内容差别非常大,依然会根据色彩分布而被认为是相近的.
当然这个假设在一定范围内存在着误差.
第一步: 定义图像描述符
这里不适用标准的颜色直方图,而是对其进行一些修改,使其更健壮和强大.
这个图像描述符是HSV颜色空间的3D颜色直方图(色相,饱和度,明度).一般来说,彩色图像由RGB构成的元组表示.通常将RGB色彩空间爱你想象成一个立方体,如下图:
然而,RGB值很容易理解,但RGB色彩空间无法模拟人眼接受到的色彩.取而代之,我们使用HSV色彩空间爱你将像素点映射到圆bin体上.
直方图
现在选定了颜色空间,接着需要定义直方图中bin的数量。直方图用来粗略的表示图像中各强度像素的密度。本质上,直方图会估计底层函数的概率密度。
我们需要注意的是直方图中bin的数量需要不断的权衡.如果选择过少,那么直方图含有的数据量就不够,无法区分某些颜色分布不同的图像;反之,如果选取的bin数量过多,那么其中的组件就会过多,导致内容相近的图片也会判断成不相似的.
一般来说,我们需要为颜色直方图描述符实验bin的个数,具体取决于数据集的大小和数据集中图像之间色彩分布的差异。可以用迭代、实验性的方式来调整bin的数目。迭代方法一般基于数据集的大小调整。数据集越小,使用的bin的数目就越少。如果数据集非常大,则会使用更多的bin,这样可以让直方图更大,更能区分图像。
如果我们测试的图片是假期照片图像引擎.我们将在HSV色彩空间中使用3D颜色直方图.8个bin用于色相通道、12个bin用于饱和度通道、3个bin用于明度通道,总共的特征向量有8 × 12 × 3=288。
这意味着数据集中的每幅图像,无论其像素数目是36 × 36,还是2000 × 1800。最终都会用288个浮点数构成的列表抽象并量化表示。
废话少说,我们现在可以开始写代码了.
代码块
colordescriptor.py
#import the necessary packages
import numpy as np
import cv2
class ColorDescriptor:
def __init__(self,bins):
self.bins=bins
def describe(self,image):
#convert the image tothe HSV color space and initialize
#the features used to quantify the image
image=cv2.cvtColor(image,cv2.COLOR_RGB2HSV)
features=[]
#获取图像的维度并计算图像中心(x,y)的位置
(h,w)=iamge.shape[:2]
(cX,cY)=(int(w*0.5),int(h*0.5))
这里不计算整个图像的3D HSV颜色直方图,而是计算图像不同区域的3D HSV直方图.
使用基于区域的直方图,而不是全局直方图的好处是:这样我们就可以模拟各个区域的颜色分布.
比如一副这样的图片,如果采用全局直方图,我们将仅仅知道图像中多少比例是蓝色,多少比例是棕色.
为了消除这个问题,可以对图像中的不同区域计算颜色直方图:
对于我们的图像描述符,将图像分为5个不停的区域:1、左上角;2、右上角;3、右下角;4、左下角;以及图像的中央。
使用这些区域,可以粗略模拟出不同的区域。能够表示出蓝天在左上角和右上角,沙滩在左下角和右下角。图像的中央是沙滩和蓝天的结合处。
下面的代码是创建基于区域的颜色描述符.
python
#import the necessary packages
import numpy as np
import cv2
class ColorDescriptor:
def __init__(self,bins):
self.bins=bins
def describe(self,image):
#convert the image tothe HSV color space and initialize
#the features used to quantify the image
image=cv2.cvtColor(image,cv2.COLOR_RGB2HSV)
features=[]
#获取图像的维度并计算图像中心(x,y)的位置
(h,w)=iamge.shape[:2]
(cX,cY)=(int(w*0.5),int(h*0.5))
#分别定义左上、右上、右下、和左下区域
segments=[(0,cX,0,cY),(cX,w,0,cY),(cX,w,cY,h),(0,cX,cY,h)]
#构建一个椭圆用来表示图像的中央区域,定义一个长短轴分别为图像长宽75%的椭圆
(axesX,axesY)=(int(w*0.75)/2,iint(h*0.75)/2)
ellipMask=np.zeros(image.shape[:2],dtype="uint8")
cv2.ellipse(ellipMask,(cX,cY),(axesX,axesY),0,0,360,255,-1)
for (startX,endX,startY,endY) in segments:
#为每个角的掩模分配内存,为图像的每个角绘制白色矩形,接着将矩形减去中间的椭圆。
cornerMask=np.zeros(image.shape[:2],dtype="uint8")
cv2.rectangle(conerMask,(startX,startY),(endX,endY),255,-1)
cornerMask=cv2.subtract(conerMask,ellipMask)
#针对每个区域都调用直方图方法。第一个参数是需要提取特征的图像,第二个参数是掩模区域,这样来提取颜色直方图。
hist=self.histogram(iamge,cornerMask)
features.extend(hist)
#histogram方法会返回当前区域的颜色直方图表示,我们将其添加到特征列表中
#提取图像中间(椭圆)区域的颜色直方图并更新features列表
hist=self.histogram(image,ellipMak)
features.extend(hist)
#调用函数返回特征向量
return features
现在来快速浏览下实际的histogram方法:
def histogram(self, image, mask):
# extract a 3D color histogram from the masked region of the
# image, using the supplied number of bins per channel; then
# normalize the histogram
hist = cv2.calcHist([image], [0, 1, 2], mask, self.bins,
[0, 180, 0, 256, 0, 256])
hist = cv2.normalize(hist).flatten()
# return the histogram
return hist.
这里的histogram方法需要两个参数,第一个是需要描述的图像,第二个是mask,描述需要描述的图像区域。
在5和6行,通过调用cv2.calcHist计算图像掩模区域的直方图,使用构造器中的bin数目作为参数。
在7行对直方图归一化。这意味着如果我们计算两幅相同的图像,其中一幅比另一幅大50%,直方图会是相同的。
对直方图进行归一化非常重要,这样每个直方图表示的就是图像中每个bin的所占的比例,而不是每个bin的个数。同样,归一化能保证不同尺寸但内容近似的图像也会在比较函数中认为是相似的。
最后,在10行向调用函数返回归一化后的3D HSV颜色直方图。