BOF算法 基于SIFT+KMeans

BOF 计算机视觉 以图搜图 Python

题目

BOF算法概念、步骤、注意事项

【实验要求】使用颜色直方图或者bof算法来提取图像特征,在指定数据集上实现以图搜图,即输入数据集中某一张图,在剩下的999张图里搜索最邻近的10张图。

一、BOF算法是什么?

BOF(Bag of Features)算法实际上就是BOW(Bag of Words)算法在图像领域的应用。
步骤如下:
1.对数据集中所有图像进行sift特征提取,每幅图像获得不定数量的128维特征。
2.对所有特征进行k-means聚类,生成词典。本质就是去重合并。最后聚类的类样本中心点就是一个个的词。所有的词构成词典。
3.用词典对每幅图像进行处理,生成每幅图像的初步BOF特征(本质是每幅图中所用词典中的词的统计频数),维度等于词典中词的个数。
4.计算词典中词的TF-IDF。再次对图像进行处理。获得每张图最终的BOF向量,也即每幅图对应的有序的词集。
5.随机输入一张数据集中的图像,输出其余999张图像中与之余弦相似度最大的10张图像

二、详细步骤

1.基于SIFT算法提取图像的特征

SIFT算法在cv2这个库中我们直接调用即可,安装方法如下:
cv2库安装
最后返回的des是特征向量的集合,类型是多维数组。

import cv2
import numpy as np

# 读取图像
img = cv2.imread(pictName) # 图像路径
# 转化为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 构造sift函数
sift = cv2.SIFT_create()
# 找出关键点
kp = sift.detect(gray, None)
# 使用关键点找出sift特征向量
kp, des = sift.compute(gray, kp)

return des  

2.对特征集进行K-means聚类

将对所有图像处理所得到的特征放在一起,进行K-means聚类。k均值聚类也可以调用sklearn库的KMeans方法,并且这个自带的聚类拥有多线程的隐操作,聚类结果出来特别快。之前手写了一个,聚一次比调库迭代300次还要慢10倍。。。。真的麻了。

from sklearn.cluster import KMeans

def Kmeans(pictsFeatsAll, wordsNum=150): # 数据集,类别数,默认150类
    estimator = KMeans(n_clusters=wordsNum, max_iter=300)  # 构造聚类器
    estimator.fit(pictsFeatsAll)  # 聚类
    # label_pred = estimator.labels_  # 获取聚类标签
    wordsDict = estimator.cluster_centers_

    return wordsDict # 视觉字典

最后只需要将所有的样本中心点返回就行了,样本中心点就是聚类出来的词。

3.获得初始BOF向量

这一步呢通过上一步获得的字典,去判断每张图的特征是属于什么词,获得一个与字典词数相同维度大小的词频数向量,这就是初始的BOF向量。

def preBOFPlus(wordsDict, allFeats2WordsIndex, featsNums):  # 写书
    # 词典(150*128),每个特征被划分到的簇(对应词在词典的下标,70w个int),每张图像的特征数(1000个数) ,70w是之前sift出来的特征数
    preIDF = [0 for i in range(len(wordsDict))]
    pictsPreBOF = []

    ddd = 0
    for i in featsNums:     # 1000个长度   #时间复杂度为o(featNums=70w)
        # 字典长度个0。表示一本书,一张图就是一本书,书里面有很多词,BOF就是用视觉词的“频率”表示一张图
        tempBOF = [0 for k in range(len(wordsDict))]
        tempIDF = set()
        # 1 分割获得每幅图的词 ,统计每幅图的词 形成频数向量
        for j in range(ddd, ddd+i):
            t = allFeats2WordsIndex[j]  # 获得一个词的序号
            tempBOF[t] += 1             # 写书,形成频数向量
            if t not in tempIDF:
                preIDF[t] += 1
                tempIDF.add(t)
        # 2 组合1000本书
        pictsPreBOF.append(tempBOF)

        ddd += i

    # 返回的是每张图的无权BOF,也就是(150*1)表示词的频数 pictsPreBOF= 1000*150
    # preIDF是每个词属不属于这张图,里面有它就加1,没有就加0  从属关系 150*1
    return preIDF, pictsPreBOF

返回的preIDF是 对于词典中的每个词,统计它在多少张图中出现过。

4.计算TF-IDF,并获得最终BOF

