画像検索の精度確認(3)

Date:

Share post:

過去の投稿を通して、TensorFlow/Kerasを使用した画像のエンベディングにおいては構図の影響が大きいことが、何となく分かってきました。
見方を変えれば、対象画像群における構図の差異を最小に止めれば、被写体自体の識別精度を高められる可能性があるとも言えます。

よって、今回は同じ被写体を撮影した複数の画像に関して、できる限り構図を統一するための対策を実施し、その効果を検証したいと思います。

なお、今までの検証では様々な被写体を扱ってきましたが、実は最終目的はワインの画像から当該商品を識別することでした(弊社はECにてワインを取り扱っていますので)。
よって、今後は上記目的に準じて被写体をワイン(もしくはそれに類する物)に限定していきたいと思います。

構図の要素

まず、一言で「構図」と言っても、具体的にどのような要素があるかを考えてみたいと思います。

  • 被写体の向き
  • 撮影のアングル
  • 背景
  • 被写体サイズ(画面占有率)
  • アスペクト比

他にもあるかと思いますが、今までの投稿を通してエンベディング結果への影響が大きそうだと感じたのは上記のような要素でした。

上記のうち、「被写体の向き」と「撮影のアングル」は一旦無視したいと思います。
ワインに関しては、何か特別な目的がある場合を除けばボトルを直立させた状態での撮影が大半かと思いますし、アングルに関しても普通に正面から撮影しているケースが多いかと思います。

一方で、「背景」および「被写体サイズ(画面占有率)」に関しては、撮影状況によってかなりの違いが生じる部分かと思います。

「アスペクト比」に関しても地味に影響が大きい部分かと思います。
以前の投稿でエンベディングの実装に関して紹介しましたが、エンベディングに際しては画像サイズを224×224にリサイズしています。これは、画像本来のアスペクト比に関わらず、強制的に1対1の比率に変換(変形)されていることを意味します。
仮に同じ被写体を同等の条件で撮影したとしても、アスペクト比が1対1の画像と16対9の画像では、前述の変換を行なった結果の画像に反映された被写体の形状はかなり異なったものになるでしょう。

上記点から、「背景」「被写体サイズ(画面占有率)」「アスペクト比」の3点に関しての対策を考えます。

画像処理

結論を先に書いてしまいますが、画像に対してはエンベディングの前処理として以下の操作を行います。
蛇足ながら、処理の大半はGemini先生に書いてもらいました。

from rembg import remove
import cv2

def conv_image(input_path, output_path):
    input_image = cv2.imread(input_path)
    output_image = remove(input_image)

    gray = cv2.cvtColor(output_image, cv2.COLOR_RGBA2GRAY)
    _, thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    max_area = 0 
    max_contour = None
    for contour in contours:
        area = cv2.contourArea(contour)
        if area > max_area:
            max_area = area
            max_contour = contour
    x, y, w, h = cv2.boundingRect(max_contour)

    if h % 2 != 0:  
        h += 1  
    new_w = int(h / 2)  
    new_x = x + (w - new_w) // 2  
    trimmed_image = output_image[y:y+h, new_x:new_x+new_w]
    resized_image = cv2.resize(trimmed_image, (224, 448))

    cv2.imwrite(output_path, resized_image)

以下、大まかに解説します。

    input_image = cv2.imread(input_path)
    output_image = remove(input_image)

上記では指定された画像を読み込んで、背景を削除しています。

「rembg」は画像から背景(被写体以外)を削除するライブラリですが、つまりは指定された画像内のどこが被写体であるかを認識できていると言うことです。
無論、画像の内容によって被写体認識の精度に差はあるようですが、それでも高度な能力には変わりなく、そのような処理が1つの関数を呼び出すだけで実現できてしまう点がPythonの凄さですね。

なお、本処理結果として背景部分のRGBAは0,0,0,0になります。

    gray = cv2.cvtColor(output_image, cv2.COLOR_RGBA2GRAY)
    _, thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

