エンベディング(Embedding(埋込))とは

文章や画像データを数値の配列であるベクトルに変換することは「エンベディング」と呼ばれます。
RAG(Retrieval-Augmented Generation:検索拡張生成)で使用されるベクトルストアには、エンベディングによってベクトル化されたデータを保存し、
ベクトルストアの検索では、コサイン類似度などの計算を通じて、類似度が高い(似ている)データを取得することが可能です。

今回は、画像に対して、拡大縮小、回転、グレースケール化、明るさ変更した画像と類似度比較してみます。
画像の撮影対象が同じなら撮影の条件によって、角度や明るさが変わっても類似度は高くなって欲しいことが期待されます。

また、pgvector 拡張機能を有効にした PostgreSQL をベクトルストアに用いることで、PostgreSQL へのエンベディング保存、エンベディング検索を試したいと思います。

目次

検証画像とOpenCVヒストグラムでの類似度計算

検証に使った画像と、
下記OpenCVを用いたpythonコードでヒストグラムでの類似度計算したので、その結果を記載します。
ライブラリインストール

pip install opencv-python

ヒストグラム類似度計算コード

import cv2

def compare_hist(image1_path, image2_path):

    # 画像読み込み
    img1 = cv2.imread(image1_path)
    img2 = cv2.imread(image2_path)

    # ヒストグラムの計算のために画像をBGRからHSVに変換
    hsv_img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2HSV)
    hsv_img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)

    # HSVチャンネルごとにヒストグラムを計算
    hist_img1 = cv2.calcHist([hsv_img1], [0, 1, 2], None, [50, 60, 60], [0, 180, 0, 256, 0, 256])
    hist_img2 = cv2.calcHist([hsv_img2], [0, 1, 2], None, [50, 60, 60], [0, 180, 0, 256, 0, 256])

    # ヒストグラムを正規化
    cv2.normalize(hist_img1, hist_img1, 0, 1, cv2.NORM_MINMAX)
    cv2.normalize(hist_img2, hist_img2, 0, 1, cv2.NORM_MINMAX)

    return cv2.compareHist(hist_img1, hist_img2, cv2.HISTCMP_CORREL)

if __name__ == "__main__": 
    image1_path = 'run_cat_smile.png'
    image2_path_list = [
        'run_cat_smile_zoomout.png',
        'run_cat_smile_rotate45.png',
        'run_cat_smile_grayscale.png',
        'run_cat_smile_brightness.png',
        'pet_darui_cat.png',
        'pet_cat_oddeye_black.png',
    ]
    for image2_path in image2_path_list:
        print(f'ファイル: {image2_path}')
        hist_similarity = compare_hist(image1_path, image2_path)
        print(f'ヒストグラム類似度: {hist_similarity:.5f}')

画像 説明 ヒストグラム類似度
1に近いほど類似している
比較元画像
比較元画像を50%のサイズへ縮小 0.99933
比較元画像を45度回転 0.99964
比較元画像をグレースケール化 0.95499
比較元画像を明るさアップ -0.00077
比較元画像とは別の猫の画像
猫の毛の色は似ているものにした
0.96067
比較元画像と同じ猫画像
猫の毛の色は別のものにした
0.92697

画像のエンベディング、PostgreSQLへの保存、検索、類似度取得

Google Cloud のドキュメントのマルチモーダル エンベディングを取得する を参考に、Embeddings API を使って、画像をエンベディング化し、ベクトルストア(PostgreSQL)へ保存、ベクトルストアで検索します。
ライブラリインストール

pip install google-cloud-aiplatform langchain langchain-google-vertexai langchain-postgres

エンベディング、PostgreSQLへの保存、検索、類似度取得コード

import os
import pprint
import vertexai
from vertexai.vision_models import (
    Image,
    MultiModalEmbeddingModel,
    MultiModalEmbeddingResponse,
)
from langchain_postgres import PGVector
from langchain_postgres.vectorstores import PGVector
from langchain_google_vertexai import VertexAIEmbeddings

# PostgreSQL 接続文字列
DATABASE_URL = "自分のPostgreSQLの接続先URLを設定してください"
# PostgreSQL のベクトルストア初期化、テーブル、レコードが自動作成される
vector_store = PGVector(
    connection=DATABASE_URL,
    collection_name="rag_similarity", # 任意の文字列
    embeddings=VertexAIEmbeddings("multimodalembedding")
)

# Vertex AI 使用設定
PROJECT_ID = os.environ.get('PROJECT_ID')
LOCATION = os.environ.get('LOCATION')
vertexai.init(project=PROJECT_ID, location=LOCATION)

# Embedding生成 のモデルを設定
model = MultiModalEmbeddingModel.from_pretrained("multimodalembedding")


def get_image_embeddings(
    image_path: str,
) -> MultiModalEmbeddingResponse:
    """
    画像のマルチモーダル embeddings 生成
    参考:
    https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings/get-multimodal-embeddings?hl=ja
    """
    image = Image.load_from_file(image_path)

    embeddings = model.get_embeddings(
        image=image,
    )

    return embeddings


if __name__ == "__main__":
    # 比較元画像
    input_file = 'run_cat_smile.png'
    # 比較対象画像のリスト
    target_file_list = [
        'run_cat_smile_zoomout.png',
        'run_cat_smile_rotate45.png',
        'run_cat_smile_grayscale.png',
        'run_cat_smile_brightness.png',
        'pet_darui_cat.png',
        'pet_cat_oddeye_black.png',
    ]

    # 比較画像のembeddings取得
    input_file_embeddings = get_image_embeddings(image_path=input_file)
    # 比較対象画像のembeddings取得してベクトルストア登録
    for target_file in target_file_list:
        target_file_embeddings = get_image_embeddings(image_path=target_file)

        vector_store.add_embeddings(
            embeddings=[target_file_embeddings.image_embedding],
            texts=[target_file],
        )

    # 比較画像でベクトルストア検索
    similarity_list = vector_store.similarity_search_with_score_by_vector(
        embedding=input_file_embeddings.image_embedding,
        k=6
    )

    # 検索結果とスコアを表示
    for doc, score in similarity_list:
        print(doc.page_content + ' score: ' + str(score))

画像 説明 ヒストグラム類似度
1に近いほど類似している
ベクトルストア検索類似度
0に近いほど類似している
比較元画像
比較元画像を50%のサイズへ縮小 0.99933 0.02482759952545166
比較元画像を45度回転 0.99964 0.03956574437413907
比較元画像をグレースケール化 0.95499 0.041498453661752865
比較元画像を明るさアップ -0.00077 0.05257987976074219
比較元画像とは別の猫の画像
猫の毛の色は似ているものにした
0.96067 0.11673441877169621
比較元画像と同じ猫画像
猫の毛の色は別のものにした
0.92697 0.2719438076019287

モデル:”multimodalembedding”でエンベディングしたベクトルをPostgreSQLへ保存し、検索して類似度を取得してみました。
比較元画像と比較元画像を加工した画像との類似度は近いものとなっており期待通りの結果でした。
類似度が閾値以上のものをRAGとして使用することが考えられます。