久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

用Python和OpenCV創(chuàng)建一個圖片搜索引擎的完整指南 - Python - 伯樂在線

 mzsm 2015-01-17

大家都知道,,通過文本或標簽來搜索圖片的體驗非常糟糕。

無論你是將個人照片貼標簽并分類,,或是在公司的網(wǎng)站上搜索一堆照片,,還是在為下一篇博客尋找合適的圖片。在用文本和關(guān)鍵字來描述圖片是非常痛苦的事,。

我就遇到了這樣的痛苦的事情,,上周二我打開了一個很老的家庭相冊,其中的照片是9年前掃描成電子檔的,。

我想找到我家在夏威夷海灘拍的照片,。我用iPhoto打開相冊,慢慢的瀏覽,。這個過程非常辛苦,,每個JPEG圖像的元信息中的日期都是錯的。我已經(jīng)不記得文件夾中的圖片是如何排列的,,我絕望的搜索海灘的照片,,但還是找不到。

也許是運氣,,我跌跌撞撞的找到了其中一幅海灘上的照片,。多美的一幅照片啊。藍天中飄著棉花糖般的白云,。晶瑩透徹的海水,,像絲綢一樣掠過在金色的沙灘上。我?guī)缀蹩梢愿杏X微風(fēng)輕撫著面龐,,呼吸著海邊濕潤的空氣,。

找到這幅照片后,,我停止了手動搜索,打開一個代碼編輯器,。

雖然iPhoto這樣的應(yīng)用能讓你將相片分組,,甚至可以檢測人臉,但我們可以做的更多,。

注意,,我并不是介紹如何手動給圖片添加標簽。我是在介紹更強大的東西,。比如通過一幅圖片來搜索一組相似的圖片。

這是不是很酷,?只需鼠標點擊一次就可以可視化搜索圖片,。

這就是我的工作內(nèi)容。我用半個小時寫好代碼,,完成了一個針對家庭假期相冊的圖片搜索引擎,。

然后用上面找到的那張海灘圖片作為搜索源。幾秒后我就找到了相冊中其他的海灘圖片,,其中沒有任何為某張圖片添加標簽的動作,。

感興趣嗎,我們繼續(xù),。

在本文的其他部分,,我將介紹如何自己創(chuàng)建一個圖像搜索引擎。

想要文本中的代碼,?

直接跳到文本中的最后的“下載”一節(jié),。

什么是圖像搜索引擎?

讀者也許會問,,什么才是一個真正的圖像搜素引擎,?

我的意思是,我們都熟悉基于文本的搜索引擎,,如Google,、Bing、Baidu等,。用戶只需輸入幾個與內(nèi)容相關(guān)的關(guān)鍵字,,接著就會獲得搜索結(jié)果。但對于圖像搜索引擎,,其工作方式就有點區(qū)別,。搜索時使用的不是文字,而是圖片,。

聽起來很困難,,我的意思是,,如何量化圖像的內(nèi)容,讓其可搜索呢,?

本文將逐步回答這個問題,。首先,先了解一下圖像搜索引擎的內(nèi)容,。

一般來說,,有三種類型的圖像搜索引擎:基于元數(shù)據(jù)基于例子,、混合模式,。

基于元數(shù)據(jù)

圖1:基于元數(shù)據(jù)的圖像搜索引擎的例子。注意其中關(guān)鍵字和標簽是手動關(guān)聯(lián)到圖像上的,。

通過元數(shù)據(jù)搜索與標準的關(guān)鍵字搜索引擎沒有本質(zhì)的不同,。這種方式很少檢測圖像本身的內(nèi)容。而是用相關(guān)的文本信息:如手動注釋或添加標簽,;以及自動上下文提示(如網(wǎng)頁中該圖片附近的文字信息),。

當用戶在基于元數(shù)據(jù)的系統(tǒng)上進行搜索時,與傳統(tǒng)的文本搜索引擎其實差點不多的,。得到的是含有類似標簽或注釋的圖片,。

再次說明,使用基于元數(shù)據(jù)系統(tǒng)的工具進行搜索,,基本上是不查找圖像本身的,。

用基于元數(shù)據(jù)進行搜索的應(yīng)用中,一個比較好的例子就是Flickr,。將圖像上傳到Flickr后,,輸入一些文本作為標簽描述這幅圖像。接著Flickr會使用這些關(guān)鍵字進行搜索,,查找并推薦其他相關(guān)的圖像,。

基于例子搜索

圖2:TinEye就是一個基于例子的圖像搜索引擎。該搜索引擎會使用圖像本身的內(nèi)容進行搜索,,而不是使用文本搜索,。

另一方面,基于例子搜索僅僅依賴于圖像的內(nèi)容,,不需要提供關(guān)鍵字,。引擎會分析、量化并存儲圖像,,然后返回其他相關(guān)的圖像,。

圖像搜索引擎量化圖像內(nèi)容的過程稱為基于內(nèi)容的圖像信息獲取(Content-Based Image Retrieval,,CBIR)系統(tǒng),。術(shù)語CBIR通常用在學(xué)術(shù)文獻中,,但在實際上,這是“圖像搜索引擎”的另一種表述,,并特意指明該搜索引擎是嚴格基于圖像的內(nèi)容的,,沒有任何關(guān)于圖像的文本的信息。

