2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【検証】工数を抑えつつ商品ドンピシャの画像検索機能を作れるか(Clarifai Smart Search編)

Last updated at Posted at 2025-09-05

はじめに

K.S.ロジャースの妹尾と申します🙋‍♂️

今回はユーザーが撮影した商品画像をもとに、保持している商品画像を検索できる機能を 工数を抑えつつ 実装する方法を調査しました。

固有の商品を特定するには、カスタムラベリングを用いた機械学習モデルを活用するのが一般的です。

ただ、開発工数・運用工数が高いため、既存のフルマネージドなAIサービスを活用できないかということで、いくつかの案の中から Clarifai Smart Search を使って実現可能か検証しました。

補足
今回は工数を抑えるため、精度向上のための調整などは行わず、デフォルトでどのくらい通用するかを検証します。

注意
機械学習、ベクトル検索、画像検索などに精通したエンジニアではないので誤っている箇所や知識不足な箇所もあると思いますので、その場合はご指南いただけますと幸いです。

結論

Clarifai Smart Searchは商品ドンピシャの画像検索機能を作る場合には不向きだと感じました。
そもそもベクトル検索にそんなこと期待するなよ...って話ではあります😅

ただし、画像検索機能でも別のユースケースであれば、かなり使えるサービスではないかと感じました。
個人的には以下の条件に当てはまる場合には有力な候補の一つになり得ると思います。

  1. 検索結果に異なる商品が含まれる可能性を許容できる場合
  2. 最終的に商品を特定できる画像検索機能を目指さない場合(検索精度は上げつつも商品の特定は目指さない)

独自にベクトルを生成する方がユースケースにマッチしたベクトル検索ができることは間違いないですが、

「あまりコストをかけずに、似たような画像を取得したい」

というようなユースケースは割とあるのではないかと思いますので、そういうケースでは重宝するフルマネージドなベクトル検索エンジンではないかと思います。

検証

画像の登録

まずはClarifaiに画像を登録します。

登録したい画像URLをまとめたCSVファイルを作成し、PythonのスクリプトでCSVファイルを読み込んで登録しました。

サンプルコード
input.py
from clarifai_grpc.channel.clarifai_channel import ClarifaiChannel
from clarifai_grpc.grpc.api import resources_pb2, service_pb2, service_pb2_grpc
from clarifai_grpc.grpc.api.status import status_code_pb2
import pandas as pd

USER_ID = '*****'
PAT = '*****'
APP_ID = '*****'
CSV_FILE_PATHS = [
    'sample-data/sample.csv'
]
REQUEST_LIMIT = 128

channel = ClarifaiChannel.get_grpc_channel()
stub = service_pb2_grpc.V2Stub(channel)

metadata = (('authorization', 'Key ' + PAT),)

userDataObject = resources_pb2.UserAppIDSet(user_id=USER_ID, app_id=APP_ID)

for csv_file_path in CSV_FILE_PATHS:
  df = pd.read_csv(csv_file_path, header=0)

  inputs = [
      resources_pb2.Input(
          id=str(row['id']),
          data=resources_pb2.Data(
              image=resources_pb2.Image(
                  url=row['url'],
                  allow_duplicate_url=True
              ),
          )
      ) for _, row in df.iterrows()
  ]
  print(str(len(inputs)) + " inputs")

  for i in range(0, len(inputs), REQUEST_LIMIT):
      post_inputs_response = stub.PostInputs(
          service_pb2.PostInputsRequest(
              user_app_id=userDataObject,
              inputs=inputs[i:i+REQUEST_LIMIT]
          ),
          metadata=metadata
      )

      if post_inputs_response.status.code != status_code_pb2.SUCCESS:
          print("There was an error with your request!")
          print("status code: " + str(post_inputs_response.status.code))
          print("status description: " + post_inputs_response.status.description)
          print("details: " + post_inputs_response.status.details)
          raise Exception("Post inputs failed")

これで画像が登録できました。

スクリーンショット 2024-11-18 17.04.06.png

画像の検索(Clarifai管理画面)

まずはClarifaiの管理画面で画像検索の検証ができるので試してみました。

image-search-web.gif

なんとなく近しい画像がランキング上位に表示されています。
ただ、これはランキング形式なので、あくまで入力した画像に近しい画像が順番に並んでいるだけです。
なので、商品ドンピシャの画像検索機能とは異なります。

ここからできるだけ商品ドンピシャに近づけようと思うと、検索結果の score(入力した画像にどれぐらい近いかを数値化したもの)による絞り込みが必要になりますのでAPIを使って検索してみます。

画像の検索(Clarifai API)

検索については画面を作れば分かりやすいのですが、できるだけ無駄な工数を割かないように入出力は以下のようにしました。

  • 入力:スクリプト内に画像のURLをべた書き
    ranks=[{
      'image_url': 'https://*****.com/test.jpg'
    }],
    
  • 出力:id, score, image_urlをjson形式で標準出力に出力
    {'id': '12345', 'score': 0.9999998211860657, 'image_url': 'https://*****.com/input_1.jpg'}
    {'id': '23456', 'score': 0.9258927698265958, 'image_url': 'https://*****.com/input_2.jpg'}
    

