[Elasticsearch] Similar image search by image or natural language(2)

Similar image search by image or natural language(1)에서는 이미지 간의 유사도를 측정해서 유사한 이미지를 찾도록 하였다. 이번 포스트에서는 자연어를 이용해서 자연어에서 설명하는 이미지와 유사한 이미지를 찾는 방법에 대해서 알아보겠다.

CLIP model은 Text로 들어오는 자연어를 Transformer 아키텍처를 사용해서 처리한다. 입력된 Text는 Tokenize되어 Embeding 된 후에 여러 개 층으로 구성된 Transformer 인코더를 통해 처리됩니다. 처리된 자연어는 Vector값으로 반환 되고, 자연어의 의미와 문맥이 어떤 이미지를 의미 하는 지를 512 차원의 Vector 값으로 나타내게 됩니다.

CLIP model process

이제, CLIP을 사용해서 검색에 사용할 자연어를 vector embedding해보자.
(이전 포스트에서 이미지를 vector embedding 하던 코드를 조금 변형해서 사용하면 된다.)

from sentence_transformers import SentenceTransformer
from PIL import Image
 
# 영어 모델 clip-ViT-B-32
# 다국어 모델 clip-ViT-B-32-multilingual-v1
img_model = SentenceTransformer('clip-ViT-B-32-multilingual-v1', device='cpu')
 
def vectorize(text):
    embedding = img_model.encode(text) 
    return embedding.tolist()
 
vectorize('빨간 사과')

위 코드를 실행해보면 텍스트가 vector embedding되어 512 차원의 vector를 반환하는 것을 확인 할 수 있다. 반환 받은 vector 값을 이용하여서 이미지 검색을 해보자.

GET image_vector/_search
{
  "knn": {
    "field": "vector",
    "k": 5,
    "num_candidates": 10,
    "query_vector": [
      -0.10659506916999817,
      0.20013009011745453,
      0.15304496884346008,
      0.03850187361240387,
      -0.126994326710701,
     ... 생략 ...
      -0.04996727779507637
    ]
  },
  "fields": [
    "image_name"
  ],
  "_source": false
}

결과를 보면 빨간 사과라는 자연어 Text에서 “빨간”“사과”의 의미를 잘 파악해서 이미지를 잘 찾아 낸 것을 볼 수 있다.

[Elasticsearch] Similar image search by image or natural language(1)

Elasticsearch(이하 ES)에서 자연어나 이미지로 유사한 이미지를 검색하는 방법에 대해서 알아보자.

유사한 이미지를 찾기 위해서는 먼저 이미지들 간의 유사도를 측정할 방법이 필요하고, 유사도를 측정하기 위해서 Vector를 사용할 수 있다. 이미지를 Embedding을 통해서 Vector로 변환하고, Vector 간의 유사도(Cosine similarity)를 계산해서 유사한 이미지를 찾을 수 있다.

그러면, 이미지를 Vector embedding하는 방법은 무엇이 있을까?

이미지를 vector embedding하는 방법과 이를 만들어주는 ML 모델들은 이미 많이 공개 되어 있다. ResNet, VGGNet, Inception 등 많은 모델들이 있다. 모델들의 성능을 잘 비교해서 알맞은 모델을 사용하면 된다.

여기에서는 OpenAI에서 공개한 CLIP model를 사용할 것이다.

CLIP을 사용해서 검색할 이미지들을 vector embedding해보자.
(사용할 이미지가 없다면 아래 첨부된 이미지를 사용해보자.)

from sentence_transformers import SentenceTransformer
from PIL import Image

img_model = SentenceTransformer('clip-ViT-B-32', device='cpu')

def vectorize(file_path):
    image = Image.open(file_path)
    embedding = img_model.encode(image)
    del image

    return embedding.tolist()

vectorize('./image/apple.jpg')

위 코드를 실행해보면 이미지가 vector embedding되어 512 차원의 vector를 반환하는 것을 확인 할 수 있다. 검색 대상의 이미지들을 모두 embedding 하면 된다.

vector embedding된 값들을 생성했다면, 이제 vector들을 ES에 올려서 유사 이미지 검색을 해보자.

먼저, ES에서 Vector를 담을 index를 생성한다.

PUT image_vector
{
  "mappings": {
    "properties": {
      "image_name": {
        "type": "keyword"
      },
      "vector": {
        "type": "dense_vector",
        "dims": 512,
        "index": true,
        "similarity": "cosine"
      }
    }
  }
}

생성한 인덱스에 만든 Vector 값들을 넣어준다.

from elasticsearch import Elasticsearch

def index(image_name, vector):
    es = Elasticsearch(cloud_id='elastic_cloud_id',
                       basic_auth=('elastic', 'elastic_secret'))
    doc = {     
        'image_name': image_name,
        'vector': vector
    }

    es.index(index='image_vector', body=doc)

index('apple.jpg', vector)

여기까지 하면 유사한 이미지를 검색하기 위한 준비는 끝난다.

원하는 이미지를 하나 골라서 검색을 해보자

GET image_vector/_search
{
  "knn": {
    "field": "vector",
    "k": 5,
    "num_candidates": 10,
    "query_vector": [
      0.5978590846061707,
      0.5926558375358582,
      -0.1243332028388977,
      0.11083680391311646,
      -0.6588778495788574,
     ... 생략 ...
      0.37319913506507874
    ]
  },
  "fields": [
    "image_name"
  ],
  "_source": false
}

왼쪽 끝의 사과 이미지와 위 예제 파일안에서 유사한 이미지 4개를 찾은 것을 확인 할 수 있다.

Next: Similar image search by image or natural language(2)