基于例子系統(tǒng)的一個比較好的例子就是TinEye,。向TinEyet提交待查找圖像時,,TinEye實際上是一個逆向圖像搜索引擎,TinEye返回該圖像最相近的匹配,,以及該圖像位于的原始網(wǎng)頁地址,。

看下本節(jié)剛開始的示例圖像。我上傳了一個Google logo圖像,。TinEye檢測了圖像的內(nèi)容,,在搜索了超過60億幅圖片后,返回了1.3萬個含有Google logo圖片的網(wǎng)頁,。

所以仔細想一下:你需要為TinEye中的60億幅圖像收到添加標簽嗎?當然不需要,,為這么多圖片手動添加標簽需要龐大的人力物力,。

取而代之,使用某些算法從圖像本身中提取“特征”(如用一組數(shù)字來量化并抽象表示圖像),。接著,,當用戶提交了需要查找的圖像,從這幅圖像中提取特征,,將其與數(shù)據(jù)庫中的特征進行比較,,嘗試返回相似的圖像。

同樣,,依然需要強調(diào),,基于例子的搜索系統(tǒng)非常依賴圖像的內(nèi)容。這種類型的系統(tǒng)很難構(gòu)建并擴展,,但可以用算法全自動的搜索,,無需人工干預(yù)。

混合方式

圖3:混合式圖像搜索引擎可以同時基于圖像和文本描述搜索,。

當然,,除了前面介紹的兩種方式,還有一種介于兩者之間的方式,,如Twitter使用的,。

在Twitter上,可以與推文一起上傳圖像,。這樣就可以即使用提取圖像本身的特征,,也可以使用推文中的文本,,從而誕生一種混合方式?;谶@種方式可以可以構(gòu)建一個即使用上下文關(guān)系,,又使用基于例子搜索的策略的圖像搜索引擎。

提示:有興趣閱讀更多關(guān)于不同類型的圖像搜索引擎的資料,?我有一篇完整的博客介紹比較這些搜索引擎的,,鏈接在此

在進一步描述和構(gòu)建圖像搜索引擎之前,,讓我們先來了解一些重要的術(shù)語,。

一些重要的術(shù)語

在深入了解之前,先花點時間了解一些重要的術(shù)語,。

在構(gòu)建圖像搜索引擎時,,首先要對數(shù)據(jù)集編列索引。索引化數(shù)據(jù)集是量化數(shù)據(jù)集的過程,,即通過圖像描述符(image descriptor,,也稱描述子)提取每幅圖像的特征

圖像描述符就是用來描述圖像的算法,。

例如:

  • R,、G、B三色通道的均值和標準差,。
  • 圖像特征形狀的統(tǒng)計矩.
  • 形狀和紋理的梯度和朝向,。

這里最重要的是圖像描述符確定了圖像是如何量化的。

另一方面,,特征是圖像描述符的輸出,。當將一幅圖像放入圖像描述符中時,就會獲得這幅圖像的特征,。

以基本的術(shù)語來說,。特征(或特征向量)僅僅是一個用來抽象表示或量化的圖像的數(shù)字列表。

來看下面這幅示例圖像:

圖4:圖像描述符的管道,。描述符中有一幅輸入圖像,,使用圖像描述符會返回一個特征向量(一個數(shù)字列表),

這里對一幅輸入圖像使用圖像描述符,,輸出是一組用來量化圖像的數(shù)字,。

通過距離量測或其他相似度比較函數(shù),特征向量可以用來表示比較的相似度,。距離量測相似度函數(shù)采用兩個特征向量作為輸入,,返回一個數(shù)值來描述著兩個特征向量的相似度。

下圖以可視化的方式比較了兩幅圖的比較過程:

圖5:為了比較兩幅圖,必須將對應(yīng)的特征向量輸入進距離量測/相似度比較函數(shù),。輸出結(jié)果是一個數(shù)值,,量化地描述兩幅圖下的相似度。

給定兩個特征向量,,使用距離函數(shù)來確定這兩個特征向量的相似度,。距離函數(shù)的輸出是一個浮點數(shù),用來描述兩幅圖像的相似度,。

CBIR系統(tǒng)的4個步驟

無論構(gòu)建的是什么樣的CBIR系統(tǒng),,最終都可以分解成4個不同的步驟。

  1. 定義圖像描述符:在這一階段,,需要決定描述圖像的哪一方面,。是關(guān)注圖像的顏色,還是圖像中的物體形狀,,或是圖像中的紋理,?
  2. 索引化數(shù)據(jù)集:現(xiàn)在有了圖像描述符,接著就是將這個圖像描述符應(yīng)用得到數(shù)據(jù)集中的每幅圖像,,提取這些圖像的特征,,將其存儲起來(如CSV文件、RDBMS,、Redis數(shù)據(jù)庫中,,等),這樣后續(xù)步驟就能使用以便比較,。
  3. 定義相似矩陣:很好,現(xiàn)在有了許多特征向量,。但如何比較這些特征向量呢,?流行的方式是比較歐幾里德距離、余弦距離,、或卡方距離,。但實際中取決于兩點:1、數(shù)據(jù)集,;2,、提取的特征類型。
  4. 搜索:最后一步是進行實際的搜索,。用戶會向系統(tǒng)提交一幅需要搜索的圖片(例如從上傳窗口或通過移動App提交),,而你的任務(wù)是:1、提取這幅圖像的特征,;2,、使用相似度函數(shù)將這幅圖像的特征與已經(jīng)索引化的特征進行比較。這樣,,只需根據(jù)相似度函數(shù)的結(jié)果,,返回相關(guān)的圖像就可以了,。

