导 读
本文将手把手教你使用CLIP和VectorDB构建一个以图搜图的工具。
背景介绍
图像到图像搜索(以图搜图)是什么意思?
在传统的图像搜索引擎中,您通常使用文本查询来查找图像,搜索引擎根据与这些图像关联的关键字返回结果。另一方面,在图像到图像搜索中,您从图像作为查询开始,系统会检索在视觉上类似于查询图像的图像。
想象你有一幅画,就像一幅美丽的日落图画。现在,你想找到其他与它相似的画作,但你无法用言语来描述它。相反,你向计算机展示你的画作,它会浏览它所知道的所有画作,并找到非常相似的画作,即使它们有不同的名称或描述。这就是图像到图像搜索--也就是以图搜图。
我可以用这个搜索工具做什么?
图像到图像搜索引擎开启了令人兴奋的可能性:
实现步骤
CLIP 和 VectorDB:简介
图 1 显示了在矢量数据库中索引图像数据集的步骤。
步骤 1:收集图像数据集(可以是原始/未标记图像)。
步骤 2:CLIP 是一种嵌入模型,用于提取图像的高维向量表示,以捕获其语义和感知特征。参考链接:
https://arxiv.org/abs/2103.00020
步骤 3:这些图像被编码到嵌入空间中,其中(图像的)嵌入在 Redis 或 Milvus 等矢量数据库中建立索引。
在查询时(图 2),样本图像通过相同的 CLIP 编码器来获取其嵌入。执行向量相似性搜索以有效地找到前 k 个最接近的数据库图像向量。与给定查询具有最高相似度得分的图像将作为视觉上最相似的搜索结果返回。
余弦相似度是 VectorDB 应用程序中最常用的相似度度量:
参考链接:
https://en.wikipedia.org/wiki/Cosine_similarity
构建图像到图像搜索引擎
【1】数据集——指环王
我们使用Google搜索来查询与关键字“指环王电影场景”相关的图片。在此代码之上,我们创建了一个函数来根据给定的查询检索 100 个 url。
import requests, lxml, re, json, urllib.request
from bs4 import BeautifulSoup
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36"
}
params = {
"q": "the lord of the rings film scenes", # search query
"tbm": "isch", # image results
"hl": "en", # language of the search
"gl": "us", # country where search comes from
"ijn": "0" # page number
}
html = requests.get("https://www.google.com/search", params=params, headers=headers, timeout=30)
soup = BeautifulSoup(html.text, "lxml")
def get_images():
"""
https://kodlogs.com/34776/json-decoder-jsondecodeerror-expecting-property-name-enclosed-in-double-quotes
if you try to json.loads() without json.dumps() it will throw an error:
"Expecting property name enclosed in double quotes"
"""
google_images = []
all_script_tags = soup.select("script")
# # https://regex101.com/r/48UZhY/4
matched_images_data = "".join(re.findall(r"AF_initDataCallback\(([^<]+)\);", str(all_script_tags)))
matched_images_data_fix = json.dumps(matched_images_data)
matched_images_data_json = json.loads(matched_images_data_fix)
# https://regex101.com/r/VPz7f2/1
matched_google_image_data = re.findall(r'\"b-GRID_STATE0\"(.*)sideChannel:\s?{}}', matched_images_data_json)
# https://regex101.com/r/NnRg27/1
matched_google_images_thumbnails = ", ".join(
re.findall(r'\[\"(https\:\/\/encrypted-tbn0\.gstatic\.com\/images\?.*?)\",\d+,\d+\]',
str(matched_google_image_data))).split(", ")
thumbnails = [
bytes(bytes(thumbnail, "ascii").decode("unicode-escape"), "ascii").decode("unicode-escape") for thumbnail in matched_google_images_thumbnails
]
# removing previously matched thumbnails for easier full resolution image matches.
removed_matched_google_images_thumbnails = re.sub(
r'\[\"(https\:\/\/encrypted-tbn0\.gstatic\.com\/images\?.*?)\",\d+,\d+\]', "", str(matched_google_image_data))
# https://regex101.com/r/fXjfb1/4
# https://stackoverflow.com/a/19821774/15164646
matched_google_full_resolution_images = re.findall(r"(?:'|,),\[\"(https:|http.*?)\",\d+,\d+\]", removed_matched_google_images_thumbnails)
full_res_images = [
bytes(bytes(img, "ascii").decode("unicode-escape"), "ascii").decode("unicode-escape") for img in matched_google_full_resolution_images
]
return full_res_images
【2】用CLIP获取嵌入向量
注意:找到所有库和辅助函数来运行此Colab 笔记本中的代码。
提取我们图像集的所有嵌入。
def get_all_image_embeddings_from_urls(dataset, processor, model, device, num_images=100):
embeddings = []
# Limit the number of images to process
dataset = dataset[:num_images]
working_urls = []
#for image_url in dataset['image_url']:
for image_url in dataset:
if check_valid_URL(image_url):
try:
# Download the image
response = requests.get(image_url)
image = Image.open(BytesIO(response.content)).convert("RGB")
# Get the embedding for the image
embedding = get_single_image_embedding(image, processor, model, device)
#embedding = get_single_image_embedding(image)
embeddings.append(embedding)
working_urls.append(image_url)
except Exception as e:
print(f"Error processing image from {image_url}: {e}")
else:
print(f"Invalid or inaccessible image URL: {image_url}")
return embeddings, working_urls
LOR_embeddings, valid_urls = get_all_image_embeddings_from_urls(list_image_urls, processor, model, device, num_images=100)
Invalid or inaccessible image URL: https://blog.frame.io/wp-content/uploads/2021/12/lotr-forced-perspective-cart-bilbo-gandalf.jpg
Invalid or inaccessible image URL: https://www.cineworld.co.uk/static/dam/jcr:9389da12-c1ea-4ef6-9861-d55723e4270e/Screenshot%202020-08-07%20at%2008.48.49.png
Invalid or inaccessible image URL: https://upload.wikimedia.org/wikipedia/en/3/30/Ringwraithpic.JPG
100 个网址中有 97 个包含有效图像。
【3】将我们的嵌入存储在 Pinecone 中
在本文中,我们将使用 Pinecone 作为 VectorDB 的示例,但您也可以使用各种其他 VectorDB 的提供程序,例如:QDrant、Milvus、Mongo 或 Redis。
您可以在我们关于 VectorDB 的文章中找到这些矢量数据库服务的很好的比较。
要将我们的嵌入存储在 Pinecone [2] 中,您首先需要创建一个Pinecone帐户。之后,创建一个名为“image-to-image”的索引。
pinecone.init(
api_key = "YOUR-API-KEY",
environment="gcp-starter" # find next to API key in console
)
my_index_name = "image-to-image"
vector_dim = LOR_embeddings[0].shape[1]
if my_index_name not in pinecone.list_indexes():
print("Index not present")
# Connect to the index
my_index = pinecone.Index(index_name = my_index_name)
创建一个函数来将数据存储在 Pinecone 索引中。
def create_data_to_upsert_from_urls(dataset, embeddings, num_images):
metadata = []
image_IDs = []
for index in range(len(dataset)):
metadata.append({
'ID': index,
'image': dataset[index]
})
image_IDs.append(str(index))
image_embeddings = [arr.tolist() for arr in embeddings]
data_to_upsert = list(zip(image_IDs, image_embeddings, metadata))
return data_to_upsert
运行上述函数得到:
LOR_data_to_upsert = create_data_to_upsert_from_urls(valid_urls,
LOR_embeddings, len(valid_urls))
my_index.upsert(vectors = LOR_data_to_upsert)
# {'upserted_count': 97}
my_index.describe_index_stats()
# {'dimension': 512,
# 'index_fullness': 0.00097,
# 'namespaces': {'': {'vector_count': 97}},
# 'total_vector_count': 97
【4】测试我们的图像到图像搜索工具
# For a random image
n = random.randint(0,len(valid_urls)-1)
print(f"Sample image with index {n} in {valid_urls[n]}")
Sample image with index 47 in
https://www.intofilm.org/intofilm-production/scaledcropped/870x489https%3A/s3-eu-west-1.amazonaws.com/images.cdn.filmclub.org/film__3930-the-lord-of-the-rings-the-fellowship-of-the-ring--hi_res-a207bd11.jpg/film__3930-the-lord-of-the-rings-the-fellowship-of-the-ring--hi_res-a207bd11.jpg
# 1. Get the image from url
LOR_image_query = get_image(valid_urls[n])
# 2. Obtain embeddings (via CLIP) for the given image
LOR_query_embedding = get_single_image_embedding(LOR_image_query, processor, model, device).tolist()
# 3. Search on Vector DB index for similar images to "LOR_query_embedding"
LOR_results = my_index.query(LOR_query_embedding, top_k=3, include_metadata=True)
# 4. See the results
plot_top_matches_seaborn(LOR_results)
上图显示了我们的图像到图像搜索工具获得的结果。所有这些都描绘了至少两个人物在开放的背景中行走。类似风景。具体来说,ID 47 的样本获得最高相似度得分 1.0。这并不奇怪,因为我们的数据集包含查询中使用的原始图像(图 3)。接下来最相似的样本是平局:ID 63 和 ID 30 的得分均为 0.77。
【5】如果有100万张甚至1亿张图片我该怎么办?
您可能已经意识到,构建一个工具通过从 Google 搜索中查询一些图像来进行图像到图像搜索是很有趣的。但是,如果您实际上拥有超过 1 亿张图像的数据集怎么办?🤔
在这种情况下,您可能会构建一个系统而不是一个工具。然而,建立一个可扩展的系统并不是一件容易的事。此外,还涉及许多成本(例如,存储成本、维护、编写实际代码)。
本文分享自 OpenCV与AI深度学习 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!