上記では、被写体の輪郭を抽出しています。
最初の処理で画像をグレースケール化し、次の処理で閾値は10での二値化を行なっています。これにより元画像は背景が黒、被写体部分が白の単純な白黒画像になるようです。
最後の処理で被写体の輪郭を抽出しています。

ここも、相応に大変な作業を行なっているはずですが、さらりと3行で書けてしまいました。
Python恐るべし。

    max_area = 0 
    max_contour = None
    for contour in contours:
        area = cv2.contourArea(contour)
        if area > max_area:
            max_area = area
            max_contour = contour
    x, y, w, h = cv2.boundingRect(max_contour)

上記では検出された輪郭の中から、面積が最大のものを検出し、その輪郭を囲む矩形の左上の座標(x, y)と幅(w)と高さ(h)を取得しています。

先に触れた「rembg」では複数の被写体を検出できます。
今回対象とする画像では、ワインが1本写っているだけの状態を期待するものですが、周辺に写り込んだ物が被写体として誤認される可能性も否定できません。
そのようなケースの対策として上記処理のように最も大きく写っている物のみを対象とするという配慮が必要になる訳です。

で、最終的には本来の被写体を囲む矩形の情報(左上の座標、幅、高さ)を取得しています。

    if h % 2 != 0:  
        h += 1  
    new_w = int(h / 2)  
    new_x = x + (w - new_w) // 2  
    trimmed_image = output_image[y:y+h, new_x:new_x+new_w]
    resized_image = cv2.resize(trimmed_image, (224, 448))

上記では、背景を削除した画像から被写体の部分のみを矩形に切り抜き、224×448にリサイズしています。

最初の3行で切り抜く幅と高さを決定していますが、幅は高さの半分になるように調整しています。
これは最終的に224×448にリサイズした際にアスペクト比(1対2)が変わらないように考慮しているためです。
上記調整の都合上、高さが奇数の場合は1プラスして偶数になるようにしています。

4行目では、切り抜く幅が事前に取得した矩形から変わったことにより、新たに切り抜き対象部分となった矩形のx座標を算出しています。

5行目では、rembgで背景を削除した画像から、前述までの処理で得られた被写体を囲むアスペクト比1対2の矩形部分を切り抜いています。

6行目では、上記で切り抜いた画像サイズを224×448にリサイズしています。

なお、上記処理では高さを基準に切り抜く幅を決定しているので、被写体によっては横に収まり切らない可能性もありますが、一般的なワインボトルの形状から考えると、かなり例外的な外観でない限り問題ないかと思います。

対象画像と処理結果

以下に今回の実験に使用した画像と、それらに対する前述の加工処理結果を示します。

対象画像は、被写体(1)と被写体(2)を複数の異なる状況下で撮影しています。

撮影場所は5ヶ所(Z,L,S,G,T)で、日中の自然光での撮影と、夜(N)に照明下で撮影したもの、およびフラッシュ(F)を使って撮影したものがあります。
各画像には前述の状況を示す名称を付けてあります。

なお、夜の撮影は3ヶ所(Z,L,S)のみで行なっています。

被写体1

