仅使用图像而不是通过标签从数据库中查找相似图像

Posted

技术标签:

【中文标题】仅使用图像而不是通过标签从数据库中查找相似图像【英文标题】:finding similar images from a database using image only not via tag 【发布时间】:2015-07-19 00:18:26 【问题描述】:

这个问题很简单,我想在给定查询图像的情况下找到相似的图像,类似于 TinEye 所做的。假设我有一件带有以下描述的衬衫

袖长:全长

衣领:礼物

图案:条纹

(上面的数据只是给你一个形象的感觉我实际上没有这个数据)

第一个图像是查询图像,下一个应该是相似度查找算法的输出。 所以基于这个例子,我们有一个灵活性,比如我们可以向用户展示一个改变颜色的图像,我们可以看到所有的图像都有相同的图案、相同的领子类型或袖子长度。所以我必须显示视觉上相似的输出。

堆栈上也有类似的线程 link from stack 不仅如此,还有很多其他的。但我对遵循的方法感到困惑。

在我的情况下,我不必在另一个类别中搜索,我必须在同一类别中搜索,例如如果输入是衬衫,我将仅在衬衫类别中搜索。那部分已经完成。

所以问题是处理这个问题的方法是什么。 对于颜色来说,这不是什么大问题。通过颜色直方图可以很容易地提取颜色信息。假设输入是圆领 T 恤,即无领、半袖,中间印有文字。现在输出应该是类似于半袖、圆领和中心印刷文字的图像。认为文本可能会有所不同。我尝试了 K-Means 聚类和 P-hash,但没有奏效。请赐教

PS:我必须找到相似的图像而不是重复的。

【问题讨论】:

这是一个不平凡的问题。 Tin-Ey​​e 使用一些非常复杂的机器学习算法来实现它的功能。不是试图让你失望或其他任何事情,但似乎你离理解问题的本质还有点距离。你说你试过K-means,你用了什么特征向量?有多少样本数据?什么克?您可能想尝试使用手动特征输入作为第一步,而不是尝试直接从可能需要深度卷积神经网络或类似的输入中提取复杂特征。 这就是我要问的。假设我想尝试一些手动功能名称,如 sift、hog、tamura 等,如果可能的话,你能告诉我如何用深度 CNN 来完成 我不是在建议像 SIFT 或 SURF 或 ORB 这样的图像特征,我是在建议像“有领”、“袖长”、“图案”、“颜色”、“尺寸”等特征. 如果您可以为每种衬衫类型构建一个特征向量,那么您可以使用 K-Means 对所有图像进行聚类,然后对于一个新样本,确定它属于哪个聚类(以及什么是“相似的”)。然后,您可以使用这些标记的类别来训练 NN。如果你真的想直接跳入 CNN,我建议查看 Caffe caffe.berkeleyvision.org 如果你能解决这个问题,祝你好运,这属于基于内容的图像检索领域,至今仍是一个未解决的问题。 en.wikipedia.org/wiki/Content-based_image_retrieval 查看本文nlpr.ia.ac.cn/2012papers/gjhy/gh102.pdf 【参考方案1】:

我会尝试将这个问题分成 3 个较小的问题:

检查图片显示的衬衫是长袖还是短袖 检查模式(stipped、plain、其他?) 确定衬衫颜色

检查图片是长袖还是短袖 这个在我看来是最简单的。您提到您有类别名称,但根据 google 图形,似乎 Shirt 或 TShirt 是长袖还是短袖可能并不明显。 我的解决方案很简单:

在图像上查找人脸 使用抓取算法从图像中提取面罩 面具脸(所以在这一步之后只剩下脸 - 其他一切都是黑色的)。请注意,这一步不是必需的 - 我只是提到它,因为它显示在最终图像上。 将图像转换为 HSV 颜色空间 使用面罩仅计算 FACE 的 H 和 S 颜色通道的直方图(不包括图像的其余部分) 使用上一步的直方图计算 hsv 图像的反投影。感谢您将仅获得颜色(在 HSV 中)与面部颜色相似的区域 - 因此您将仅获得包含皮肤的区域。 阈值结果(总是有一些噪音:))