再次強調(diào),這是所有CBIR系統(tǒng)中最基本的4步,。如果使用的特征表示不同,,則步驟數(shù)會增加,也會為每個步驟增加一定數(shù)量的子步驟,。就目前而言,,讓我們關(guān)注并使用這4步。

下面通過圖像來具體了解這4個大步驟,。下圖表述的是步驟1和2:

圖6:處理并提取數(shù)據(jù)集中的每幅圖像的流程圖,。

首先提取數(shù)據(jù)集中每幅圖像的特征,將這些特征存入一個數(shù)據(jù)庫,。

接著可以執(zhí)行搜索(步驟3和4):

圖7:在CBIR系統(tǒng)中執(zhí)行搜索,。用戶提交一個搜索請求,系統(tǒng)對搜索圖像進行描述,,其特征會與數(shù)據(jù)集中已有的特征進行比較,,并對結(jié)果根據(jù)相關(guān)度進行排序,返回給用戶,。

首先,,用戶必須像搜索引擎提交一幅需要查找的圖像。接著對這幅圖像提取特征信息,。將這些特征信息與數(shù)據(jù)集中已有的圖像的特征信息進行比較,。最后,對結(jié)果根據(jù)相關(guān)度進行排序并返回給用戶,。

數(shù)據(jù)集——假期相冊

這里將INRIA假期數(shù)據(jù)集作為圖像搜索的數(shù)據(jù)集,。

這個數(shù)據(jù)集含有全世界許多地方的假期旅行,包括埃及金字塔,、潛水,、山區(qū)的森林、餐桌上的瓶子和盤子,、游艇,、海面上的日落。

下面是數(shù)據(jù)集中的一些圖片:

圖8:數(shù)據(jù)集中的示例圖像,。我們將使用這些圖像構(gòu)建自己的圖像搜索引擎,。

在本例中,對于我們希望從旅行相冊中找到某種景色的相片來說,,用這幅數(shù)據(jù)集作為示例來說非常好,。

目標

我們的目標是構(gòu)建一個個人圖像搜索引擎。將假期照片作為數(shù)據(jù)集,我們希望將這個數(shù)據(jù)集變成可搜索的,,即一個“基于例子”的圖像搜索引擎,。例如,如果我提交了一幅在河中航行的帆船的照片,,圖像搜索引擎應(yīng)該能找到并返回相冊中碼頭和船塢拍攝的照片,。

看下面的圖,其中有我提交的照片,,即一幅在水里的船,。得到了假期照片集合中相關(guān)的圖像。

圖9:圖像搜索引擎的例子,。提交了一幅含有海中船只的圖像,。返回相關(guān)的圖像,這些圖像都是在海中的船,。

為了構(gòu)建這個系統(tǒng),,將使用一個簡單且有效的圖像描述符:顏色直方圖

通過將顏色直方圖作為我們的圖像描述符,,可以根據(jù)圖像的色彩分布提取特征,。由于這一點,我們可以對我們的圖像搜索引擎做個重要的假設(shè):

假設(shè):如果圖像含有相似的色彩分布,,那么這兩幅圖像就認為是相似的,。即使圖像的內(nèi)容差別非常大,依然會根據(jù)色彩分布而被認為是相近的,。

這個假設(shè)非常重要,,在使用顏色直方圖作為圖像描述符時,這是個公平且合理的假設(shè),。

第一步1:定義圖像描述符

這里不使用標準的顏色直方圖,,而是對其進行一些修改,使其更加健壯和強大,。

這個圖像描述符是HSV顏色空間的3D顏色直方圖(色相、飽和度,、明度),。一般來說,圖像由RGB構(gòu)成的元組表示,。通常將RGB色彩空間想象成一個立方體,,如下圖所示。

圖10:RGB立方體的例子,。

然而,,雖然RGB值很容易理解,但RGB色彩空間無法模擬人眼接受到的色彩。取而代之,,我們使用HSV色彩空間將像素點的映射到圓bin體上,。

圖11:HSV圓bin體的例子。

還有其他顏色空間能夠更好的模擬人眼接收的顏色,,如CIE L*a*b*和CIE XYZ顏色空間,,但作為第一個圖像搜索引擎的實現(xiàn),先簡化使用的色彩模型,。

現(xiàn)在選定了顏色空間,,接著需要定義直方圖中bin的數(shù)量。直方圖用來粗略的表示圖像中各強度像素的密度,。本質(zhì)上,,直方圖會估計底層函數(shù)的概率密度。在本例中,,P是圖像I中像素色彩C出現(xiàn)的概率,。