画像名元画像加工結果メモ
1_Zボトルの下の方が少々切れています。
ラベル(エチケット)の下の部分が被写体の最下部と判断されているっぽいです。
テーブルの色とボトルの色が近かったことが原因でしょうかね。
他の場所よりも若干暗めの場所であるため、画像の色が濃い印象があります。
1_L1_Zと同様に下の方が切れています。
テーブルの色とは十分に区別できそうなのですが、謎です。
他の場所では概ね正面から光が当たっていますが、ここでは若干斜め左後方から当たる形での撮影になっています。
1_S背景が白いためか、ボトルの色が薄く見えます。
1_G他の場所と比較して、一番明るい場所で撮影したためか、ボトル表面への様々な写り込みがあります。
この辺がそのまま被写体の模様のように認識されれば、結果にも影響が出そうです。
1_T今回撮影した画像のうち、唯一背景部分が除外し切れませんでした。
背後の襖の縁の部分に関しては横方法(襖の下の部分)の写り込みは排除できているのですが、この差は何だったのでしょう。
1_Z_N1_Zと同様に下が切れました。
このテーブルの色でボトルとの境界を認識するのは厳しいようです。
日中でも若干暗い環境ですが、夜(蛍光灯下)でも暗めです。
1_Z_N_Fフラッシュを焚いたことで、ボトル表面やテーブルの色合いに変化が生じたため、ボトル下の境界が正しく認識されるようになりました。
一方で、全体の色合いに関しても1_Z_Nと比較してかなり印象が変わっています。
1_L_Nここは他の2ヶ所と比較して最も明るい照明(LED)であり、フラッシュを焚いていない状態では最も明るく写っています。
日中は問題のあったボトル下の境界の認識も正しく行えているようです。
1_L_N_Fフラッシュを焚いたことで、やはり色合いが少々変わっています。
同様にフラッシュを焚いて撮影した1_Z_N_Fとは、かなり近い印象を受けます。
1_S_N若干オレンジっぽい色合いの照明下での撮影になっています。
ボトルの影が濃く出ていますが、これは問題なく無視できたようです。
1_S_N_Fフラッシュを焚いて撮影したものは、やはり印象が近いものになるようです。

被写体2

画像名元画像加工結果メモ
2_Z被写体1と同様にボトルの下の認識に失敗しています。
このテーブルは鬼門のようです。
2_L1_Lではボトルの下の境界認識に失敗していましたが、こちらは大丈夫だったようです。
2_Sボトルの色が濃いためか、1_Sと比較して背景色の影響はあまり感じられません。
2_G1_Gと同様にボトル表面への写り込みが激しいです。
元々見た目が地味なボトルであるため、写り込みの影響が大きそうな予感がします。
2_T今回の画像では「被写体の向き」に関してはあまり考慮していない(考慮しなくても概ね同じになると想定した)のですが、本画像に関しては若干左に傾いている印象があります。
この辺がどの程度影響するか…
2_Z_Nボトルの下の境界の認識がかなり不思議なところに引かれています。
位置的にはボトルの本来の底の部分と、ラベルの下の部分との中間辺りでしょうか。
2_Z_N_Fこちらはボトルの下の境界の認識を完全に間違っているパターンです。
ただ、元画像を見る限り、むしろ認識しやすい画像に思えますが。
2_L_Nラベルの向かって左側に影のようなものが写り込んでいます。
これが模様のように認識された場合は影響があるかもしれません。
2_L_N_F元画像のボトル部分の印象としては2_Z_N_Fとあまり変わらないのですが、こちらはボトルの下の境界の認識が正しく行えています。
2_S_Nこちらもボトルの影の影響はなかったようです。
2_S_N_Fフラッシュを焚いたケースでの印象にブレが少ない点は他の画像と同様です。

総じて、背景除去(rembg)はかなり頑張ってくれた印象です。
明確な失敗例は「1_T」のみで、よほど余計な物が写り込んでしまわない限り、ワインボトルの撮影に限定した場合の背景除去の成功率はかなり高いことが期待されます。

ボトル下部の認識にはいくつか失敗していますが、背景除去の段階で問題があったのか、被写体の輪郭抽出で失敗しているのかは分かりません。

色合いに関しては、撮影状況で相応に変わってしまっていますが、その点は今回の趣旨からは外れますので、とりあえずは不問にします。

と言うことで、目的であった「背景」「被写体サイズ(画面占有率)」「アスペクト比」の3点の影響を排除し、サイズ224×448で、背景色(被写体以外)は黒、被写体が画像の上下一杯に撮影された画像を統一的に取得することは、ある程度可能なようです。

距離測定

上記で取得できた加工画像に関してエンベディングを実施し、PostgreSQLのコサイン距離算出機能を使用して各画像間の距離を測定してみたいと思います。

結果を全て列記すると冗長になるので、以下の4点に絞ります。

  1. 対象画像と同じ被写体で最も近いもの
  2. 対象画像と同じ被写体で最も遠いもの
  3. 対象画像と異なる被写体で最も近いもの
  4. 対象画像と異なる被写体で最も遠いもの

