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

作者:ALEX SALGADO

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

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

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

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

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

架构

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

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

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

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

bash 复制代码
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 中删除现有索引。
python 复制代码
1.  ### Util class
2.  from elasticsearch import Elasticsearch, exceptions as es_exceptions
3.  import getpass

5.  class Util:
6.      @staticmethod
7.      def get_index_name():
8.        return "dog-image-index"

10.      @staticmethod
11.      def get_connection():
12.          es_cloud_id = getpass.getpass('Enter Elastic Cloud ID:  ')
13.          es_user = getpass.getpass('Enter cluster username:  ')
14.          es_pass = getpass.getpass('Enter cluster password:  ')

16.          es = Elasticsearch(cloud_id=es_cloud_id,
17.                            basic_auth=(es_user, es_pass)
18.                            )
19.          es.info() # should return cluster info
20.          return es

23.      @staticmethod
24.      def create_index(es: Elasticsearch, index_name: str):
25.          # Specify index configuration
26.          index_config = {
27.            "settings": {
28.              "index.refresh_interval": "5s",
29.              "number_of_shards": 1
30.            },
31.            "mappings": {
32.              "properties": {
33.                "image_embedding": {
34.                  "type": "dense_vector",
35.                  "dims": 512,
36.                  "index": True,
37.                  "similarity": "cosine"
38.                },
39.                "dog_id": {
40.                  "type": "keyword"
41.                },
42.                "breed": {
43.                  "type" : "keyword"
44.                },
45.                "image_path" : {
46.                  "type" : "keyword"
47.                },
48.                "owner_name" : {
49.                  "type" : "keyword"
50.                },
51.                "exif" : {
52.                  "properties" : {
53.                    "location": {
54.                      "type": "geo_point"
55.                    },
56.                    "date": {
57.                      "type": "date"
58.                    }
59.                  }
60.                }
61.              }
62.            }
63.          }

65.          # Create index
66.          if not es.indices.exists(index=index_name):
67.              index_creation = es.indices.create(index=index_name, ignore=400, body=index_config)
68.              print("index created: ", index_creation)
69.          else:
70.              print("Index  already exists.")

73.      @staticmethod
74.      def delete_index(es: Elasticsearch, index_name: str):
75.          # delete index
76.          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 对象转换为字典。
python 复制代码
1.  import os
2.  from sentence_transformers import SentenceTransformer
3.  from PIL import Image

5.  # domain class
6.  class Dog:
7.      model = SentenceTransformer('clip-ViT-B-32')

9.      def __init__(self, dog_id, image_path, breed, owner_name):
10.          self.dog_id = dog_id
11.          self.image_path = image_path
12.          self.breed = breed
13.          self.image_embedding = None
14.          self.owner_name = owner_name

16.      @staticmethod
17.      def get_embedding(image_path: str):
18.          temp_image = Image.open(image_path)
19.          return Dog.model.encode(temp_image)

21.      def generate_embedding(self):
22.          self.image_embedding = Dog.get_embedding(self.image_path)

24.      def __repr__(self):
25.          return (f"Image(dog_id={self.dog_id}, image_path={self.image_path}, "
26.                  f"breed={self.breed}, image_embedding={self.image_embedding}, "
27.                  f"owner_name={self.owner_name})")

29.      def to_dict(self):
30.          return {
31.              'dog_id': self.dog_id,
32.              'image_path': self.image_path,
33.              'breed': self.breed,
34.              'image_embedding': self.image_embedding,
35.              'owner_name': self.owner_name
36.          }

DogRepository class

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

方法

  • insert():将一条新狗插入 Elasticsearch。
  • bulk_insert():将多条狗批量插入到Elasticsearch中。
  • search_by_image():通过图像搜索相似的狗。
python 复制代码
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)

9.      def insert(self, dog: Dog):
10.          dog.generate_embedding()
11.          document = dog.__dict__
12.          self.es_client.index(index=self._index_name, document=document)

