• Elasticsearch:运用向量搜索通过图像搜索找到你的小狗


    作者:ALEX SALGADO

    你是否曾经遇到过这样的情况:你在街上发现了一只丢失的小狗,但不知道它是否有主人? 了解如何使用向量搜索或图像搜索来做到这一点。

    通过图像搜索找到你的小狗

    您是否曾经遇到过这样的情况:你在街上发现了一只丢失的小狗,但不知道它是否有主人? 在 Elasticsearch 中通过图像处理使用向量搜索,此任务可以像漫画一样简单。

    想象一下这个场景:在一个喧闹的下午,路易吉,一只活泼的小狗,在 Elastic 周围散步时不小心从皮带上滑落,发现自己独自在繁忙的街道上徘徊。 绝望的主人正在各个角落寻找他,用充满希望和焦虑的声音呼唤着他的名字。 与此同时,在城市的某个地方,一位细心的人注意到这只小狗表情茫然,决定提供帮助。 很快,他们给路易吉拍了一张照片,并利用所在公司的向量图像搜索技术,开始在数据库中进行搜索,希望能找到有关这只小逃亡者主人的线索。

    如果你想在阅读时跟踪并执行代码,请访问在 Jupyter Notebook (Google Collab) 上运行的文件 Python 代码。

    架构

    我们将使用 Jupyter Notebook 来解决这个问题。 首先,我们下载要注册的小狗的图像,然后安装必要的软件包。

    注意:要实现此示例,我们需要在使用图像数据填充向量数据库之前在 Elasticsearch 中创建索引。

    • 首先部署 Elasticsearch(我们为你提供 14 天的免费试用期)。
    • 在此过程中,请记住存储要在 Python 代码中使用的凭据(用户名、密码)。
    • 为简单起见,我们将使用在 Jupyter Notebook (Google Colab) 上运行的 Python 代码。

    下载代码 zip 文件并安装必要的软件包

    1. !git clone https://github.com/salgado/image-search-01.git
    2. !pip -q install Pillow sentence_transformers elasticsearch

    让我们创建 4 个类来帮助我们完成这项任务,它们是:

    • Util 类:负责处理前期任务和 Elasticsearch 索引维护。
    • Dog 类:负责存储我们小狗的属性。
    • DogRepository 类:负责数据持久化任务。
    • DogService 类:它将成为我们的服务层。

    Util class

    Util 类提供了用于管理 Elasticsearch 索引的实用方法,例如创建和删除索引。

    方法

    • create_index():在 Elasticsearch 中创建一个新索引。
    • delete_index():从 Elasticsearch 中删除现有索引。
    1. ### Util class
    2. from elasticsearch import Elasticsearch, exceptions as es_exceptions
    3. import getpass
    4. class Util:
    5. @staticmethod
    6. def get_index_name():
    7. return "dog-image-index"
    8. @staticmethod
    9. def get_connection():
    10. es_cloud_id = getpass.getpass('Enter Elastic Cloud ID: ')
    11. es_user = getpass.getpass('Enter cluster username: ')
    12. es_pass = getpass.getpass('Enter cluster password: ')
    13. es = Elasticsearch(cloud_id=es_cloud_id,
    14. basic_auth=(es_user, es_pass)
    15. )
    16. es.info() # should return cluster info
    17. return es
    18. @staticmethod
    19. def create_index(es: Elasticsearch, index_name: str):
    20. # Specify index configuration
    21. index_config = {
    22. "settings": {
    23. "index.refresh_interval": "5s",
    24. "number_of_shards": 1
    25. },
    26. "mappings": {
    27. "properties": {
    28. "image_embedding": {
    29. "type": "dense_vector",
    30. "dims": 512,
    31. "index": True,
    32. "similarity": "cosine"
    33. },
    34. "dog_id": {
    35. "type": "keyword"
    36. },
    37. "breed": {
    38. "type" : "keyword"
    39. },
    40. "image_path" : {
    41. "type" : "keyword"
    42. },
    43. "owner_name" : {
    44. "type" : "keyword"
    45. },
    46. "exif" : {
    47. "properties" : {
    48. "location": {
    49. "type": "geo_point"
    50. },
    51. "date": {
    52. "type": "date"
    53. }
    54. }
    55. }
    56. }
    57. }
    58. }
    59. # Create index
    60. if not es.indices.exists(index=index_name):
    61. index_creation = es.indices.create(index=index_name, ignore=400, body=index_config)
    62. print("index created: ", index_creation)
    63. else:
    64. print("Index already exists.")
    65. @staticmethod
    66. def delete_index(es: Elasticsearch, index_name: str):
    67. # delete index
    68. es.indices.delete(index=index_name, ignore_unavailable=True)

    如果你是自构建的集群,你可以参考文章 “Elasticsearch:关于在 Python 中使用 Elasticsearch 你需要知道的一切 - 8.x” 来了解如何使用客户端来连接 Elasticsearch 集群。

    Dog class

    Dog 类代表一只狗及其属性,例如 ID、图像路径、品种、所有者姓名和图像嵌入。

    属性

    • dog_id:狗的 ID。
    • image_path:狗图像的路径。
    • breed:狗的品种。
    • owner_name:狗的主人的名字。
    • image_embedding:狗的图像嵌入。

    方法:

    • __init__():初始化一个新的 Dog 对象。
    • generate_embedding():生成狗的图像嵌入。
    • to_dict():将 Dog 对象转换为字典。
    1. import os
    2. from sentence_transformers import SentenceTransformer
    3. from PIL import Image
    4. # domain class
    5. class Dog:
    6. model = SentenceTransformer('clip-ViT-B-32')
    7. def __init__(self, dog_id, image_path, breed, owner_name):
    8. self.dog_id = dog_id
    9. self.image_path = image_path
    10. self.breed = breed
    11. self.image_embedding = None
    12. self.owner_name = owner_name
    13. @staticmethod
    14. def get_embedding(image_path: str):
    15. temp_image = Image.open(image_path)
    16. return Dog.model.encode(temp_image)
    17. def generate_embedding(self):
    18. self.image_embedding = Dog.get_embedding(self.image_path)
    19. def __repr__(self):
    20. return (f"Image(dog_id={self.dog_id}, image_path={self.image_path}, "
    21. f"breed={self.breed}, image_embedding={self.image_embedding}, "
    22. f"owner_name={self.owner_name})")
    23. def to_dict(self):
    24. return {
    25. 'dog_id': self.dog_id,
    26. 'image_path': self.image_path,
    27. 'breed': self.breed,
    28. 'image_embedding': self.image_embedding,
    29. 'owner_name': self.owner_name
    30. }

    DogRepository class

    DogRepository 类提供了从 Elasticsearch 保存和检索狗数据的方法。

    方法

    • insert():将一条新狗插入 Elasticsearch。
    • bulk_insert():将多条狗批量插入到Elasticsearch中。
    • search_by_image():通过图像搜索相似的狗。
    1. from typing import List, Dict
    2. # persistence layer
    3. class DogRepository:
    4. def __init__(self, es_client: Elasticsearch, index_name: str = "dog-image-index"):
    5. self.es_client = es_client
    6. self._index_name = index_name
    7. Util.create_index(es_client, index_name)
    8. def insert(self, dog: Dog):
    9. dog.generate_embedding()
    10. document = dog.__dict__
    11. self.es_client.index(index=self._index_name, document=document)
    12. def bulk_insert(self, dogs: List[Dog]):
    13. operations = []
    14. for dog in dogs:
    15. operations.append({"index": {"_index": self._index_name}})
    16. operations.append(dog.__dict__)
    17. self.es_client.bulk(body=operations)
    18. def search_by_image(self, image_embedding: List[float]):
    19. field_key = "image_embedding"
    20. knn = {
    21. "field": field_key,
    22. "k": 2,
    23. "num_candidates": 100,
    24. "query_vector": image_embedding,
    25. "boost": 100
    26. }
    27. # The fields to retrieve from the matching documents
    28. fields = ["dog_id", "breed", "owner_name","image_path", "image_embedding"]
    29. try:
    30. resp = self.es_client.search(
    31. index=self._index_name,
    32. body={
    33. "knn": knn,
    34. "_source": fields
    35. },
    36. size=1
    37. )
    38. # Return the search results
    39. return resp
    40. except Exception as e:
    41. print(f"An error occurred: {e}")
    42. return {}

    DogService Class

    DogService 类提供管理狗数据的业务逻辑,例如插入和搜索狗。

    方法

    • insert_dog():将一条新狗插入 Elasticsearch。
    • search_dogs_by_image():通过图像搜索相似的狗。
    1. from typing import List, Dict
    2. # persistence layer
    3. class DogRepository:
    4. def __init__(self, es_client: Elasticsearch, index_name: str = "dog-image-index"):
    5. self.es_client = es_client
    6. self._index_name = index_name
    7. Util.create_index(es_client, index_name)
    8. def insert(self, dog: Dog):
    9. dog.generate_embedding()
    10. document = dog.__dict__
    11. self.es_client.index(index=self._index_name, document=document)
    12. def bulk_insert(self, dogs: List[Dog]):
    13. operations = []
    14. for dog in dogs:
    15. operations.append({"index": {"_index": self._index_name}})
    16. operations.append(dog.__dict__)
    17. self.es_client.bulk(body=operations)
    18. def search_by_image(self, image_embedding: List[float]):
    19. field_key = "image_embedding"
    20. knn = {
    21. "field": field_key,
    22. "k": 2,
    23. "num_candidates": 100,
    24. "query_vector": image_embedding,
    25. "boost": 100
    26. }
    27. # The fields to retrieve from the matching documents
    28. fields = ["dog_id", "breed", "owner_name","image_path", "image_embedding"]
    29. try:
    30. resp = self.es_client.search(
    31. index=self._index_name,
    32. body={
    33. "knn": knn,
    34. "_source": fields
    35. },
    36. size=1
    37. )
    38. # Return the search results
    39. return resp
    40. except Exception as e:
    41. print(f"An error occurred: {e}")
    42. return {}

    上面介绍的类(classes)为构建狗数据管理系统奠定了坚实的基础。 Util 类提供了用于管理 Elasticsearch 索引的实用方法。 Dog 类代表狗的属性。 DogRepository 类提供了从 Elasticsearch 保存和检索狗数据的方法。 DogService 类提供了高效的狗数据管理的业务逻辑。

    主要代码

    我们的代码基本上有两个主要流程或阶段:

    • 使用基本信息和图像注册狗。
    • 使用新图像执行搜索以在向量数据库中查找狗。

    第一阶段:注册小狗

    为了存储有关 Luigi 和其他公司的小狗的信息,我们将使用 Dog 类。

    为此,我们对序列进行如下的编程:

    开始为小狗登记

    1. # Start a connection
    2. es_db = Util.get_connection()
    3. Util.delete_index(es_db, Util.get_index_name())
    4. # Register one dog
    5. dog_repo = DogRepository(es_db, Util.get_index_name())
    6. dog_service = DogService(dog_repo)
    7. # Visualize the inserted Dog
    8. from IPython.display import display
    9. from IPython.display import Image as ShowImage
    10. filename = "/content/image-search-01/dataset/dogs/Luigi.png"
    11. display(ShowImage(filename=filename, width=300, height=300))

    输出:

    登记 Luigi

    1. dog = Dog('Luigi', filename, 'Jack Russel/Rat Terrier', 'Ully')
    2. dog_service.register_dog(dog)

    登记所有其他小狗

    1. import json
    2. # JSON data
    3. data = '''
    4. {
    5. "dogs": [
    6. {"dog_id": "Buddy", "image_path": "", "breed": "Labrador Retriever", "owner_name": "Berlin Red"},
    7. {"dog_id": "Bella", "image_path": "", "breed": "German Shepherd", "owner_name": "Tokyo Blue"},
    8. {"dog_id": "Charlie", "image_path": "", "breed": "Golden Retriever", "owner_name": "Paris Green"},
    9. {"dog_id": "Bigu", "image_path": "", "breed": "Beagle", "owner_name": "Lisbon Yellow"},
    10. {"dog_id": "Max", "image_path": "", "breed": "Bulldog", "owner_name": "Canberra Purple"},
    11. {"dog_id": "Luna", "image_path": "", "breed": "Poodle", "owner_name": "Wellington Brown"},
    12. {"dog_id": "Milo", "image_path": "", "breed": "Rottweiler", "owner_name": "Hanoi Orange"},
    13. {"dog_id": "Ruby", "image_path": "", "breed": "Boxer", "owner_name": "Ottawa Pink"},
    14. {"dog_id": "Oscar", "image_path": "", "breed": "Dachshund", "owner_name": "Kabul Black"},
    15. {"dog_id": "Zoe", "image_path": "", "breed": "Siberian Husky", "owner_name": "Cairo White"}
    16. ]
    17. }
    18. '''
    19. # Convert JSON string to Python dictionary
    20. dogs_data = json.loads(data)
    21. # Traverse the list and print dog_id of each dog
    22. image_dogs = "/content/image-search-01/dataset/dogs/"
    23. for dog_info in dogs_data["dogs"]:
    24. dog = Dog(dog_info["dog_id"], image_dogs + dog_info["dog_id"] + ".png" , dog_info["breed"], dog_info["owner_name"])
    25. dog_service.register_dog(dog)

    可视化新狗

    1. # visualize new dogs
    2. import matplotlib.pyplot as plt
    3. import matplotlib.image as mpimg
    4. import math
    5. image_dogs = "/content/image-search-01/dataset/dogs/"
    6. num_dogs = len(dogs_data["dogs"])
    7. cols = int(math.sqrt(num_dogs))
    8. rows = int(math.ceil(num_dogs / cols))
    9. # Configurar o tamanho da figura
    10. plt.figure(figsize=(5, 5))
    11. # Loop para exibir as imagens dos cães
    12. for i, dog_info in enumerate(dogs_data["dogs"]):
    13. filename = image_dogs + dog_info["dog_id"] + ".png"
    14. img = mpimg.imread(filename)
    15. plt.subplot(rows, cols, i+1) # (número de linhas, número de colunas, índice do subplot)
    16. plt.imshow(img)
    17. plt.axis('off')
    18. plt.show()

    输出:

    第二阶段:寻找丢失的狗

    现在我们已经登记了所有小狗,让我们进行搜索。 我们的开发人员拍了这张丢失小狗的照片。

    1. filename = "/content/image-search-01/dataset/lost-dogs/lost_dog1.png"
    2. display(ShowImage(filename=filename, width=300, height=300))

    输出:

    看看我们能找到这只可爱的小狗的主人吗?

    1. # find dog by image
    2. result = dog_service.find_dog_by_image(filename)

    获取结果

    让我们看看我们发现了什么......

    1. filename = result['hits']['hits'][0]['_source']['image_path']
    2. display(ShowImage(filename=filename, width=300, height=300))

    输出:

    瞧! 我们找到了!!!

    但谁将是所有者和他们的名字?

    1. # Print credentials
    2. print(result['hits']['hits'][0]['_source']['dog_id'])
    3. print(result['hits']['hits'][0]['_source']['breed'])
    4. print(result['hits']['hits'][0]['_source']['owner_name'])

    输出:

    • Luigi
    • Jack Russel/Rat Terrier
    • Ully

    好结局

    我们找到了路易吉!!! 我们通知 Ully 吧。

    1. filename = "/content/image-search-01/dataset/lost-dogs/Ully.png"
    2. display(ShowImage(filename=filename, width=300, height=300))

    输出:

    很快,Ully 和 Luigi 就团聚了。 小狗高兴地摇着尾巴,Ully 紧紧地抱住它,保证再也不会让它离开她的视线。 他们经历了一阵情感旋风,但现在他们在一起了,这才是最重要的。 就这样,Ully 和 Luigi 心中充满了爱和欢乐,从此幸福地生活在一起。

    结论

    在这篇博文中,我们探索了如何使用 Elasticsearch 通过向量搜索来寻找丢失的小狗。 我们演示了如何生成狗的图像嵌入,在 Elasticsearch 中对其进行索引,然后使用查询图像搜索相似的狗。 该技术可用于寻找丢失的宠物,以及识别图像中其他感兴趣的物体。

    向量搜索是一个强大的工具,可用于多种应用。 它特别适合需要根据外观搜索相似对象的任务,例如图像检索和对象识别。

    我们希望这篇博文能够提供丰富的信息,并且你会发现我们讨论的技术对你自己的项目很有用。

    原文:Finding your puppy with Image Search — Elastic Search Labs

  • 相关阅读:
    在simulink中提取结构体数组的成员
    ELK日志保留7天-索引生命周期策略
    第23篇 基于Qt实现PID温度加热控制系统
    阿里云服务器经济型e实例规格云服务器性能介绍
    再探Handler(下)(Handler核心原理最全解析)
    Redis持久化与主从复制
    基于Java毕业设计预防接种服务平台源码+系统+mysql+lw文档+部署软件
    HTML小游戏16 —— 消除游戏《魔法石》源码(附完整源码)
    软考高级系统架构设计师系列之:深入理解设计模式
    vs2010 c++ 解析 json(jsoncpp)
  • 原文地址:https://blog.csdn.net/UbuntuTouch/article/details/134438930