主要注意的是,為直方圖選取bin的數(shù)目需要不斷的權(quán)衡,。如果選擇的bin數(shù)目過少,,那么直方圖含有的數(shù)據(jù)量就不夠,無法區(qū)分某些不同顏色分布的圖像,。反之,,如果直方圖選取的bin的數(shù)目過多,那么其中的組件就過多,,導(dǎo)致內(nèi)容很相近的圖片也會判斷成不相似,。

下面是直方圖bin過少的例子。

圖12:9個bin直方圖的例子,。注意其中bin的數(shù)量很少,,只有少數(shù)給定的像素值位列其中。

注意其中只有少數(shù)幾個bin及相應(yīng)的像素值,。

下面是直方圖bin過多的例子,。

圖13:128 bin直方圖的例子。注意其中含有許多的柱和相應(yīng)的像素值,。

在上面的的例子中使用了許多bin,,bin的數(shù)目過多,由于需要直方圖中每個“山峰”和“山谷”都需要匹配才能認為圖像是“相似的”,,所以就失去了“概括”圖像的能力,。

就我個人而言,我喜歡用迭代,、實驗性的方式來調(diào)整bin的數(shù)目,。迭代方法一般基于數(shù)據(jù)集的大小調(diào)整,。數(shù)據(jù)集越小,使用的bin的數(shù)目就越少,。如果數(shù)據(jù)集非常大,,則會使用更多的bin,這樣可以讓直方圖更大,,更能區(qū)分圖像,。

一般來說,讀者需要為顏色直方圖描述符實驗bin的個數(shù),,具體取決于數(shù)據(jù)集的大小和數(shù)據(jù)集中圖像之間色彩分布的差異,。

對于我們的假期照片圖像搜索引擎,將在HSV色彩空間中使用3D顏色直方圖,,8個bin用于色相通道,、12個bin用于飽和度通道、3個bin用于明度通道,,總共的特征向量有8 × 12 × 3=288,。

這意味著數(shù)據(jù)集中的每幅圖像,無論其像素數(shù)目是36 × 36,,還是2000 × 1800,。最終都會用288個浮點數(shù)構(gòu)成的列表抽象并量化表示。

我認為解釋3D直方圖最好的方式是用連接詞AND,。一個3D HSV顏色描述符將查找指定圖像中1號bin有多少像素含有色相值,,AND有多少像素有飽和度值,AND有多少像素有明度值,。計算出符合條件的像素值,。雖然需要對每個bin重復(fù)這個操作,但可以非常高效的完成這個任務(wù),。

很酷,,是吧!

理論講解的夠多了,,下面來開始編碼,。

用你最喜歡的編輯器打開一個新文件,,命名為colordescriptor.py,。加入下面代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# import the necessary packages
import numpy as np
import cv2
class ColorDescriptor:
    def __init__(self, bins):
        # store the number of bins for the 3D histogram
        self.bins = bins
    def describe(self, image):
        # convert the image to the HSV color space and initialize
        # the features used to quantify the image
        image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        features = []
        # grab the dimensions and compute the center of the image
        (h, w) = image.shape[:2]
        (cX, cY) = (int(w * 0.5), int(h * 0.5))

首先導(dǎo)入所需的Python模塊,。用NumPy進行數(shù)值處理,,用cv2使用OpenCV的Python綁定,。

在第五行定義了ColorDescriptor類,。該類用來封裝所有用于提取圖像中3D HSV顏色直方圖的邏輯,。

ColorDescriptor的__init__方法只有一個參數(shù)——bins,,即顏色直方圖中bin的數(shù)目,。

在第10行定義describe方法,,用于描述指定的圖像。

在describe方法中,,將圖像從RGB顏色空間(或是BGR顏色空間,,OpenCV以NumPy數(shù)組的形式反序表示RGB圖像)轉(zhuǎn)成HSV顏色空間。接著初始化用于量化圖像的特征列表features,。

17和18行獲取圖像的維度,,并計算圖像中心(x, y)的位置。

現(xiàn)在遇到難點,。

這里不計算整個圖像的3D HSV顏色直方圖,,而是計算圖像中不同區(qū)域的3D HSV顏色直方圖。

使用基于區(qū)域的直方圖,,而不是全局直方圖的好處是:這樣我們可以模擬各個區(qū)域的顏色分布,。例如看下面的這幅圖像:

圖14:待搜索的圖像。

在這幅圖像中,,很明顯,,藍天在圖像的上部,而沙灘在底部,。使用全局搜索的話,,就無法確定圖像中“藍色”區(qū)域和“棕色”沙子區(qū)域的位置。而是僅僅知道圖像中有多少比例是藍色,,有多少比例是棕色,。

為了消除這個問題,可以對圖像中的不同區(qū)域計算顏色直方圖:

圖15:將圖像分為5個不同區(qū)域的例子,。

對于我們的圖像描述符,,將圖像分為5個不停的區(qū)域:1、左上角,;2,、右上角;3,、右下角,;4、左下角,;以及圖像的中央,。

使用這些區(qū)域,可以粗略模擬出不同的區(qū)域,。能夠表示出藍天在左上角和右上角,,沙灘在左下角和右下角。圖像的中央是沙灘和藍天的結(jié)合處,。