14.      def bulk_insert(self, dogs: List[Dog]):
15.          operations = []
16.          for dog in dogs:
17.              operations.append({"index": {"_index": self._index_name}})
18.              operations.append(dog.__dict__)
19.          self.es_client.bulk(body=operations)

21.      def search_by_image(self, image_embedding: List[float]):
22.        field_key = "image_embedding"

24.        knn = {
25.            "field": field_key,
26.            "k": 2,
27.            "num_candidates": 100,
28.            "query_vector": image_embedding,
29.            "boost": 100
30.        }

32.        # The fields to retrieve from the matching documents
33.        fields = ["dog_id", "breed", "owner_name","image_path", "image_embedding"]

35.        try:
36.            resp = self.es_client.search(
37.                index=self._index_name,
38.                body={
39.                    "knn": knn,
40.                    "_source": fields
41.                },
42.                size=1
43.            )
44.            # Return the search results
45.            return resp
46.        except Exception as e:
47.            print(f"An error occurred: {e}")
48.            return {}

DogService Class

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

方法

  • insert_dog():将一条新狗插入 Elasticsearch。
  • search_dogs_by_image():通过图像搜索相似的狗。
python 复制代码
 2.  from typing import List, Dict
3.  # persistence layer
4.  class DogRepository:
5.      def __init__(self, es_client: Elasticsearch, index_name: str = "dog-image-index"):
6.          self.es_client = es_client
7.          self._index_name = index_name
8.          Util.create_index(es_client, index_name)

10.      def insert(self, dog: Dog):
11.          dog.generate_embedding()
12.          document = dog.__dict__
13.          self.es_client.index(index=self._index_name, document=document)

15.      def bulk_insert(self, dogs: List[Dog]):
16.          operations = []
17.          for dog in dogs:
18.              operations.append({"index": {"_index": self._index_name}})
19.              operations.append(dog.__dict__)
20.          self.es_client.bulk(body=operations)

22.      def search_by_image(self, image_embedding: List[float]):
23.        field_key = "image_embedding"

25.        knn = {
26.            "field": field_key,
27.            "k": 2,
28.            "num_candidates": 100,
29.            "query_vector": image_embedding,
30.            "boost": 100
31.        }

33.        # The fields to retrieve from the matching documents
34.        fields = ["dog_id", "breed", "owner_name","image_path", "image_embedding"]

36.        try:
37.            resp = self.es_client.search(
38.                index=self._index_name,
39.                body={
40.                    "knn": knn,
41.                    "_source": fields
42.                },
43.                size=1
44.            )
45.            # Return the search results
46.            return resp
47.        except Exception as e:
48.            print(f"An error occurred: {e}")
49.            return {}

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

主要代码

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

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

第一阶段:注册小狗

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

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

开始为小狗登记

ini 复制代码
 2.  # Start a connection
3.  es_db = Util.get_connection()
4.  Util.delete_index(es_db, Util.get_index_name())

6.  # Register one dog
7.  dog_repo = DogRepository(es_db, Util.get_index_name())
8.  dog_service = DogService(dog_repo)

10.  # Visualize the inserted Dog
11.  from IPython.display import display
12.  from IPython.display import Image as ShowImage

14.  filename = "/content/image-search-01/dataset/dogs/Luigi.png"
15.  display(ShowImage(filename=filename, width=300, height=300))

输出:

登记 Luigi

ini 复制代码
1.  dog = Dog('Luigi', filename, 'Jack Russel/Rat Terrier', 'Ully')

3.  dog_service.register_dog(dog)

登记所有其他小狗

makefile 复制代码
1.  import json