なお、上記に関しては、理想的には1,2,3,4の順で近いと判断されことを期待しますが、それに反するケースもありました。
そのようなケースに関しては赤字で示してあります。

また、比較のため、加工前の元画像での距離計測結果も併せて紹介します。

1_Z.jpg

加工後距離元画像距離
1_L_N.jpg0.0533022279399845141_G.jpg0.11455616444705097
1_G.jpg0.093190395766143071_L_N.jpg0.26468970973143
2_Z.jpg0.124100872499066982_Z.jpg0.15873566741158684
2_G.jpg0.185882628090233932_L_N.jpg0.31398766368974573

加工後の画像に関しては、期待通りの結果が得られています。
構図を合わせてある上に背景の余計な写り込みがないためか、元画像の場合と比較して、全般的に距離が小さい(近め)です。

最も近いと判断されたのが「1_L_N.jpg」ですが、これは撮影状況(場所、時間帯)が全く異なっていることから考えると、撮影状況に左右されずに同じ被写体を認識できている好例と言えます。

一方で、異なる被写体として「2_Z.jpg」が最も近いと判断されている点が興味深いです。
背景を排除してあるにも関わらず、同じ状況で撮影されたものが最も近いと判断される要因が何かあるのかもしれません。
個別に触れませんが、以降の比較においてもこの傾向が見られます。

元画像に関しては、加工後の画像の場合と比較して距離が大きく、かつ順序性に関しても期待通りにはなっていません。
以降の比較結果を見ても、正しい順序で認識されたパターンは1つもありませんでした。
異なる被写体に対しても、同じ撮影状況のものが最も近いと判断されがちな点は加工後の画像の場合と同様ですが、その傾向がさらに顕著(高確率)です。
やはり、背景の類似度が大きく影響していると推測されます。

1_L.jpg

加工後距離元画像距離
1_G.jpg0.048882026530401971_L_N.jpg0.1446702907633819
1_Z_N.jpg0.137243275020714431_Z_N.jpg0.23953829723813524
2_L.jpg0.145365369524209752_L.jpg0.1872801422165704
2_S_N.jpg0.228603171521878922_Z_N.jpg0.301847624206672

傾向的には「1_Z.jpg」と似ていますが、同じ被写体で最も距離の遠いものと、異なる被写体で最も距離の遠いものとの差がかなり接近しています。

1_S.jpg

加工後距離元画像距離
1_S_N.jpg0.077334316503179571_G.jpg0.12095993037272534
1_G.jpg0.119902710335118121_L_N.jpg0.27110902286358407
2_Z.jpg0.156165961947846932_S.jpg0.16492757022420057
2_S_N.jpg0.194748831393097732_L_N.jpg0.283325541088174

傾向的には先の例と同様ですが、異なる被写体で最も近いものが「2_Z.jpg」で、撮影状況が異なるものでした。

1_G.jpg

加工後距離元画像距離
1_L.jpg0.048882026530401971_S_N_F.jpg0.10966620463509569
1_Z_N.jpg0.161463493603994921_L_N.jpg0.29692292078478666
2_G.jpg0.140687170309657162_G.jpg0.1535493942802234
2_S_N.jpg0.23767203151151442_L_N.jpg0.3072706213972002

本ケースでは、同じ被写体で最も遠いものと、異なる被写体で最も近いものの距離が逆転しています。
つまり、「1_Z_N.jpg」と「2_G.jpg」の2つのみが既に登録されている状態で「1_G.jpg」の検索を行なった場合、被写体2が写っていると誤認されてしまうと言うことです。

1_T.jpg

加工後距離元画像距離
1_Z.jpg0.06209285930762521_G.jpg0.12734160727510402
1_Z_N_F.jpg0.111766659706448061_L_N.jpg0.2895090856169755
2_T.jpg0.129134286331585862_Z_N_F.jpg0.16101873834518443
2_S_N_F.jpg0.188502862209926742_L_N.jpg0.3059484788246565

傾向的には「1_Z.jpg」「1_L.jpg」と同様です(以下、このパターンを「正常パターン」とします)。