该算法的最终结果是显示皮肤区域的黑白图像。使用此图像,您可以计算带有皮肤的像素数,并检查皮肤是仅在脸上还是在其他地方。您也可以尝试寻找轮廓 - 通常两种解决方案都会有机会检查手是否可见。是的 - 衬衫有短袖,没有 - 长袖。

以下是结果(从左上角开始 - 原始图像、面罩(抓取算法的结果)、蒙面脸、hsv 图像、计算反投影的结果、在前一个上使用阈值的结果): 正如您所看到的,不幸的是,图像 3 失败了,因为脸的颜色与衬衫图案的颜色非常相似(而且通常脸的颜色非常接近白色——这家伙有问题,他应该多花点时间在外面;))。

来源很简单,但如果您有不明白的地方,请随时提问:

import cv2
import numpy as np


def process_image(img, face_pos, title):
    if len(face_pos) == 0:
        print 'No face found!'
        return
    mask = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8) #create mask with the same size as image, but only one channel. Mask is initialized with zeros
    cv2.grabCut(img, mask, tuple(face_pos[0]), np.zeros((1,65), dtype=np.float64), np.zeros((1,65), dtype=np.float64), 1, cv2.GC_INIT_WITH_RECT) #use grabcut algorithm to find mask of face. See grabcut description for more details (it's quite complicated algorithm)
    mask = np.where((mask==1) + (mask==3), 255, 0).astype('uint8') #set all pixels == 1 or == 3 to 255, other pixels set to 0
    img_masked = cv2.bitwise_and(img, img, mask=mask) #create masked image - just to show the result of grabcut
    #show images
    cv2.imshow(title, mask) 
    cv2.imshow(title+' masked', img_masked)

    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) #convert image to hsv
    channels = [0,1]
    channels_ranges = [180, 256]
    channels_values = [0, 180, 0, 256]
    histogram = cv2.calcHist([img_hsv], channels, mask, channels_ranges, channels_values) #calculate histogram of H and S channels
    histogram = cv2.normalize(histogram, None, 0, 255, cv2.NORM_MINMAX) #normalize histogram

    dst = cv2.calcBackProject([img_hsv], channels, histogram, channels_values, 1) # calculate back project (find all pixels with color similar to color of face)
    cv2.imshow(title + ' calcBackProject raw result', dst)

    ret, thresholded = cv2.threshold(dst, 25, 255, cv2.THRESH_BINARY) #threshold result of previous step (remove noise etc)
    cv2.imshow(title + ' thresholded', thresholded)

    cv2.waitKey(5000)
    #put partial results into one final image
    row1 = np.hstack((img, cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR), img_masked))
    row2 = np.hstack((img_hsv, cv2.cvtColor(dst, cv2.COLOR_GRAY2BGR), cv2.cvtColor(thresholded, cv2.COLOR_GRAY2BGR)))
    return np.vstack((row1, row2))


paths = ['1.jpg', '2.jpg', '3.jpg', '4.jpg']
haar_cascade = cv2.CascadeClassifier('C:\\DevTools\\src\\opencv\\data\\haarcascades\\haarcascade_frontalface_default.xml') #change it to path to face cascade - it's inside opencv folder

for path in paths:
    img = cv2.imread(path)
    face_pos = haar_cascade.detectMultiScale(img, 1.3, 5, cv2.CASCADE_FIND_BIGGEST_OBJECT)
    if len(face_pos) == 0: #if haar cascade failed to find any face, try again with different (more accurate, but slower) settings
        face_pos = haar_cascade.detectMultiScale(img, 1.1, 3, cv2.CASCADE_FIND_BIGGEST_OBJECT)
    result = process_image(img, face_pos, path)
    cv2.imwrite('result_' + path, result) #save the result

检查图案(有花纹、平纹还是其他?)并确定衬衫的颜色 在这里,我将尝试从图像中提取(遮罩)衬衫,而不是仅对其进行操作。为了实现它,我会尝试使用与上一部分类似的方法 - 抓取算法。这次初始化它可能会更难。我想到的相当简单(但可能并不完美)的解决方案是:

在几乎整个区域周围设置矩形(每边只留下几个像素) 将蒙版初始化为图像中间的sure foreground值 - 只需使用sure foregroung“颜色”在中间画一些圆圈 在图片的四角设置蒙版为sure background 在面部矩形中将蒙版设置为sure background(在“检查图像是否显示长袖或短袖衬衫”步骤中使用 Haar 级联创建的蒙版)

或者,您可以将整个掩码初始化为sure foregroundpossible foreground,并使用分水岭算法找到大的白色区域(这是背景)。拥有此区域后 - 将其用作背景。 最有可能一起使用这两种解决方案会给你最好的结果。

您也可以尝试更简单的解决方案。看起来所有图像的中心都有 SHIRT 而不是背景、皮肤或其他任何东西。就像这里: 所以你可以只分析这部分衬衫。您也可以尝试使用 Haar 级联来定位图像的这个确定的衬衫部分 - 只需找到面部,然后将创建的矩形向下移动。

一旦你有了蒙面衬衫,你就可以计算它的参数。我会尝试的两件事是:

将其转换为 HSV 颜色空间并计算色调和饱和度通道的直方图(2 个分隔 - 不是我们在上一步中所做的)。比较 2 件衬衫的直方图应该让您有机会找到颜色相似的衬衫。为了比较直方图,我会使用一些(归一化的)相关系数。 使用傅立叶变换查看这件衬衫中最常见的频率。对于普通衬衫,它应该比脱衣的频率小得多。

我知道这些解决方案并不完美,但希望对您有所帮助。如果您有任何问题或疑问,请随时提出。


//编辑: 我使用傅里叶变换做了一些简单的模式比较。结果......不是很好,也不是很糟糕 - 总比没有好,但绝对不完美;)我会说这是开始的好点。 带有代码和图像的包(你的 + 一些来自谷歌的)是 here。代码:

import cv2
import numpy as np
from collections import OrderedDict
import operator


def shirt_fft(img, face_pos, title):
    shirt_rect_pos = face_pos[0]
    # print shirt_rect_pos
    shirt_rect_pos[1] += 2*shirt_rect_pos[3] #move down (by 2 * its height) rectangle with face - now it will point shirt sample
    shirt_sample = img[shirt_rect_pos[1]:shirt_rect_pos[1]+shirt_rect_pos[3], shirt_rect_pos[0]:shirt_rect_pos[0]+shirt_rect_pos[2]].copy() #crop shirt sample from image
    shirt_sample = cv2.resize(shirt_sample, dsize=(256, 256)) #resize sample to (256,256)
    # cv2.imshow(title+' shirt sample', shirt_sample)

    shirt_sample_gray = cv2.cvtColor(shirt_sample, cv2.COLOR_BGR2GRAY) #convert to gray colorspace

    f = np.fft.fft2(shirt_sample_gray) #calculate fft
    fshift = np.fft.fftshift(f) #shift - now the brightest poitn will be in the middle
    # fshift = fshift.astype(np.float32)
    magnitude_spectrum = 20*np.log(np.abs(fshift)) # calculate magnitude spectrum (it's easier to show)
    print magnitude_spectrum.max(), magnitude_spectrum.min(), magnitude_spectrum.mean(), magnitude_spectrum.dtype
    magnitude_spectrum = cv2.normalize(magnitude_spectrum, alpha=255.0, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1) #normalize the result and convert to 8uc1 (1 channe with 8 bits - unsigned char) datatype
    print magnitude_spectrum.max(), magnitude_spectrum.min(), magnitude_spectrum.mean(), magnitude_spectrum.dtype
    # cv2.imshow(title+' fft magnitude', magnitude_spectrum)
    magnitude_spectrum_original = magnitude_spectrum.copy()
    # temp, magnitude_spectrum = cv2.threshold(magnitude_spectrum, magnitude_spectrum.max()*0.75, 255.0, cv2.THRESH_TOZERO)
    # temp, magnitude_spectrum = cv2.threshold(magnitude_spectrum, 125, 255.0, cv2.THRESH_TOZERO)
    # temp, magnitude_spectrum = cv2.threshold(magnitude_spectrum, 250, 255.0, cv2.THRESH_TOZERO_INV) #clear the brightest part
    temp, magnitude_spectrum = cv2.threshold(magnitude_spectrum, 200, 255.0, cv2.THRESH_TOZERO) #clear all values from 0 to 200 - removes noise etc
    # cv2.imshow(title+' fft magnitude thresholded', magnitude_spectrum)
    # cv2.waitKey(1)

    # if chr(cv2.waitKey(5000)) == 'q':
        # quit()

    # return fshift
    return shirt_sample_gray, magnitude_spectrum_original, magnitude_spectrum