下面的代碼是創(chuàng)建基于區(qū)域的顏色描述符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# import the necessary packages
import numpy as np
import cv2
class ColorDescriptor:
    def __init__(self, bins):
        # store the number of bins for the 3D histogram
        self.bins = bins
    def describe(self, image):
        # convert the image to the HSV color space and initialize
        # the features used to quantify the image
        image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        features = []
        # grab the dimensions and compute the center of the image
        (h, w) = image.shape[:2]
        (cX, cY) = (int(w * 0.5), int(h * 0.5))
        # divide the image into four rectangles/segments (top-left,
        # top-right, bottom-right, bottom-left)
        segments = [(0, cX, 0, cY), (cX, w, 0, cY), (cX, w, cY, h),
            (0, cX, cY, h)]
        # construct an elliptical mask representing the center of the
        # image
        (axesX, axesY) = (int(w * 0.75) / 2, int(h * 0.75) / 2)
        ellipMask = np.zeros(image.shape[:2], dtype = 'uint8')
        cv2.ellipse(ellipMask, (cX, cY), (axesX, axesY), 0, 0, 360, 255, -1)
        # loop over the segments
        for (startX, endX, startY, endY) in segments:
            # construct a mask for each corner of the image, subtracting
            # the elliptical center from it
            cornerMask = np.zeros(image.shape[:2], dtype = 'uint8')
            cv2.rectangle(cornerMask, (startX, startY), (endX, endY), 255, -1)
            cornerMask = cv2.subtract(cornerMask, ellipMask)
            # extract a color histogram from the image, then update the
            # feature vector
            hist = self.histogram(image, cornerMask)
            features.extend(hist)
        # extract a color histogram from the elliptical region and
        # update the feature vector
        hist = self.histogram(image, ellipMask)
        features.extend(hist)
        # return the feature vector
        return features

22和23行用于分別定義左上,、右上,、右下、和左下區(qū)域,。

這里,,我們需要構(gòu)建一個橢圓用來表示圖像的中央?yún)^(qū)域。在代碼的27行,,定義一個長短軸分別為圖像長寬75%的橢圓,。

接著初始化一個空白圖像(將圖像填充0,表示黑色的背景),,該圖像與需要描述的圖像大小相同,,見28行。

最后,,在29行使用cv2.ellipse函數(shù)繪制實際的橢圓,。該函數(shù)需要8個不同的參數(shù):

  1. 需要繪制橢圓的圖像。這里使用了下面會介紹的“掩?!钡母拍?。
  2. 兩個元素的元組,用來表示圖像的中心坐標,。
  3. 兩個元組的元組,,表示橢圓的兩個軸。在這里,,橢圓的長短軸長度為圖像長寬的75%,。
  4. 橢圓的旋轉(zhuǎn)角度。在本例中,,橢圓無需旋轉(zhuǎn),,所以值為0。
  5. 橢圓的起始角,。
  6. 橢圓的終止角,。看上一個參數(shù),,這意味著繪制的是完整的橢圓,。
  7. 橢圓的顏色,255表示的繪制的是白色橢圓,。
  8. 橢圓邊框的大小,。傳遞正數(shù)比會以相應(yīng)的像素數(shù)目繪制橢圓邊框。負數(shù)表示橢圓是填充模式,。

在35行為每個角的掩模分配內(nèi)存,,在36行為圖像的每個角繪制白色矩形,接著在37行將矩形減去中間的橢圓,。

如果將這個過程用圖像動態(tài)表示,,看上去應(yīng)該是這樣的:

圖16:為圖像中每個需要提取特征的區(qū)域構(gòu)建掩模,。

如這個動畫所示,我們獨立檢測每塊區(qū)域,,在迭代中移除每個矩形與圖像中間的橢圓重疊的部分。

讀者也許會奇怪,,“我們不是要提取圖像的顏色直方圖嗎,?為什么要做這些掩模的事情?”

問得好,!

原因是因為我們需要告訴OpenCV直方圖函數(shù)我們要提取的顏色直方圖的區(qū)域,。

記住,我們的目標是分開描述圖像的每個區(qū)域,。表述不同區(qū)域最高效的方法是使用掩模,。對于圖像中某個點(x, y),只有掩模中該點位白色(255)時,,該像素點才會用于計算直方圖,。如果該像素點對應(yīng)的位置在掩模中是黑色(0),將會被忽略,。

通過下圖可以更加深刻的了解這個概念,。

圖17:對圖像使用掩模。注意左圖中只有右圖掩模中對應(yīng)的區(qū)域為白色才會顯示,。

可以看到,,只有掩模的區(qū)域才會用于直方圖的計算中。

很合理,,是吧,。

所以現(xiàn)在在41行針對每個區(qū)域都調(diào)用直方圖方法。第一個參數(shù)是需要提取特征的圖像,,第二個參數(shù)是掩模區(qū)域,,這樣來提取顏色直方圖。

histogram方法會返回當前區(qū)域的顏色直方圖表示,,我們將其添加到特征列表中,。

46和47行提取圖像中間(橢圓)區(qū)域的顏色直方圖并更新features列表。

最后,,在50行像調(diào)用函數(shù)返回特征向量,。

現(xiàn)在來快速瀏覽下實際的histogram方法:

1
2
3
4
5
6
7
8
9
10
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方法需要兩個參數(shù),第一個是需要描述的圖像,,第二個是mask,,描述需要描述的圖像區(qū)域。

在5和6行,,通過調(diào)用cv2.calcHist計算圖像掩模區(qū)域的直方圖,,使用構(gòu)造器中的bin數(shù)目作為參數(shù),。

在7行對直方圖歸一化。這意味著如果我們計算兩幅相同的圖像,,其中一幅比另一幅大50%,,直方圖會是相同的。對直方圖進行歸一化非常重要,,這樣每個直方圖表示的就是圖像中每個bin的所占的比例,,而不是每個bin的個數(shù)。同樣,,歸一化能保證不同尺寸但內(nèi)容近似的圖像也會在比較函數(shù)中認為是相似的,。

最后,在10行向調(diào)用函數(shù)返回歸一化后的3D HSV顏色直方圖,。

步驟2:從數(shù)據(jù)集提取特征

現(xiàn)在有了定義好的圖像描述符,,進入第二步,對數(shù)據(jù)集中的每幅圖像提取特征(如顏色直方圖),。提取特征并將其持久保存起來的過程一般稱為“索引化”,。

繼續(xù)來看對假期照片數(shù)據(jù)集進行索引化的代碼。創(chuàng)建一個新文件,,命名為index.py,,添加索引化所需的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# import the necessary packages
from pyimagesearch.colordescriptor import ColorDescriptor
import argparse
import glob
import cv2
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument('-d', '--dataset', required = True,
    help = 'Path to the directory that contains the images to be indexed')
ap.add_argument('-i', '--index', required = True,
    help = 'Path to where the computed index will be stored')
args = vars(ap.parse_args())
# initialize the color descriptor
cd = ColorDescriptor((8, 12, 3))

首先導(dǎo)入所需的模塊。注意第一步中的ColorDescriptor類,,這里將其放入pyimagesearch模塊,,以便更好的組織代碼。

還需要argparse模塊來處理命令行參數(shù),、glob來獲取圖像的文件路徑,,以及cv2來使用OpenCV的接口。

7-12行用來處理命令行指令,。這里需要兩個指令,,–dataset,表示假期相冊的路徑,。–index,,表示輸出的CSV文件含有圖像文件名和對應(yīng)的特征。

最后,,在16行初始化ColorDescriptor,,8 bin擁有色相、12 bin用于飽和度,、3 bin用于明度,。

現(xiàn)在所有內(nèi)容都初始化了,可以從數(shù)據(jù)集提取特征了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# open the output index file for writing
output = open(args['index'], 'w')
# use glob to grab the image paths and loop over them
for imagePath in glob.glob(args['dataset'] + '/*.png'):
    # extract the image ID (i.e. the unique filename) from the image
    # path and load the image itself
    imageID = imagePath[imagePath.rfind('/') + 1:]
    image = cv2.imread(imagePath)
    # describe the image
    features = cd.describe(image)
    # write the features to file
    features = [str(f) for f in features]
    output.write('%s,%sn' % (imageID, ','.join(features)))
# close the index file
output.close()

在2行代開輸出文件,在5行遍歷數(shù)據(jù)集中的所有圖像,。

對于每幅圖像,,可以提取一個imageID,即圖像的文件名,。對于這個作為示例的搜索引擎,,我們假定每個文件名都是唯一的,也可以針對每幅圖像生產(chǎn)一個UUID,。在9行將從磁盤上讀取圖像,。

現(xiàn)在圖像載入內(nèi)存了,在12行對圖像使用圖像描述符并提取特征,。ColorDescriptor的describe方法返回由浮點數(shù)構(gòu)成的列表,用來量化并表示圖像,。

這個數(shù)字列表,,或者說特征向量,含有第一步中圖像的5個區(qū)域的描述,。每個區(qū)域由一個直方圖表示,,含有8 × 12 × 3 = 288項。5個區(qū)域總共有5 × 288 = 1440維度,。,。。因此每個圖像使用1440個數(shù)字量化并表示,。

15和16行簡單的將圖像的文件名和管理的特征向量寫入文件,。

為了索引化我們的相冊數(shù)據(jù)集,打開一個命令行輸入下面的命令:

1
$ python index.py --dataset dataset --index index.csv

這個腳本運行的很快,,完成后將會獲得一個名為index.csv的新文件,。

使用你最喜歡的文本編輯器打開并查看該文件。

可以看到在.csv文件的每一行,,第一項是文件名,,第二項是一個數(shù)字列表。這個數(shù)字列表就是用來表示并量化圖像的特征向量,。

對index文件運行wc命令,,可以看到已經(jīng)成功對數(shù)據(jù)集中805幅圖像索引化了:

1
2
$ wc -l index.csv
    805 index.csv

第3步:搜索

現(xiàn)在已經(jīng)從數(shù)據(jù)集提取了特征了,接下來需要一個方法來比較這些特征,,獲取相似度,。這就是第三步的內(nèi)容,創(chuàng)建一個類來定義兩幅圖像的相似矩陣,。

創(chuàng)建一個新文件,,命名為searcher.py,讓我們在這里做點神奇的事情:

1
2
3
4
5
6
7
8
9
10
11
12
# import the necessary packages
import numpy as np
import csv
class Searcher:
    def __init__(self, indexPath):
        # store our index path
        self.indexPath = indexPath
    def search(self, queryFeatures, limit = 10):
        # initialize our dictionary of results
        results = {}

首先先導(dǎo)入NumPy用于數(shù)值計算,csv用于方便的處理index.csv文件,。

在第5行定義Searcher類,。Searcher類的構(gòu)造器只需一個參數(shù),indexPath,,用于表示index.csv文件在磁盤上的路徑,。

要實際執(zhí)行搜索,需要在第10行調(diào)用search方法,。該方法需要兩個參數(shù),,queryFeatures是提取自待搜索圖像(如向CBIR系統(tǒng)提交并請求返回相似圖像的圖像),和返回圖像的數(shù)目的最大值,。

最后,,在12行初始化results字典。在這里,,字典有很用的用途,,每個圖像有唯一的imageID,可以作為字典的鍵,,而相似度作為字典的值,。

好了,現(xiàn)在將注意力放在這里,。這里是發(fā)生神奇的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# open the index file for reading
with open(self.indexPath) as f:
    # initialize the CSV reader
    reader = csv.reader(f)
    # loop over the rows in the index
    for row in reader:
        # parse out the image ID and features, then compute the
        # chi-squared distance between the features in our index
        # and our query features
        features = [float(x) for x in row[1:]]
        d = self.chi2_distance(features, queryFeatures)
        # now that we have the distance between the two feature
        # vectors, we can udpate the results dictionary -- the
        # key is the current image ID in the index and the
        # value is the distance we just computed, representing
        # how 'similar' the image in the index is to our query
        results[row[0]] = d
    # close the reader
    f.close()
# sort our results, so that the smaller distances (i.e. the
# more relevant images are at the front of the list)
results = sorted([(v, k) for (k, v) in results.items()])
# return our (limited) results
return results[:limit]

在1行打開index.csv文件,,在3行獲取CSV讀取器的句柄,接著在6行循環(huán)讀取index.csv文件的每一行,。

對于每一行,,提取出索引化后的圖像的顏色直方圖,用11行的chi2_distance函數(shù)將其與待搜索的圖像特征進行比較,,該函數(shù)在下面介紹,。

在32行使用唯一的圖像文件名作為鍵,用與待查找圖像的與索引后的圖像的相似讀作為值來更新results字典,。

最后,,將results字典根據(jù)相似讀升序排序。

卡方相似度為零的圖片表示完全相同,。相似度數(shù)值越高,,表示兩幅圖像差別越大。

說到卡方相似讀,,看下面的源碼:

1
2
3
4
5
6
7
def chi2_distance(self, histA, histB, eps = 1e-10):
    # compute the chi-squared distance
    d = 0.5 * np.sum([((a - b) ** 2) / (a + b + eps)
        for (a, b) in zip(histA, histB)])
    # return the chi-squared distance
    return d

chi2_distance函數(shù)需要兩個參數(shù),,即用來進行比較的兩個直方圖??蛇x的eps值用來預(yù)防除零錯誤,。

這個函數(shù)的名稱來自皮爾森的卡方測試統(tǒng)計,用來比較離散概率分布。

由于比較的是顏色直方圖,,根據(jù)概率分布的定義,,卡方函數(shù)是個完美的選擇。

一般來說,,直方圖兩端的值的差別并不重要,,可以使用權(quán)重對其進行處理,卡方距離函數(shù)就是這么做的,。

還能跟的上嗎,?我保證,最后一步是最簡單的,,僅僅需要將前面的各部分組合在一起,。

第四步:執(zhí)行搜索

如果我告訴你,執(zhí)行搜索是最簡單的一步,,你信嗎,?實際上,只需一個驅(qū)動程序?qū)肭懊娑x的所有的模塊,,將其依次組合成具有完整功能的CBIR系統(tǒng),。

所以新建最后一個文件,,命名為search.py,,這樣我們的例子就能完成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# import the necessary packages
from pyimagesearch.colordescriptor import ColorDescriptor
from pyimagesearch.searcher import Searcher
import argparse
import cv2
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument('-i', '--index', required = True,
    help = 'Path to where the computed index will be stored')
ap.add_argument('-q', '--query', required = True,
    help = 'Path to the query image')
ap.add_argument('-r', '--result-path', required = True,
    help = 'Path to the result path')
args = vars(ap.parse_args())
# initialize the image descriptor
cd = ColorDescriptor((8, 12, 3))

首先導(dǎo)入所需的包,導(dǎo)入第一步的ColorDescriptor來提取待查找圖像的特征,;導(dǎo)入第三步定義的Searcher類,,用于執(zhí)行執(zhí)行實際的搜索。

argparse和cv2模塊一直會導(dǎo)入,。

在8-15行處理命令行參數(shù),。我們需要用一個–index來表示index.csv文件的位置。

還需要–query來表示帶搜索圖像的存儲路徑,。該圖像將與數(shù)據(jù)集中的每幅圖像進行比較,。目標是找到數(shù)據(jù)集中歐給你與待搜索圖像相似的圖像。