1_Z_N.jpg

加工後距離元画像距離
1_L_N.jpg0.056981480250092641_Z_N_F.jpg0.11024759576754595
1_G.jpg0.161463493603994921_L_N_F.jpg0.25746854485665893
2_Z_N.jpg0.120334266820584462_Z_N.jpg0.13446946286703654
2_G.jpg0.248493044138763392_L.jpg0.3046028004739765

本ケースも「1_G.jpg」と同様に距離の逆転が見られます(以下、このパターンを「異常パターン」とします)。

1_Z_N_F.jpg

加工後距離元画像距離
1_S_N_F.jpg0.025055999936510531_S_N_F.jpg0.09421901569376945
1_G.jpg0.116033173160306151_L_N.jpg0.2707283531360207
2_Z_N_F.jpg0.100367252749646972_Z_N_F.jpg0.07673877645765759
2_G.jpg0.20669737892476372_L_N.jpg0.30129086383516224

異常パターンですが、異なる被写体で最も近いものが「2_Z_N_F.jpg」と撮影状況が同じであることと、距離が約「0.10」とかなり近い点が印象的です。
やはり、被写体のみを強調するように画像を加工しても、撮影状況の影響が想定以上に残るようです。

なお、元画像の比較においては、全体で最も近いと判断されたものが別の被写体かつ撮影状況が同じものであり、かつ距離も「0.07」とかなり近いものとして認識されています。
やはり、被写体以外の要素が特徴ベクトル生成にかなり影響しているようです。

1_L_N.jpg

加工後距離元画像距離
1_Z.jpg0.0533022279399845141_L_N_F.jpg0.14050397598929443
1_Z_N_F.jpg0.103855864875624591_S_N.jpg0.3099462039319306
2_Z_N.jpg0.136612325981780082_L_N.jpg0.15738630325535297
2_G.jpg0.19380330692973182_S_N.jpg0.3475153044570132

正常パターンです。

1_L_N_F.jpg

加工後距離元画像距離
1_S_N_F.jpg0.047219461709860911_L_N.jpg0.14050397598929443
1_T.jpg0.110787204000998621_T.jpg0.26644439000220477
2_Z_N_F.jpg0.101944266550347942_L_N_F.jpg0.1737931176616888
2_G.jpg0.197256958200917272_Z_N.jpg0.2997633350561495

異常パターンで、「1_Z_N_F.jpg」と同様に異なる被写体との距離の近さが印象的です。

1_S_N.jpg

加工後距離元画像距離
1_Z_N.jpg0.060552291069429611_S_N_F.jpg0.07506104824705973
1_G.jpg0.097338594922537051_L_N.jpg0.3099462039319306
2_Z_N_F.jpg0.138262885450820552_S_N.jpg0.13229571724130829
2_G.jpg0.192376550424541272_L_N.jpg0.33750876834021004

正常パターンです。

1_S_N_F.jpg

加工後距離元画像距離
1_Z_N_F.jpg0.025055999936510531_S_N.jpg0.07506104824705973
1_G.jpg0.11738549203333871_L_N.jpg0.294820412225697
2_Z_N_F.jpg0.09147050964265782_S_N_F.jpg0.10256521478538061
2_G.jpg0.194317877029649872_L_N.jpg0.3237407132149034

異常パターンであることに加えて、異なる被写体で最も近いものとの距離が約「0.09」とかなり近いです。

2_Z.jpg

加工後距離元画像距離
2_Z_N.jpg0.09112632007177892_T.jpg0.1682652563099004
2_G.jpg0.169378538939391482_L_N.jpg0.297475763899096
1_Z.jpg0.124100872499066981_Z.jpg0.15873566741158684
1_G.jpg0.19436798245234421_L_N.jpg0.33444531011148193

異常パターンです。
実は、見た目の印象としては被写体2の方が撮影状況での違いが少なそうに見えたので、こちらの方が成績が良いと期待していたのですが、最も近いケースでも約「0.09」と被写体1のケースと比較して遠いと判断されていますし、期待通りの結果にはなっていません。