ひとまず score >= 0.9 にマッチする画像を検索結果として表示することにしました。

サンプルコード
search.py
USER_ID = '*****'
PAT = '*****'
APP_ID = '*****'

from clarifai.client.user import User

client = User(user_id=USER_ID, pat=PAT)
app = client.app(app_id=APP_ID)
search = app.search(pagination=True)
res = search.query(
    ranks=[{
        'image_url': 'https://*****.com/test.jpg'
    }],
    page_no=1,
    per_page=50,
)

cnt = 0
results = []
for r in res:
  cnt += 1
  for h in r.hits:
    # results.idにidが存在する場合は、スキップ
    if any(h.input.id == result['id'] for result in results):
      continue
    # 0.9未満のスコアはスキップ
    if h.score < 0.9:
      continue

    result = {
      'id': h.input.id,
      'score': h.score,
      'image_url': h.input.data.image.url
    }
    results.append(result)
    print(result)

if cnt == 0:
  print('No result found')

それでは、先ほど管理画面にて検索した商品について検索してみましょう。

1つ目の商品

まず、こちらの検索結果は以下の通りです。

{'id': '397', 'score': 0.9999998807907104, 'image_url': 'https://coach.scene7.com/is/image/Coach/cs979_gld_a0?$desktopProductTile$'}
{'id': '398', 'score': 0.9469653964042664, 'image_url': 'https://coach.scene7.com/is/image/Coach/cs977_ss_a0?$desktopProductTile$'}
{'id': '400', 'score': 0.9238855838775635, 'image_url': 'https://coach.scene7.com/is/image/Coach/co223_ahe_a0?$desktopProductTile$'}
{'id': '401', 'score': 0.9225902557373047, 'image_url': 'https://coach.scene7.com/is/image/Coach/co224_blk_a0?$desktopProductTile$'}

score >= 0.9 にマッチする画像が4件ヒットしました。

score image
0.9999998807907104
0.9469653964042664
0.9238855838775635
0.9225902557373047

全てがシンプルなテクスチャの腕時計であることから、一定の絞り込み効果があると判断できます。
ただ、商品ドンピシャでの検索というのは失敗しています。
この結果から、同一商品の色違いでも score はかなり落ちることが分かります。

他にも腕時計の画像には以下のようなものがありますが、この辺りはヒットしませんでした。

2つ目の商品

続いて、こちらの検索結果は以下の通りです。

{'id': '38', 'score': 0.9649432301521301, 'image_url': 'https://coach.scene7.com/is/image/Coach/cu107_tok_a0?$desktopProductTile$'}
{'id': '122', 'score': 0.903478741645813, 'image_url': 'https://coach.scene7.com/is/image/Coach/cy826_a5f_a0?$desktopProductTile$'}
{'id': '133', 'score': 0.9025021195411682, 'image_url': 'https://coach.scene7.com/is/image/Coach/caf83_a5f_a0?$desktopProductTile$'}

score >= 0.9 にマッチする画像が3件ヒットしました。

score image
0.9649432301521301
0.903478741645813
0.9025021195411682

他にもバッグの画像には以下のようなものがありますが、この辺りはヒットしませんでした。

