はじめに
K.S.ロジャースの妹尾と申します🙋♂️
今回はユーザーが撮影した商品画像をもとに、保持している商品画像を検索できる機能を 工数を抑えつつ 実装する方法を調査しました。
固有の商品を特定するには、カスタムラベリングを用いた機械学習モデルを活用するのが一般的です。
ただ、開発工数・運用工数が高いため、既存のフルマネージドなAIサービスを活用できないかということで、いくつかの案の中から Clarifai Smart Search を使って実現可能か検証しました。
補足
今回は工数を抑えるため、精度向上のための調整などは行わず、デフォルトでどのくらい通用するかを検証します。
注意
機械学習、ベクトル検索、画像検索などに精通したエンジニアではないので誤っている箇所や知識不足な箇所もあると思いますので、その場合はご指南いただけますと幸いです。
結論
Clarifai Smart Searchは商品ドンピシャの画像検索機能を作る場合には不向きだと感じました。
そもそもベクトル検索にそんなこと期待するなよ...って話ではあります😅
ただし、画像検索機能でも別のユースケースであれば、かなり使えるサービスではないかと感じました。
個人的には以下の条件に当てはまる場合には有力な候補の一つになり得ると思います。
- 検索結果に異なる商品が含まれる可能性を許容できる場合
- 最終的に商品を特定できる画像検索機能を目指さない場合(検索精度は上げつつも商品の特定は目指さない)
独自にベクトルを生成する方がユースケースにマッチしたベクトル検索ができることは間違いないですが、
「あまりコストをかけずに、似たような画像を取得したい」
というようなユースケースは割とあるのではないかと思いますので、そういうケースでは重宝するフルマネージドなベクトル検索エンジンではないかと思います。
検証
画像の登録
まずはClarifaiに画像を登録します。
登録したい画像URLをまとめたCSVファイルを作成し、PythonのスクリプトでCSVファイルを読み込んで登録しました。
サンプルコード
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")
これで画像が登録できました。
画像の検索(Clarifai管理画面)
まずはClarifaiの管理画面で画像検索の検証ができるので試してみました。
なんとなく近しい画像がランキング上位に表示されています。
ただ、これはランキング形式なので、あくまで入力した画像に近しい画像が順番に並んでいるだけです。
なので、商品ドンピシャの画像検索機能とは異なります。
ここからできるだけ商品ドンピシャに近づけようと思うと、検索結果の 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
にマッチする画像を検索結果として表示することにしました。
サンプルコード
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
がほぼ同じというのが意外な結果だと感じました。(人間から見ると明らかに差があるように見えるがそうでもないのだろうか )
当初は閾値の調整を行うことで、ある程度類似商品だけに絞り込めるのでは?と思っていました。
ただ、この結果を見ると閾値調整での絞り込みはかなり難しいと感じました。
この検索結果の精度改善をしたいとなった場合には、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 |
他にもバッグの画像には以下のようなものがありますが、この辺りはヒットしませんでした。
こちらの結果から、やはり色は重要な指標になっていそうだということが分かります。
形状はほぼ同じだとしても色や柄が異なると、 score
が 0.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
は下がってしまいます。
この差をなるべく少なくするためには、画像を加工してなるべく見た目を寄せる必要があります。
例えば以下のような方法があるかと思います。
- 外部API(remove.bgなど)を活用して、登録画像・入力画像の背景を単一色に置き換える
- データ拡張(ランダムクロップ、拡大縮小、回転など)にて同じ商品に紐づく画像を複数登録する
- 白黒画像に変換する(色による違いを極力減らす)
おわりに
今回はClarifai Smart Searchを検証してみましたが、他のサービスを使った案もあるので時間があれば続編として、そちらの検証も記事にしようと思っています!
もし、「こんなサービスあるよ!」「こんなやり方があるよ!」など知見のある方がいれば是非コメントで教えていただければと思います😄
K.S.ロジャースでは一緒に働いていただけるメンバーを募集しています!
フルリモートかつフルフレックスで働きやすく、レベルの高い仲間が集うそんな会社です💪
皆様のご応募どしどしお待ちしております🙇
K.S.ロジャースではWantedlyでもブログ投稿をしています。
Techブログだけでなく会社ブログなども投稿しているので、気になった方はぜひ覗いてみてください😁
では良いエンジニアライフを👋