なお、元画像に関しては、ここでも全体で最も近いものが別の被写体になっています(2例目)。

2_L.jpg

加工後距離元画像距離
2_G.jpg0.071463332277665442_L_N_F.jpg0.1452687221424216
2_S_N.jpg0.155143841992275272_S_N_F.jpg0.29288479242598187
1_L.jpg0.145365369524209751_L_N_F.jpg0.18553540009592362
1_Z_N.jpg0.197129197030975421_S_N_F.jpg0.3119170351401982

異常パターンです。

2_S.jpg

加工後距離元画像距離
2_T.jpg0.0585314666921064042_S_N.jpg0.12345849787036045
2_Z_N_F.jpg0.13475219363153492_L_N.jpg0.2816018882695466
1_T.jpg0.148460322770839431_S_N_F.jpg0.15525036907823297
1_Z_N.jpg0.193550991584048651_L_N.jpg0.3136921062260888

正常パターンです。

2_G.jpg

加工後距離元画像距離
2_L.jpg0.071463332277665442_L_N_F.jpg0.15937802391379097
2_S_N.jpg0.213405979509433962_L_N.jpg0.2501518284879478
1_L.jpg0.17340324627185911_G.jpg0.1535493942802234
1_Z_N.jpg0.248493044138763391_L_N.jpg0.29754292798111837

異常パターンです。
同じ被写体で距離が「0.2」以上離れていると判断された初めてのケースです。

なお、元画像に関しては、ここでも全体で最も近いものが別の被写体になっています(3例目)。

2_T.jpg

加工後距離元画像距離
2_S.jpg0.0585314666921064042_Z_N.jpg0.13421409168053378
2_G.jpg0.128084112348383132_L_N.jpg0.25262842359711757
1_T.jpg0.129134286331585861_T.jpg0.17167423538005955
1_L.jpg0.192158687623271931_L_N.jpg0.29122009712766384

正常パターンですが、同じ被写体で最も遠いものと、異なる被写体で最も近いものとの差がかなり接近しています。

2_Z_N.jpg

加工後距離元画像距離
2_S_N.jpg0.065380658163953442_T.jpg0.13421409168053378
2_G.jpg0.207776719370257322_L_N.jpg0.2392597742896171
1_Z_N.jpg0.120334266820584461_Z_N.jpg0.13446946286703654
1_G.jpg0.222286033877141961_L_N.jpg0.3265711914237934

異常パターンで、同じ被写体に対する距離が「0.2」以上離れているケースです。

2_Z_N_F.jpg

加工後距離元画像距離
2_S_N_F.jpg0.0560405544507408542_S_N_F.jpg0.10911182232964656
2_G.jpg0.174044762461660782_L_N.jpg0.29210721777250526
1_S_N_F.jpg0.09147050964265781_Z_N_F.jpg0.07673877645765759
1_G.jpg0.179408397878659361_L_N.jpg0.3031689272930945

異常パターンですが、「1_S_N_F.jpg」でも触れたように、異なる被写体との距離が最も近いと判断されたケースです。

なお、元画像に関しては、ここでも全体で最も近いものが別の被写体になっています(4例目)。

2_L_N.jpg

加工後距離元画像距離
2_L_N_F.jpg0.076104160963638262_L_N_F.jpg0.12851273085936765
2_G.jpg0.154117048616844722_S_N.jpg0.29827881123461675
1_S_N_F.jpg0.152695150631929951_L_N.jpg0.15738630325535297
1_G.jpg0.22159987688792761_S_N.jpg0.33750876834021004

異常パターンですが、同じ被写体で最も遠いものと、異なる被写体で最も近いものとの距離の差が小さいので、惜しいケースではあります。

2_L_N_F.jpg

加工後距離元画像距離
2_S_N_F.jpg0.060175735714219082_L_N.jpg0.12851273085936765
2_G.jpg0.132649163298661942_Z.jpg0.22025200951262203
1_L_N_F.jpg0.118959913330177861_L_N_F.jpg0.1737931176616888
1_S.jpg0.17723905783871441_S_N.jpg0.2590164118508067