想象一下,,使用Google搜索并輸入“Python OpenCV tutorials”,,會希望獲得與Python和OpenCV相關(guān)的信息。

與之相同,,如果針對相冊構(gòu)建一個圖像搜索引擎,,提交了一副關(guān)于云、大海上的帆船的圖像,,希望通過圖像搜索引擎獲得相似的圖像,。

接著需要一個–result-path,用來表示相冊數(shù)據(jù)集的路徑。通過這個命令可以選擇不同的數(shù)據(jù)集,,向用戶顯示他們所需要的最終結(jié)果,。

最后,在18行使用圖像描述符提取相同的參數(shù),,就如同在索引化那一步做的一樣,。如果我們是為了比較圖像的相似度(事實也正是如此),就無需改變數(shù)據(jù)集中顏色直方圖的bin的數(shù)目,。

直接將第三步中使用的直方圖bin的數(shù)目作為參數(shù)在第四步使用,。

這樣會保證圖像的描述是連續(xù)且可比較的。

現(xiàn)在到了進行真正比較的時候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# load the query image and describe it
query = cv2.imread(args['query'])
features = cd.describe(query)
# perform the search
searcher = Searcher(args['index'])
results = searcher.search(features)
# display the query
cv2.imshow('Query', query)
# loop over the results
for (score, resultID) in results:
    # load the result image and display it
    result = cv2.imread(args['result_path'] + '/' + resultID)
    cv2.imshow('Result', result)
    cv2.waitKey(0)

在2行從磁盤讀取待搜索圖像,,在3行提取該圖像的特征,。

在6和7行使用提取到的特征進行搜索,返回經(jīng)過排序后的結(jié)果列表,。

到此,,所需做的就是將結(jié)果顯示給用戶。

在9行顯示出待搜索的圖像,。接著在13-17行遍歷搜索結(jié)果,,將相應(yīng)的圖像顯示在屏幕上。

所有這些工作完成后,,就可以實際操作了,。

繼續(xù)閱讀,看最終效果如何,。

CBIR系統(tǒng)實戰(zhàn)

打開終端,,切換到代碼所在的目錄,執(zhí)行下面的命令:

1
$ python search.py --index index.csv --query queries/108100.png --result-path dataset

圖18:在相冊中搜索含有埃及金字塔的圖像,。

第一幅圖像是待搜索的埃及金字塔,。我們的目標是在相冊中找到相似的圖像??梢钥吹?,在相冊中找到了去金字塔游玩拍攝的照片。

我們還游覽的埃及其他地方,,所以用其他照片搜索試試:

1
$ python search.py --index index.csv --query queries/115100.png --result-path dataset

圖19:使用搜索引起搜索埃及其他地方的圖片,,注意圖中藍天的位置。

注意我們的搜索圖像中,,上半部分是藍天,。中間和下半部分是褐色的建筑和土地。

可以肯定,,圖像搜索引擎會返回上半部分是藍天,,下半部分是棕褐色建筑和沙子的圖像,。

這是因為我們使用了本文開頭介紹的基于區(qū)域的顏色直方圖描述符。使用這種圖像描述符可以粗略的針對每個區(qū)域執(zhí)行,,最后的結(jié)果中會含有圖像每個區(qū)域的像素的密度,。

旅途的最后一站是海灘,用下面的命令搜索海灘上的圖像:

1
$ python search.py --index index.csv --query queries/103300.png --result-path dataset

圖20:使用OpenCV構(gòu)建CBIR系統(tǒng)來搜索相冊,。

注意,,前3個搜索結(jié)果是在相同地點拍攝到的圖像。其他圖像都含有藍色的區(qū)域,。

當然,,沒有潛水的海灘之旅是不完整的。

1
$ python search.py --index index.csv --query queries/103100.png --result-path dataset

圖21:圖像搜索引起再次返回相關(guān)的結(jié)果,。這次是水下冒險,。

結(jié)果非常棒。前5個結(jié)果是同一條魚,,前10幅有9幅是水下探險,。

最后,一天的旅途結(jié)束了,,到了觀看夕陽的時候:

1
$ python search.py --index index.csv --query queries/127502.png --result-path dataset

圖22:這個OpenCV圖像搜索引起可以查找到相冊集中含有夕陽的相片,。

搜索結(jié)果非常棒,所有的結(jié)果都含有夕陽,。

這樣,,你就有了第一個圖像搜索引擎:

總結(jié)

本文介紹了如何構(gòu)建一個圖像搜索引擎,來查找相冊中的圖像,。

使用顏色直方圖對相冊中的圖像的顏色部分進行分類,。接著,,使用顏色描述符索引化相冊,,提取相冊中每一副圖像的顏色直方圖。

使用卡方距離比較圖像,,這是比較離散概率分布最常見的選擇,。

接著,實現(xiàn)了提交待搜索圖像和返回查找結(jié)果的邏輯,。

下一步

接下來該干什么,?

可以看到,使用命令行是與這個圖像搜索引擎交互的唯一方式,。這樣還不是太吸引人

下一篇文章將探索如何將這個圖像搜索引擎封裝進一個Python網(wǎng)絡(luò)框架中,,讓其更易于使用。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多