2位の商品は同一形状で柄と文字が異なる商品、3位の商品は形状こそ異なりますが、全体のテクスチャは類似している商品がヒットしました。
商品ドンピシャでの検索というのは失敗していますが、似ている商品に絞り込めてはいそうです。
ただ、2位と3位の商品の score がほぼ同じというのが意外な結果だと感じました。(人間から見ると明らかに差があるように見えるがそうでもないのだろうか :thinking:
当初は閾値の調整を行うことで、ある程度類似商品だけに絞り込めるのでは?と思っていました。
ただ、この結果を見ると閾値調整での絞り込みはかなり難しいと感じました。
この検索結果の精度改善をしたいとなった場合には、1位と2位の商品だけをヒットさせるという調整をしたいと思うので score にほとんど差がないと、閾値での調整次第で同一商品がヒットしなくなることも懸念されます。

3つ目の商品

最後に、こちらの検索結果は以下の通りです。

{'id': '1', 'score': 0.9999997615814209, 'image_url': 'https://coach.scene7.com/is/image/Coach/cv930_chk_a0?$desktopProductTile$'}
{'id': '23', 'score': 0.9408445358276367, 'image_url': 'https://coach.scene7.com/is/image/Coach/cp123_cah_a0?$desktopProductTile$'}
{'id': '22', 'score': 0.9188821315765381, 'image_url': 'https://coach.scene7.com/is/image/Coach/cv928_deb_a0?$desktopProductTile$'}
{'id': '12', 'score': 0.9169434309005737, 'image_url': 'https://coach.scene7.com/is/image/Coach/cv931_chk_a0?$desktopProductTile$'}
{'id': '15', 'score': 0.9034721255302429, 'image_url': 'https://coach.scene7.com/is/image/Coach/cp137_cah_a0?$desktopProductTile$'}

score >= 0.9 にマッチする画像が5件ヒットしました。

score image
0.9999997615814209
0.9408445358276367
0.9188821315765381
0.9169434309005737
0.9034721255302429

他にもバッグの画像には以下のようなものがありますが、この辺りはヒットしませんでした。

こちらの結果から、やはり色は重要な指標になっていそうだということが分かります。
形状はほぼ同じだとしても色や柄が異なると、 score0.9 を下回ることもあるようです。
もし、同一商品だけど別色の商品を検索結果に表示させたいという要件がある場合には、普通にやろうとすると難しいということになります。

追加検証

画像の解像度

高画質の商品画像に対して、解像度を下げた画像で検索することで、どの程度scoreが変動するのかを検証してみましょう。

■ 検索対象の画像

■ 検索結果

以下の結果から、同一画像でも解像度が下がることで score が下がることを確認できました。
ここには記載しませんが、逆に低画質の商品画像に対しては、低画質の画像の方が score が高くなることも確認しました。

image size score image memo
1 0.995370090007782 検索対象の画像と同一画質
1/2 0.9909393787384033 -
1/4 0.9734631180763245 -
1/8 0.9656218886375427 -

色違いの同一商品

次に同一画像の色違い画像でどの程度 score が変動するのかを検証してみましょう。

■ 検索対象の画像

■ 検索結果

以下の結果から、同一商品でも色が違うだけで、かなり score が下がることを再確認できました。

score image memo
0.9999996423721313 検索対象の画像と同一色
0.9490211009979248 -
0.9412142634391785 -
0.9393134117126465 -
0.9354055523872375 - 

検証結果

今までの検証にて、ある程度の精度での絞り込みはできることが確認できました。

ただし、色や解像度などでも score に大きな影響があることを踏まえると、形状だけではない他の要素も score に大きな影響を与えることが分かります。

すなわち、以下の2つの画像の score は均衡する可能性が高いです。

  • 似ている形状の別の商品だが同一色の画像
  • 同一商品だが異なる色の画像

上記のことからClarifaiを使って同一商品の画像を検索する場合には、ある程度工夫する必要があると思います。
ただし、どこまで行ってもベクトル検索(類似度による検索)なので、別商品でも見た目が似ていれば score が上がります。
そのため、検索結果に誤った商品が混入することは許容する必要があります。

ただし、完璧を目指すのは難しいという前提で、独自に画像から形状だけにフォーカスしたベクトル生成ができれば、同一商品の画像を検索するというユースケースをある程度満たせるポテンシャルはベクトル検索にもあるのではないかと思いました。

補足
カスタムラベリングした機械学習モデルを活用する方法であれば、誤った商品だと判断した場合は検索結果が全て誤った商品になります。
そのため混入はしないけど誤って判断することはあります。

さらなる精度改善に向けて

今回検証してみて、さらに精度を高めるアプローチとして思いついたものを挙げておきます。
ただ、この辺りを実装をしだすと 「工数を抑えつつ」 という前提から外れてしまうかなと思いますので、今回検証は実施しません。(あくまで参考まで)

score の閾値を調整する

まず、最初に思いつくのは閾値調整だと思います。
一番工数がかからない調整方法である点はメリットですが、デメリットとしては閾値を上げるにつれて同一商品画像の場合でも検索結果から除外される可能性が上がります。
実施する際には上記のトレードオフになることを許容した上での調整が必要になります。

score の閾値をリリース時点で最適な値にするというのは難しいものがあります。
そのため、実際にリリースして運用していく中で最適な閾値を探っていくことになると思います。

閾値調整を諦めて、シンプルにscoreの高い順で検索結果に表示するというのも落とし所としてはありかと思います。

画像を加工してできるだけ見た目を似せる

ユーザーが自身の保持している商品を撮影して、その画像を入力に画像検索を行う場合だと、入力される画像がどのくらい登録している画像に近いかはユーザーに委ねられます。

そのため、背景に他のものが写っていたり、撮影する角度が違ったりするだけで、 score は下がってしまいます。

この差をなるべく少なくするためには、画像を加工してなるべく見た目を寄せる必要があります。

例えば以下のような方法があるかと思います。

  1. 外部API(remove.bgなど)を活用して、登録画像・入力画像の背景を単一色に置き換える
  2. データ拡張(ランダムクロップ、拡大縮小、回転など)にて同じ商品に紐づく画像を複数登録する
  3. 白黒画像に変換する(色による違いを極力減らす)

おわりに

今回はClarifai Smart Searchを検証してみましたが、他のサービスを使った案もあるので時間があれば続編として、そちらの検証も記事にしようと思っています!

もし、「こんなサービスあるよ!」「こんなやり方があるよ!」など知見のある方がいれば是非コメントで教えていただければと思います😄

K.S.ロジャースでは一緒に働いていただけるメンバーを募集しています!
フルリモートかつフルフレックスで働きやすく、レベルの高い仲間が集うそんな会社です💪
皆様のご応募どしどしお待ちしております🙇

K.S.ロジャースではWantedlyでもブログ投稿をしています。
Techブログだけでなく会社ブログなども投稿しているので、気になった方はぜひ覗いてみてください😁

では良いエンジニアライフを👋

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?