異常パターンです。

2_S_N.jpg

加工後距離元画像距離
2_Z_N.jpg0.065380658163953442_S_N_F.jpg0.11907943219684702
2_G.jpg0.213405979509433962_L_N.jpg0.29827881123461675
1_Z_N.jpg0.1435269514185441_S_N_F.jpg0.1267647449836231
1_G.jpg0.23767203151151441_L_N.jpg0.3475153044570132

異常パターンで、同じ被写体に対する距離が「0.2」以上離れているケースです。

2_S_N_F.jpg

加工後距離元画像距離
2_Z_N_F.jpg0.0560405544507408542_Z_N_F.jpg0.10911182232964656
2_G.jpg0.15920527057761582_L.jpg0.29288479242598187
1_S_N_F.jpg0.135628371856946651_S_N_F.jpg0.10256521478538061
1_G.jpg0.211719559693310241_L_N.jpg0.3115218845573049

異常パターンです。

なお、元画像に関しては、ここでも全体で最も近いものが別の被写体になっています(5例目)。

分析

上記で得られた測定結果を整理したいと思います。

まず、以下の3点に着目します。

被写体1に関する最大距離0.16146349360399492
被写体2に関する最大距離0.21340597950943396
異なる被写体間の最小距離0.0914705096426578

最初に言えることは、(あくまで今回の測定の範囲では)「0.09」以下の距離であれば、同じ被写体と判断できると言うことです。
言い換えると、「0.09」以上の距離の場合は別の被写体の場合があり得る訳です。

一方で、同じ被写体の場合でも、撮影状況によって「0.2」程度の距離となってしまう場合もあり得ることが分かります。
同じ被写体を確実に判別できるように、判断基準を「0.09」以下の距離としてしまうと、同一被写体を写した多くのケースが対象外になってしまうことになりそうです。

上記より、被写体を判別するためのベストな閾値(閾値以下であれば同じ被写体、閾値以上であれば別の被写体)を決定することは難しそうです。
しかし、ベストではないにしても、ベターな閾値を考えてみる意味はあるかもしれません。

以下に、ある閾値未満に距離を限定した場合に、どれだけの成功例(同じ被写体と正しく認識できた)と失敗例(違う被写体を混同した)があったかを整理してみました。

なお、画像は各被写体ごとに11枚ありますので、自身を除けば10枚の同一被写体を写した別画像があることになります。
よって各画像ごとに成功例は最大10ケース、失敗例は最大11ケースあることになります。
被写体ごとで見れば、成功例は最大110ケース、失敗例は最大121ケースで、全体としては成功例は最大220ケース、失敗例は最大242ケースとなります。

以下の表中では、失敗例を含むケースは赤字で表記しています。
また、成功例が最大の10になった場合は、それ以上の閾値に対して失敗例を加算していくことに意味はありません。
成功例が10に到達したと言うことは、それら様々な状況で撮影された画像のいずれか1つが登録された状態であれば、それ以上の閾値でなければ対象とならない画像(別の被写体)の全てよりも近いと判断できる状態になったことを意味します。
言い換えれば、成功例が10に到達するまでに失敗例がどの程度混入するかを確認することが重要ということです。
よって、成功例が10に到達した段階で値を固定し、以降の失敗例加算は行いません。
この状態になった状況を青字で表記しています。