paths = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', 'plain1.jpg', 'plain2.jpg', 'plain3.jpg', 'plain4.jpg', 'stripes1.jpg', 'stripes2.jpg']
haar_cascade = cv2.CascadeClassifier('C:\\DevTools\\src\\opencv\\data\\haarcascades\\haarcascade_frontalface_default.xml') #change it to path to face cascade - it's inside opencv folder

fft_dict = OrderedDict()
results_img = None

for path in paths:
    img = cv2.imread(path)
    face_pos = haar_cascade.detectMultiScale(img, 1.3, 5, cv2.CASCADE_FIND_BIGGEST_OBJECT)
    if len(face_pos) == 0: #if haar cascade failed to find any face, try again with different (more accurate, but slower) settings
        face_pos = haar_cascade.detectMultiScale(img, 1.1, 3, cv2.CASCADE_FIND_BIGGEST_OBJECT)
    # result = process_image(img, face_pos, path)
    # cv2.imwrite('result_' + path, result) #save the result
    results = shirt_fft(img, face_pos, path)
    if results_img is None:
        results_img = np.hstack(results)
    else:
        results_img = np.vstack((results_img, np.hstack(results)))
    fft_dict[path] = results[2]

similarity_dict = 
cv2.imshow('results_img', results_img)
cv2.waitKey(1)


#for each image calcualte value of correlation with each other image
for i in range(len(fft_dict.keys())):
    for j in range(i+1, len(fft_dict.keys())):
    # for j in range(i, len(fft_dict.keys())):
        key1, key2 = fft_dict.keys()[i], fft_dict.keys()[j]
        print 'pair: ', key1, key2 
        img1 = fft_dict[key1]
        img2 = fft_dict[key2].copy()
        # img2 = img2[10:246, 10:246]
        correlation = cv2.matchTemplate(img1, img2, cv2.TM_CCORR_NORMED)
        # correlation = cv2.matchTemplate(img1, img2, cv2.TM_SQDIFF_NORMED)
        # print correlation
        print correlation.shape, correlation.dtype, correlation.max()
        similarity_dict[key1 + ' - ' + key2] = correlation.max()
        # similarity_dict[key1 + ' - ' + key2] = correlation

#sort values (from best to worst matches)
sorted_similarity_dict = sorted(similarity_dict.items(), key=operator.itemgetter(1), reverse=True)
print "final result: "
for a in sorted_similarity_dict:
    print a


cv2.waitKey(50000)

有些行被注释了-你可以尝试使用它们,也许你会取得更好的效果。 基本算法非常简单 - 对于每张图片:

从图像中剪切衬衫样本(只需将带有面部的矩形向下移动 2* 高度) 将此矩形转换为灰色颜色空间并将大小调整为 (256, 256) 计算此样本的 fft 计算 fft 变换的 magnite 谱 标准化(从 0 到 255) 阈值(清除所有值

现在我们可以计算该图像在所有衬衫样本之间的标准化互相关。高结果 -> 相似样本。最终结果:

('plain1.jpg - plain3.jpg', 1.0)  
('plain3.jpg - plain4.jpg', 1.0)  
('plain1.jpg - plain4.jpg', 1.0)  
('stripes1.jpg - stripes2.jpg', 0.54650664)  
('1.jpg - 3.jpg', 0.52512592)  
('plain1.jpg - stripes1.jpg', 0.45395589)  
('plain3.jpg - stripes1.jpg', 0.45395589)  
('plain4.jpg - stripes1.jpg', 0.45395589)  
('plain1.jpg - plain2.jpg', 0.39764369)  
('plain2.jpg - plain4.jpg', 0.39764369)  
('plain2.jpg - plain3.jpg', 0.39764369)  
('2.jpg - stripes1.jpg', 0.36927304)  
('2.jpg - plain3.jpg', 0.35678366)  
('2.jpg - plain4.jpg', 0.35678366)  
('2.jpg - plain1.jpg', 0.35678366)  
('1.jpg - plain1.jpg', 0.28958824)  
('1.jpg - plain3.jpg', 0.28958824)  
('1.jpg - plain4.jpg', 0.28958824)  
('2.jpg - 3.jpg', 0.27775836)  
('4.jpg - plain3.jpg', 0.2560707)  
('4.jpg - plain1.jpg', 0.2560707)  
('4.jpg - plain4.jpg', 0.2560707)  
('3.jpg - stripes1.jpg', 0.25498456)  
('4.jpg - plain2.jpg', 0.24522379)  
('1.jpg - 2.jpg', 0.2445447)  
('plain4.jpg - stripes2.jpg', 0.24032137)  
('plain3.jpg - stripes2.jpg', 0.24032137)  
('plain1.jpg - stripes2.jpg', 0.24032137)  
('3.jpg - stripes2.jpg', 0.23217434)  
('plain2.jpg - stripes2.jpg', 0.22518013)  
('2.jpg - stripes2.jpg', 0.19549081)  
('plain2.jpg - stripes1.jpg', 0.1805127)  
('3.jpg - plain4.jpg', 0.14908621)  
('3.jpg - plain1.jpg', 0.14908621)  
('3.jpg - plain3.jpg', 0.14908621)  
('4.jpg - stripes2.jpg', 0.14738286)  
('2.jpg - plain2.jpg', 0.14187276)  
('3.jpg - 4.jpg', 0.13638313)  
('1.jpg - stripes1.jpg', 0.13146029)  
('4.jpg - stripes1.jpg', 0.11624481)  
('1.jpg - plain2.jpg', 0.11515292)  
('2.jpg - 4.jpg', 0.091361843)  
('1.jpg - 4.jpg', 0.074155055)  
('1.jpg - stripes2.jpg', 0.069594234)  
('3.jpg - plain2.jpg', 0.059283193)  

所有衬衫样本的图像,幅度谱(阈值之前和之后)在这里:

图片名称(与这张大图上的样本顺序相同):['1.jpg', '2.jpg', '3.jpg', '4.jpg', 'plain1.jpg', 'plain2.jpg', 'plain3.jpg', 'plain4.jpg', 'stripes1.jpg', 'stripes2.jpg'] 如您所见,具有相同模式的样本的阈值图像非常相似。我认为如果你能找到一种更好的方法来比较这些图像(阈值幅度谱),这个解决方案可能会更好。

edit2: 只是一个简单的想法——在你从很多衬衫中裁剪衬衫样本后,你可以尝试训练一些分类器,然后使用这个分类器识别它们的模式。寻找有关训练 Haar 或 LBP(本地二进制模式)级联的教程。

【讨论】:

你能帮我如何对最常见的衬衫使用傅里叶变换吗? 请详细说明第二点“使用傅里叶变换来查看这件衬衫中最常见的频率。对于普通衬衫,它的频率应该比脱衣的要小得多。”。如果可能的话支持代码 我没有第二部分的任何代码,不幸的是我现在不能花时间在上面。这只是我听说过的东西,我没有自己使用过。在这里cns-alumni.bu.edu/~slehar/fourier/fourier.html 你可以阅读一些关于傅里叶变换的基本知识。您应该尝试计算图像的傅立叶变换并比较它们 - 如果 2 幅图像的 FFT 结果相似,则它们很可能代表相似的模式。我不是这方面的专家,所以我无法为您提供更多帮助,但您应该可以在互联网上找到更多信息。 嗯,我也听说过使用 fft,但我不知道如何使用。如果问题不大,请帮忙,否则无论如何都很好,谢谢 查看更新的答案。它并不完美,但绝对应该足以开始并理解我在说什么。

以上是关于仅使用图像而不是通过标签从数据库中查找相似图像的主要内容,如果未能解决你的问题,请参考以下文章

在数据库的特定目录中查找重复或相似的图像

使用sql将图像从客户端发送到数据库

通过 URL 通过代理向内存数据提供图像数据

使用SIFT搜索图像数据库

图库意图仅显示图像或视频而不是两者?

细粒度图像分类