什么是TF-IDF?

  # 4.计算词典中词的TF-IDF。再次对图像进行处理。得到每幅图像的最终的有权BOF
    # 计算IDF
    IDF = preIDF
    for i in range(len(IDF)):
        if IDF[i] == 0:  # 说明 有的词 《消失了》
            print(f"preIDF的第{i}维为0,程序异常!结束")
            exit()
        IDF[i] = 1000.0 / float(IDF[i])

    # 计算有权BOF
    pictsBOF = copy.deepcopy(pictsPreBOF)

    for i in range(len(pictsBOF)):  # 1000
        lenFeatsNum = float(featsNums[i])
        for j in range(wordsNum):
            pictsBOF[i][j] = float(pictsBOF[i][j]**2) * IDF[j] / lenFeatsNum

    # 根据需要获得TF-IDF矩阵
    # TF_IDF = pictsPreBOF
    # for i in range(len(pictsBOF)):  # 1000
    #     for j in range(wordsNum):
    #       TF_IDF[i][j] = pictsBOF[i][j]/float(TF_IDF[i][j])

5.以图搜图

随机输入一张图,返回最接近的10张图的标号。

def onePicFindMore(picNum, pictsBOF, simlPicsNum=10):  # 默认取10

    # 获得输入图像的BOF向量
    inputPicBOF = pictsBOF[picNum]

    # 计算它与其他图像的余弦相似度
    picsSimil = []  # 相似度
    for i in range(len(pictsBOF)):
        picsSimil.append([cosineSimiliar(inputPicBOF, pictsBOF[i]), i])  # 加入标号

    picsSimil.sort(reverse=True)            # 按照相似度从大到小排序
    picsSimil.pop(0)                        # 除掉第一个,第一个一定是自己
    picsSimil = picsSimil[:simlPicsNum]     # 保留指定数量的图像标号

    picsNumber = []                         # 返回的标号
    for i in range(len(picsSimil)):
        picsNumber.append(picsSimil[i][1])  # 第二个数是标号

    return picsNumber


def cosineSimiliar(a, b):
    am = 0.0
    bm = 0.0
    aMutib = 0.0
    for i in range(len(a)):
        am += a[i]**2
        bm += b[i]**2
        aMutib += a[i]*b[i]

    return aMutib/math.sqrt(am*bm)

6.展示结果

仅用数字下标其实也能看出来是不是同类,但是不知道为啥不是同类。如果将结果图像放在一起,能更清楚的观察到图像间的相似和差异,以及为什么不是一类的图像确是很相似的。

    def displayPics(PicsNumber):    # 输入图像标号
 
    	picsNames = []  # 图像的路径以及名称
    	picsArray = []  # 图像list
    	for i in PicsNumber:
        	PicsNames = f'corel/{int(i/100)}/{i}.jpg'  # 文件路径
        	picsNames.append(f'{i}.jpg')
        	# 读取文件 : opencv的颜色通道顺序为[B,G,R],而matplotlib颜色通道顺序为[R,G,B],所以需要调换一下通道位置
        	picsArray.append(cv2.imread(PicsNames)[:, :, (2, 1, 0)])  # 调换通道
    	temp = picsNames.pop()
    	picsNames.append('INPUT : '+temp)
    	for i in range(len(PicsNumber)):
       		 plt.subplot(3, 5, i+1)
        	plt.imshow(picsArray[i], 'gray')
        	plt.title(picsNames[i])
       	 	plt.xticks([])
        	plt.yticks([])
    	plt.show()
    	
    	return

三、结果




可以看到,
第一幅图背景简单,恐龙图里都只有恐龙一个物体,检测到的特征点比较少,查找相似精度高,那张大象呢,也很明显,简单的背景和单一物体成像,相似度确实高。
第二幅图是花类的图像。能找一个恐龙出来我是没想到的,估计是对比度的问题,花类图片就是花瓣的部分亮度比较高,到了边缘就有一个亮度的突变,很快就变黑,恐龙的边缘也差不多。然后就造成了这个结果。
第三幅图是草地和马,这个特征就太明显了,草地和马都有自己很鲜明的特征,这个精度高是很正常的
第四幅图是车类。这种车的轮廓决定了它的特征很好找。最后结果还是不错的。
整体准确度还是比较高的。当然也有一部分近似度很低的,就不放图了。
这是一次有趣的实验。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2022年5月21日
下一篇 2022年5月21日

相关推荐