指定された画像0.090.100.110.120.130.14
1_Z.jpg8 / 010 / 010 / 010 / 010 / 010 / 0
1_L.jpg3 / 05 / 09 / 09 / 09 / 010 / 0
1_S.jpg4 / 04 / 08 / 010 / 010 / 010 / 0
1_G.jpg2 / 04 / 06 / 09 / 09 / 09 / 0
1_T.jpg5 / 05 / 08 / 010 / 010 / 010 / 0
1_Z_N.jpg5 / 06 / 08 / 08 / 08 / 19 / 2
1_Z_N_F.jpg3 / 05 / 07 / 110 / 110 / 110 / 1
1_L_N.jpg7 / 07 / 010 / 010 / 010 / 010 / 0
1_L_N_F.jpg6 / 07 / 09 / 110 / 210 / 210 / 2
1_S_N.jpg8 / 010 / 010 / 010 / 010 / 010 / 0
1_S_N_F.jpg5 / 05 / 19 / 110 / 110 / 110 / 1
2_Z.jpg0 / 02 / 03 / 06 / 07 / 18 / 2
2_L.jpg3 / 03 / 05 / 05 / 05 / 08 / 0
2_S.jpg2 / 02 / 05 / 08 / 08 / 010 / 0
2_G.jpg1 / 01 / 01 / 02 / 03 / 04 / 0
2_T.jpg2 / 05 / 07 / 07 / 010 / 110 / 1
2_Z_N.jpg2 / 04 / 06 / 06 / 08 / 18 / 3
2_Z_N_F.jpg2 / 03 / 14 / 35 / 37 / 39 / 5
2_L_N.jpg1 / 05 / 08 / 09 / 09 / 09 / 0
2_L_N_F.jpg4 / 04 / 06 / 06 / 18 / 210 / 2
2_S_N.jpg1 / 02 / 04 / 05 / 07 / 08 / 0
2_S_N_F.jpg2 / 03 / 03 / 03 / 06 / 08 / 1
被写体156 / 0
50% / 0%
68 / 1
62% / 1%
94 / 3
85% / 2%
106 / 4
96% / 3%
106 / 5
96% / 4%
108 / 6
98% / 5%
被写体220 / 0
18% / 0%
34 / 1
31% / 1%
52 / 3
47% / 2%
62 / 4
56% / 3%
78 / 8
71% / 7%
92 / 14
84% / 12%
全体76 / 0
34% / 0%
102 / 2
46% / 1%
146 / 6
66% / 2%
168 / 8
76% / 3%
184 / 13
83% / 5%
200 / 20
90% / 8%

改めて上記のように整理すると、被写体1,2で状況がかなり違います。
間違った結果を出さないように閾値を「0.09」にした場合、被写体1では50%の成功例があるのに対し、被写体2では18%しかありません。
つまり被写体2を写した画像に関しては、82%のケースにおいて同一被写体と判別されない(できない)ことになります。

閾値を「0.11」から「0.12」に上げる段階では、被写体1に関する成功例は85%から96%に増えますが、失敗例は2%から3%に増えるのみです。
同様に被写体2に関しては、成功例が47%から56%に増えますが、失敗例は2%から3%に増えるのみです。

閾値を「0.13」に上げると、被写体1に関する成功例は96%のまま、失敗例は4%と増え、被写体2に関しても成功例は71%と増えるものの、失敗例も7%と倍以上に増加します。

上記数値の推移から判断すると閾値「0.12」辺りのバランスが良さそうですが、それでも被写体2の場合は56%程度でしか判別が成功しない(撮影状況が判別精度に影響する)ということなので、満足度としては微妙なところです。
加えて、3%程度で誤審の可能性がありますし。

まとめ

目的とした構図の調整に関しては、かなり満足できる結果になったかと思います。
少なくとも目視レベルでは、被写体以外の影響が大幅に排除されたことを体感できます。

一方で、その結果を利用してのエンベディングと距離判定に関しては期待した程の精度には達しませんでした。
元画像をそのまま使用した場合と比較すれば、かなり改善された結果ではありますが。

精度を落としている要因としては以下のようなものが考えられます。

  • 光の当たり方や色調により、被写体の色合いが違って見える
  • ボトルに写り込んだ周辺の状況が被写体自体の模様のように判断されている

上記に関してどの程度調整の余地があるかは分かりませんが、引き続き対策を考えたいと思います。

Related articles

画像検索の精度確認(4)

前回の投稿では、エンベディングの前処...

(ちょっとニッチな)FastAPI環境構築の巻(そ...

最近Tさんがすごい勢いで記事を書かれて...

画像検索の精度確認(2)

先の投稿で、類似度の高い画像の識別に...