3.  # JSON data
4.  data = '''
5.  {
6.    "dogs": [
7.      {"dog_id": "Buddy", "image_path": "", "breed": "Labrador Retriever", "owner_name": "Berlin Red"},
8.      {"dog_id": "Bella", "image_path": "", "breed": "German Shepherd", "owner_name": "Tokyo Blue"},
9.      {"dog_id": "Charlie", "image_path": "", "breed": "Golden Retriever", "owner_name": "Paris Green"},
10.      {"dog_id": "Bigu", "image_path": "", "breed": "Beagle", "owner_name": "Lisbon Yellow"},
11.      {"dog_id": "Max", "image_path": "", "breed": "Bulldog", "owner_name": "Canberra Purple"},
12.      {"dog_id": "Luna", "image_path": "", "breed": "Poodle", "owner_name": "Wellington Brown"},
13.      {"dog_id": "Milo", "image_path": "", "breed": "Rottweiler", "owner_name": "Hanoi Orange"},
14.      {"dog_id": "Ruby", "image_path": "", "breed": "Boxer", "owner_name": "Ottawa Pink"},
15.      {"dog_id": "Oscar", "image_path": "", "breed": "Dachshund", "owner_name": "Kabul Black"},
16.      {"dog_id": "Zoe", "image_path": "", "breed": "Siberian Husky", "owner_name": "Cairo White"}
17.    ]
18.  }
19.  '''

21.  # Convert JSON string to Python dictionary
22.  dogs_data = json.loads(data)

24.  # Traverse the list and print dog_id of each dog
25.  image_dogs = "/content/image-search-01/dataset/dogs/"
26.  for dog_info in dogs_data["dogs"]: 
27.      dog = Dog(dog_info["dog_id"], image_dogs + dog_info["dog_id"] + ".png" , dog_info["breed"], dog_info["owner_name"])
28.      dog_service.register_dog(dog)

可视化新狗

ini 复制代码
1.  # visualize new dogs
2.  import matplotlib.pyplot as plt
3.  import matplotlib.image as mpimg
4.  import math

6.  image_dogs = "/content/image-search-01/dataset/dogs/"
7.  num_dogs = len(dogs_data["dogs"])

9.  cols = int(math.sqrt(num_dogs))
10.  rows = int(math.ceil(num_dogs / cols))

12.  # Configurar o tamanho da figura
13.  plt.figure(figsize=(5, 5))

15.  # Loop para exibir as imagens dos cães
16.  for i, dog_info in enumerate(dogs_data["dogs"]): 
17.      filename = image_dogs + dog_info["dog_id"] + ".png"
18.      img = mpimg.imread(filename)

20.      plt.subplot(rows, cols, i+1)  # (número de linhas, número de colunas, índice do subplot)
21.      plt.imshow(img)
22.      plt.axis('off')

24.  plt.show()

输出:

第二阶段:寻找丢失的狗

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

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

输出:

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

ini 复制代码
1.  # find dog by image
2.  result = dog_service.find_dog_by_image(filename)

获取结果

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

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

输出:

瞧! 我们找到了!!!

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

css 复制代码
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 吧。

ini 复制代码
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

相关推荐
Elastic 中国社区官方博客1 天前
使用 Azure LLM Functions 与 Elasticsearch 构建更智能的查询体验
大数据·人工智能·elasticsearch·microsoft·搜索引擎·全文检索·azure
Mr.stupidCoder2 天前
Git将本地文件推送到GitHub仓库
git·elasticsearch·github
RocketJ2 天前
mac电脑.sh文件,用来清除git当前分支
git·elasticsearch·macos
Elastic 中国社区官方博客2 天前
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
大数据·开发语言·javascript·elasticsearch·搜索引擎·全文检索·apache
猕员桃2 天前
《Elasticsearch 分布式搜索在聊天记录检索中的深度优化》
分布式·elasticsearch·wpf
追随远方2 天前
Android OpenSL ES 音频播放完整实现指南
android·elasticsearch·音视频
G皮T3 天前
【Elasticsearch】正排索引、倒排索引(含实战案例)
大数据·elasticsearch·搜索引擎·kibana·倒排索引·搜索·正排索引
G皮T3 天前
【Elasticsearch】Elasticsearch 近实时高速查询原理
大数据·elasticsearch·搜索引擎·全文检索·倒排索引·搜索·nrt
aini_lovee3 天前
python在容器内克隆拉取git私有仓库
git·python·elasticsearch
在未来等你4 天前
SQL进阶之旅 Day 29:NoSQL结合使用策略
redis·sql·mongodb·elasticsearch·postgresql·nosql·hybrid-database