JS を使用して複数の画像類似性アルゴリズムを実装する

JS を使用して複数の画像類似性アルゴリズムを実装する

検索分野では、Google画像検索、Baidu画像検索、Taobaoの商品写真検索など、「類似画像・類似商品を探す」などの関連機能が以前から登場しています。画像の類似性を計算する同様の機能を実現するには、高尚な「人工知能」を使用するほか、実際には、js といくつかの簡単なアルゴリズムを通じてほぼ同様の効果を実現できます。

この記事を読む前に、Ruan Yifeng が何年も前に書いた記事「類似画像検索の原理」を読むことを強くお勧めします。この記事で取り上げているアルゴリズムも、この論文から引用したものです。

体験アドレス: https://img-compare.netlify.com/

特徴抽出アルゴリズム

理解を容易にするために、各アルゴリズムは「特徴抽出」と「特徴比較」という 2 つのステップを経ます。次に、各アルゴリズムの「特徴抽出」ステップの詳細な解釈に焦点を当て、「特徴比較」については個別に説明します。

平均ハッシュアルゴリズム

Ruan Da の記事によると、「平均ハッシュ アルゴリズム」は主に次の手順で構成されています。

最初のステップは、サイズを 8×8 に縮小して画像の詳細を削除し、構造や明暗などの基本的な情報のみを保持し、サイズや比率の違いによって生じる画像の違いを破棄することです。

2 番目のステップは、色を単純化することです。縮小した画像をグレースケールに変換します。

3 番目のステップは平均値を計算することです。すべてのピクセルの平均グレースケール値を計算します。

4 番目のステップは、ピクセルのグレースケールを比較することです。 64 ピクセルのグレースケール値を平均値と比較します。平均値以上の場合は 1 として記録され、平均値未満の場合は 0 として記録されます。

ステップ 5: ハッシュ値を計算します。前のステップの比較結果を組み合わせると、このイメージのフィンガープリントである 64 ビットの整数が形成されます。

6番目のステップは、ハッシュ値の差を計算し、類似度(ハミング距離またはコサイン値)を取得することです。

「平均ハッシュアルゴリズム」の原理と手順を理解したら、コーディングを開始できます。コードを読みやすくするために、この記事のすべての例では TypeScript を使用します。

画像圧縮:

キャンバスの drawImage() メソッドを使用して画像圧縮を実装し、getImageData() メソッドを使用して ImageData オブジェクトを取得します。

  1. エクスポート関数compressImg (imgSrc: 文字列、imgWidth:数値= 8 ): Promise < ImageData > {  
  2. 新しい Promise を返します ((resolve, reject) = > {  
  3. if (!imgSrc) {  
  4. 拒否('imgSrc は空にできません!')  
  5. }  
  6. constキャンバス=ドキュメント.createElement('キャンバス')  
  7. const ctx =キャンバス.getContext ('2d')  
  8. const img =新しい画像()  
  9. img.crossOrigin = '匿名'    
  10. img.onload =関数(){  
  11. キャンバスの幅=画像の幅   
  12. キャンバスの高さ=画像の幅   
  13. ctx?.drawImage(img, 0, 0, imgWidth, imgWidth)  
  14. const data = ctx ?.getImageData(0, 0, imgWidth, imgWidth) を ImageData として取得する 
  15. 解決(データ)  
  16. }  
  17. img.src = imgSrc    
  18. })  
  19. }

なぜキャンバスを使って画像を圧縮できるのかと疑問に思う読者もいるかもしれません。簡単に言えば、「大きな画像」を「小さなキャンバス」に描画するために、類似した色を持ついくつかの隣接するピクセルが削除されることが多く、これにより画像の情報量が効果的に削減され、圧縮効果が得られます。

上記の CompressImg() 関数では、new Image() を使用して画像を読み込み、プリセット画像の幅と高さの値を設定して画像を指定されたサイズに圧縮し、最後に圧縮された画像の ImageData データを取得します。これは、画像のすべてのピクセルに関する情報を取得できることも意味します。

ImageData の詳細については、MDN ドキュメントを参照してください。

グレースケール画像

カラー画像をグレースケール画像に変換するには、まず「グレースケール画像」の概念を理解する必要があります。 Wikipedia ではグレースケール画像を次のように説明しています:

コンピュータ サイエンスでは、グレースケールのデジタル画像とは、ピクセルごとに 1 つの色のみがサンプリングされた画像です。

ほとんどの場合、任意の色は 3 つのカラー チャネル (R、G、B) の明るさと色空間 (A) で構成でき、ピクセルは 1 つの色のみを表示するため、「ピクセル => RGBA」の対応関係が得られます。 「各ピクセルには 1 つのサンプル色のみがある」とは、このピクセルを構成する 3 つの原色チャネルの明るさが等しいことを意味するため、RGB の平均値を計算するだけで済みます。

  1. // RGBA配列に基づいてImageDataを生成する 
  2. エクスポート関数createImgData(dataDetail: number[]) {  
  3. constキャンバス=ドキュメント.createElement('キャンバス')  
  4. const ctx =キャンバス.getContext ('2d')  
  5. const imgWidth = Math.sqrt (dataDetail.length / 4)  
  6. const newImageData = ctx ?.createImageData(imgWidth, imgWidth) を ImageData として作成します 
  7. i = 0とすると、i <  データ詳細の長さ; i += 4) {  
  8. R =データ詳細[i]とする 
  9. G = dataDetail [i + 1]とします。  
  10. B =データ詳細[i + 2]とします。  
  11. Alpha = dataDetail [i + 3]とします。  
  12. 新しい画像データ.data[i] = R  
  13. 新しい画像データ.data[i + 1] = G  
  14. 新しい画像データ.data[i + 2] = B  
  15. newImageData.data[i + 3] = アルファ 
  16. }  
  17. 新しい画像データを返す 
  18. }  
  19. エクスポート関数 createGrayscale (imgData: ImageData) {  
  20. const newData: number[] = Array(imgData.data.length)  
  21. 新しいデータを埋め込む(0)  
  22. imgData.data.forEach((_data, インデックス) = > {  
  23. ((インデックス + 1) % 4 === 0) の場合 {  
  24. const R = imgData.data [インデックス - 3]  
  25. const G = imgData.data [インデックス - 2]  
  26. const B = imgData.data [インデックス - 1]
  27.   定数グレー= ~~((R + G + B) / 3)  
  28. newData[インデックス - 3] = グレー 
  29. newData[インデックス - 2] = グレー 
  30. newData[インデックス - 1] = グレー
  31.   newData[index] = 255 // アルファ値は255に固定されます 
  32. }  
  33. })  
  34. createImgData(newData) を返します 
  35. }

ImageData.data は Uint8ClampedArray 配列であり、「RGBA 配列」として理解できます。配列内の各数値の範囲は 0 から 255 で、4 つの数値の各グループはピクセルの RGBA 値を表します。 ImageData は読み取り専用オブジェクトなので、context.createImageData() を使用して新しい ImageData オブジェクトを作成するための createImageData() メソッドを記述する必要があります。

グレースケール画像を取得したら、指紋抽出を実行できます。

指紋抽出

「平均ハッシュアルゴリズム」では、グレースケール画像内のピクセルのグレースケール値が平均値より大きい場合は 1 とみなされ、そうでない場合は 0 とみなされます。これらの情報を組み合わせると、画像の指紋が作成されます。グレースケール画像の ImageData オブジェクトがあるので、指紋の抽出は簡単になります。

  1. エクスポート関数 getHashFingerprint (imgData: ImageData) {  
  2. const grayList = imgData .data.reduce((pre: number[], cur, index) = > {  
  3. ((インデックス + 1) % 4 === 0) の場合 {  
  4. pre.push(imgData.data[インデックス - 1])  
  5. }  
  6. 戻る前 
  7. }, [])  
  8. 定数長さ= grayList.length  
  9. const grayAverage = grayList .reduce((pre, next) = > (pre + next), 0) / 長さ 
  10. grayList.map( gray = > (gray > = grayAverage ? 1 : 0)).join('')を返します。  
  11. }

上記の一連の手順により、「平均ハッシュ アルゴリズム」を通じて画像の指紋情報を取得できます (例はサイズ 8×8 のグレースケール画像です)。

知覚ハッシュアルゴリズム

「知覚ハッシュ アルゴリズム」の詳細な紹介については、こちらの記事「知覚ハッシュ アルゴリズムに基づく視覚オブジェクト追跡」を参照してください。

簡単に言えば、離散コサイン変換の後、アルゴリズムは画像をピクセル領域から周波数領域に変換し、有効な情報を運ぶ低周波成分が DCT マトリックスの左上隅に集中するため、この機能を使用して画像の特徴を抽出できます。

アルゴリズムの手順は次のとおりです。

  • サイズを縮小: pHash は小さい画像から開始しますが、88、3232 より大きい画像が最適です。この目的は周波数を下げることではなく、DCT 計算を簡素化することです。
  • 色を簡素化: 計算をさらに簡素化するために、画像をグレースケール画像に変換します。
  • DCT の計算: 画像の DCT 変換を計算し、32*32 の DCT 係数行列を取得します。
  • DCT の削減: DCT の結果は 3232 サイズのマトリックスですが、画像の最低周波数を表す左上隅の 88 サイズのマトリックスのみを保持する必要があります。
  • 平均を計算します。平均ハッシュと同様に、DCT の平均を計算します。
  • ハッシュ値を計算します。これは最も重要なステップです。8*8 DCT マトリックスに従って、64 ビットのハッシュ値を 0 または 1 に設定します。DCT 平均以上の値を「1」に設定し、DCT 平均未満の値を「0」に設定します。これらを組み合わせると、64 ビットの整数が形成され、これがイメージのフィンガープリントになります。

コードに戻って、まず DCT メソッドを追加します。

  1. 関数 memoizeCosines (N: 数値、cosMap: 任意) {  
  2. cosMap cosMap = cosMap || {}  
  3. cosMap[N] = 新しい配列(N * N)  
  4. PI_N = Math.PI / Nとします。  
  5. k = 0とすると、k <   N ;k++){  
  6. ( n = 0とすると、n <   N ;n++){  
  7. cosMap[N][n + (k * N)] = Math.cos(PI_N * (n + 0.5) * k)  
  8. }  
  9. }  
  10. cosMapを返す 
  11. }  
  12. 関数dct(信号:数値[]、スケール:数値= 2 ){  
  13. L =信号の長さとする 
  14. cosMap: any = nullとします   
  15. もし (!cosMap || !cosMap[L]) {  
  16. cosMap = memoizeCosines (L, cosMap)  
  17. }  
  18. 係数=信号.map(function () { return 0 })とします。  
  19. 係数.map(function (_, ix) {を返す 
  20. スケールを返す * signal.reduce(function (prev, cur, index) {  
  21. prev + (cur * cosMap[L][index + (ix * L)]) を返す 
  22. }, 0)  
  23. })  
  24. }

次に、DCT 方式で生成された 1 次元配列を 2 次元配列 (行列) にアップグレードし、行列から「左上隅」の内容を取得するという 2 つの行列処理方式を追加します。

  1. // 1次元配列のアップグレード 
  2. 関数createMatrix(arr: number[]) {  
  3. 定数長さ= arr .長さ 
  4. const matrixWidth = Math.sqrt (長さ)  
  5. 定数行列= []  
  6. i = 0とすると、i <  マトリックス幅; i++) {  
  7. const _temp = arr .slice(i * マトリックス幅、i * マトリックス幅 + マトリックス幅)  
  8. マトリックス.push(_temp)  
  9. }  
  10. 戻り行列 
  11. }  
  12. // 行列から「左上隅」のサイズが範囲 × 範囲であるコンテンツを取得します 
  13. 関数 getMatrixRange (行列: 数値[][], 範囲:数値= 1 ) {  
  14. 定数範囲行列= []  
  15. i = 0とすると、i <  範囲; i++) {  
  16. j = 0とすると、j <  範囲; j++) {  
  17. 範囲Matrix.push(行列[i][j])
  18.   }  
  19. }  
  20. 範囲行列を返す 
  21. }

以前「平均ハッシュアルゴリズム」で記述したグレースケール画像変換関数 createGrayscale() を再利用すると、「知覚ハッシュアルゴリズム」の固有値を取得できます。

  1. エクスポート関数 getPHashFingerprint (imgData: ImageData) {  
  2. const dct dctData = dct(imgData.data は任意)  
  3. 定数dctMatrix = createMatrix (dctData)  
  4. 定数rangeMatrix = getMatrixRange (dctMatrix、dctMatrix.length / 8)  
  5. 定数rangeAve = rangeMatrix.reduce ((pre, cur) = > pre + cur, 0) / rangeMatrix.length  
  6. rangeMatrix.map( val = > (val > = rangeAve ? 1 : 0)).join('')を返します。  
  7. }

色分布法

まず、Ruan Da の「色彩配分法」の説明から一節を引用します。

Ruan Da は 256 色の値を 4 つに簡略化しました。この原則に基づいて、色配分法のアルゴリズムを設計するときに、この間隔の分割を変更可能に設定できます。唯一の要件は、間隔の数が 256 で割り切れる必要があることです。アルゴリズムは次のとおりです。

  1. // 色の間隔を分割します。間隔のデフォルトの数は 4 です。  
  2. // 256色の値を4つに簡略化 
  3. エクスポート関数simplicedColorData(imgData:ImageData、zoneAmount: number = 4 ){  
  4. const colorZoneDataList: 数値[] = []  
  5. 定数ゾーンステップ= 256 / ゾーン量 
  6. const zoneBorder = [0] // ゾーン境界
  7.   ( i = 1とします; i < = zoneAmount; i++) {  
  8. ゾーン境界をプッシュ(ゾーンステップ * i - 1)  
  9. }  
  10. imgData.data.forEach((データ, インデックス) = > {  
  11. ((インデックス + 1) % 4 !== 0) の場合 {  
  12. i = 0とすると、i <  ゾーン境界の長さ; i++) {  
  13. if (データ>ゾーンボーダー[i] && データ< = ゾーンボーダー[i + 1]) {  
  14. データ= i    
  15. }  
  16. }  
  17. }  
  18. colorZoneDataList.push(データ)  
  19. })  
  20. colorZoneDataListを返す 
  21. }

色の値を簡略化した後、さまざまなグループに分類できます。

  1. エクスポート関数 seperateListToColorZone (simplifiedDataList: number[]) {  
  2. const ゾーンリスト: 文字列[] = []  
  3. tempZone: 数値[] = []とします 
  4. 簡略化されたデータリスト.forEach((データ, インデックス) = > {  
  5. ((インデックス + 1) % 4 !== 0) の場合 {  
  6. tempZone.push(データ)  
  7. } それ以外 {
  8.   zonedList.push(JSON.stringify(tempZone))  
  9. tempZone = []  
  10. }  
  11. })  
  12. zonedListを返す 
  13. }

最後に、各同一グループの合計数を数えるだけです。

  1. エクスポート関数 getFingerprint (zonedList: string[], zoneAmount: number = 16 ) {  
  2. 定数カラーセパレートマップ: {  
  3. [キー: 文字列]: 数値 
  4. } = {}  
  5. i = 0とすると、i <  ゾーン量; i++) {  
  6. j = 0とすると、j <  ゾーン量; j++) {  
  7. k = 0とすると、k <  ゾーン量; k++) {  
  8. colorSeperateMap[JSON.stringify([i, j, k])] = 0  
  9. }  
  10. }  
  11. }  
  12. ゾーンリスト.forEach(ゾーン= > {  
  13. colorSeperateMap[ゾーン]++  
  14. })  
  15. Object.values(colorSeperateMap) を返します。  
  16. }

コンテンツ機能メソッド

「コンテンツ特徴方式」とは、画像をグレースケール画像に変換した後、「バイナリ画像」に変換し、ピクセル値(黒または白)に基づいて指紋を形成して比較する方法を指します。このアルゴリズムの中核は、バイナリ画像を生成するための「しきい値」を見つけることです。

グレースケール画像を生成するために、「平均ハッシュ アルゴリズム」で説明した RGB 平均を取る方法とは異なり、ここでは加重アプローチを使用します。なぜこれをするのですか?これには色彩科学のいくつかの概念が関係します。

詳細については、以下に簡単にまとめた「グレースケールから RGB への変換」という記事を参照してください。

最も簡単な方法は、平均 RGB 値を持つグレースケール画像を使用することですが、赤、緑、青の 3 色の波長と、それらが画像全体に与える影響は無視されます。次の図を例にとると、RGB の平均値をそのままグレースケールとして取得すると、処理されたグレースケール画像は全体的に暗くなり、その後のバイナリ画像の生成に大きな支障をきたすことになります。

では、どうすればこの状況を改善できるのでしょうか?答えは、RGB の 3 つの色に異なる重みを追加することです。赤色光は波長が長く、緑色光は波長が短く視覚への刺激が比較的少ないことを考えると、赤色光の比重を意図的に減らし、緑色光の比重を増やす必要があります。統計の結果、より良い重量比は R:G:B = 0.299:0.587:0.114 です。

こうすることで、グレースケール処理関数を取得できます。

  1. 列挙型グレースケールウェイト{  
  2. R = .299、  
  3. G = .587、  
  4. =.114  
  5. }  
  6. 関数 toGray (imgData: ImageData) {  
  7. 定数グレーデータ= []  
  8. 定数データ= imgData.data  
  9. i = 0とすると、i <  データ長さ; i += 4) {  
  10. 定数グレー= ~~(データ[i] * グレースケールウェイト.R + データ[i + 1] * グレースケールウェイト.G + データ[i + 2] * グレースケールウェイト.B)  
  11. データ[i] = データ[i + 1] = データ[i + 2] = グレー 
  12. grayData.push(グレー)  
  13. }  
  14. grayDataを返す 
  15. }

上記の関数は grayData 配列を返します。この配列の各要素はピクセルのグレー値を表します (RBG は同じ値を持つため、必要な値は 1 つだけです)。次に、Otsu法を使用して2値画像の閾値を計算します。 「大金法」については、すでに阮大氏の記事で詳しく論じられているので、ここでは詳しく述べません。ここで「Otsu Method」の Java 実装を見つけ、後で少し修正して js バージョンに変更しました。

  1. / OTSUアルゴリズム 
  2. // http://www.labbookpages.co.uk/software/imgProc/otsuThreshold.html から書き換え 
  3. エクスポート関数 OTSUAlgorithm (imgData: ImageData) {  
  4. const grayData = toGray (画像データ)  
  5. ptr = 0とする   
  6. histData = Array (256).fill(0)とします。  
  7. 合計= grayData.lengthとします 
  8. 一方で (ptr <  合計) {  
  9. h = 0xFF & grayData[ptr++]とします。  
  10. 履歴データ[h]++  
  11. }  
  12. 合計0する   
  13. i = 0とすると、i <   256 ; i++) {  
  14. 合計 += i * 履歴データ[i]  
  15. }  
  16. wB = 0とする   
  17. wF = 0とする   
  18. 合計B = 0とする   
  19. varMax = 0とする   
  20. 閾値0にする   
  21. t = 0とすると、t <   256 ; t++) {  
  22. wB += ヒストデータ[t]  
  23. ( wB === 0 ) の場合、継続 
  24. wF =合計- wB  
  25. if ( wF === 0) ブレーク 
  26. 合計B += t * 履歴データ[t]  
  27. mB = sumB / wBとする 
  28. mF = (合計 - 合計B) / wFとします。  
  29. varBetween = wB * wF * (mB - mF) ** 2とします。  
  30. (変数Between >変数Max)の場合{  
  31. varMax = varBetween    
  32. tしきい値= t  
  33. }  
  34. }  
  35. 戻り閾値 
  36. }

OTSUAlgorithm() 関数は ImageData オブジェクトを受け取ります。前のステップで toGray() メソッドを通じてグレースケール値リストを取得した後、「Otsu 法」に従って最適なしきい値を計算して返します。次に、このしきい値を使用して元の画像を処理してバイナリ画像を取得します。

  1. エクスポート関数バイナリ化 (imgData: ImageData, しきい値: 数値) {  
  2. constキャンバス=ドキュメント.createElement('キャンバス')  
  3. const ctx =キャンバス.getContext ('2d')
  4.   const imgWidth = Math.sqrt (imgData.data.length / 4)  
  5. const newImageData = ctx ?.createImageData(imgWidth, imgWidth) を ImageData として作成します 
  6. i = 0とすると、i <   imgData.data.length ; i += 4) {  
  7. R = imgData .data[i]とします。  
  8. G = imgData .data[i + 1]とします。  
  9. B = imgData .data[i + 2]とします。  
  10. Alpha = imgData.data [i + 3]とします。  
  11. 合計= (R + G + B) / 3 とする 
  12. newImageData.data[i] = 合計>しきい値 ? 255 : 0  
  13. newImageData.data[i + 1] = 合計>しきい値 ? 255 : 0  
  14. newImageData.data[i + 2] = 合計>しきい値 ? 255 : 0  
  15. newImageData.data[i + 3] = アルファ 
  16. }  
  17. 新しい画像データを返す 
  18. }

画像サイズが N×N の場合、バイナリ画像の「白か黒か」の特性に基づいて、N×N の 0-1 行列が得られます。これが指紋です。

特徴マッチングアルゴリズム

さまざまな方法でさまざまな種類の画像フィンガープリント(特徴)を取得した後、それらをどのように比較すればよいでしょうか?ここでは、3 つの比較アルゴリズムを紹介し、これらのアルゴリズムが適用可能な状況を分析します。

ハミング距離

以下はWikipediaの「ハミング距離」の説明です。

情報理論では、同じ長さの 2 つの文字列間のハミング距離は、2 つの文字列の対応する位置にある異なる文字の数です。つまり、ある文字列を別の文字列に変換するために置き換える必要がある文字の数です。

例えば:

  • 1011101 と 1001001 間のハミング距離は 2 です。
  • 2143896 と 2233796 の間のハミング距離は 3 です。
  • 「toned」と「roses」の間のハミング距離は 3 です。

意味を理解したら、ハミング距離を計算するメソッドを書くことができます。

  1. エクスポート関数 hammingDistance (str1: 文字列、str2: 文字列) {  
  2. 距離0する   
  3. const str1 str1Arr = str1.split('')  
  4. const str2 str2Arr = str2.split('')  
  5. str1Arr.forEach((文字, インデックス) = > {  
  6. if (letter !== str2Arr[index]) {  
  7. 距離++  
  8. }  
  9. })  
  10. 戻り距離 
  11. }

hammingDistance() メソッドを使用して、Wikipedia の例を確認します。

検証結果は予想通りです。

ハミング距離がわかれば、同じ長さの 2 つの文字列間の類似性もわかります (ハミング距離が小さいほど、類似性は大きくなります)。

類似度 = (文字列の長さ - ハミング距離) / 文字列の長さ

コサイン類似度

Wikipedia からコサイン類似度の定義を学ぶことができます。

コサイン類似度は、2 つのベクトル間の角度のコサインを測定することによって、2 つのベクトル間の類似度を測定します。 0 度の角度のコサインは 1 であり、その他の角度のコサインは 1 以下であり、最小値は -1 です。したがって、2 つのベクトル間の角度の余弦によって、2 つのベクトルがほぼ同じ方向を向いているかどうかが決まります。 2 つのベクトルが同じ方向を向いている場合、コサイン類似度値は 1 になります。2 つのベクトル間の角度が 90° の場合、コサイン類似度値は 0 になります。2 つのベクトルが完全に反対方向を向いている場合、コサイン類似度値は -1 になります。これはベクトルの長さとは無関係であり、ベクトルが指す方向のみに関係することがわかります。コサイン類似度は通常、正の空間で使用されるため、指定される値は 0 から 1 の間になります。

これらの境界は任意の次元のベクトル空間に当てはまり、コサイン類似度は高次元の正の空間で最も一般的に使用されることに注意してください。

コサイン類似度は、2 つのベクトル間の角度を計算し、2 つのベクトルの方向が類似しているかどうかを直感的に示します。これは、2 つの N×N 0-1 行列の類似度を計算するのに非常に便利です。コサイン類似度の式に従って、その js 実装を記述できます。

  1. エクスポート関数 cosineSimilarity (sampleFingerprint: number[], targetFingerprint: number[]) {  
  2. // cosθ = ∑n, i = 1 (Ai × Bi) / (√∑n, i = 1 (Ai)^2) × (√∑n, i = 1 (Bi)^2) = A · B / |A| × |B|  
  3. 定数長さ= sampleFingerprint.length  
  4. 内部製品= 0とする   
  5. i = 0とすると、i <  長さ; i++) {  
  6. 内部製品 += サンプル指紋[i] * ターゲット指紋[i]  
  7. }  
  8. vecA = 0とする   
  9. vecB = 0とする   
  10. i = 0とすると、i <  長さ; i++) {  
  11. vecA += サンプル指紋[i] ** 2  
  12. vecB += ターゲット指紋[i] ** 2  
  13. }  
  14. 定数outerProduct = Math.sqrt (vecA) * Math.sqrt(vecB)  
  15. innerProduct / outerProduct を返す 
  16. }

2つの比較アルゴリズムの適用可能なシナリオ

「ハミング距離」と「コサイン類似度」という 2 つの特徴比較アルゴリズムを理解した後、どの特徴抽出アルゴリズムのシナリオに適しているかを確認する必要があります。

まずは「色の配分方法」について見てみましょう。 「色分布法」では、画像の色を区間に分割し、異なる色区間の数を数えることで特徴を取得します。ここでの固有値は「量」、つまり0-1以外の行列に関連しています。

明らかに、2 つの「色分布方法」の特徴の類似性を比較するには、「ハミング距離」は適用できず、「コサイン類似度」を通じてのみ計算できます。

次に「平均ハッシュアルゴリズム」と「コンテンツ特徴量法」について見てみましょう。結果から、両方の特徴抽出アルゴリズムは N×N 0-1 行列を取得でき、行列内の要素の値は「量」とは関係なく、0-1 のみであることがわかります。そのため、「ハミング距離」や「コサイン類似度」による類似度の計算に適しています。

計算精度

画像の特徴を抽出し、比較する方法を理解した後、最も重要なことは類似性の計算精度を理解することです。

この記事で説明されている類似性は客観的なアルゴリズムによってのみ達成されますが、2 つの画像が「類似している」かどうかを判断することは非常に主観的な問題です。そこで私は、異なるアルゴリズムと精度を使用して 2 つの画像の類似性を計算できるシンプルなサービスを作成しました。

https://img-compare.netlify.com/

さまざまな方法でさまざまな資料を比較した結果、次のような非常に主観的な結論に達しました。

  • より豊かな色彩とより詳細な描写を持つ 2 つの画像の場合、「色配分法」の計算結果が最も直感的です。

  • コンテンツは似ているが色が大きく異なる 2 つの画像の場合、「コンテンツ特徴法」と「平均/知覚ハッシュ アルゴリズム」の両方で直感的な結果を生成できます。

  • 「色配分法」では、区間の分割数が計算結果に大きく影響するため、適切な区間を選択することが重要です。

まとめると、3 つの特徴抽出アルゴリズムと 2 つの特徴比較アルゴリズムにはそれぞれ長所と短所があり、実際のアプリケーションではさまざまな状況に応じて柔軟に選択する必要があります。

要約する

この記事は、Ruan Yifeng の「類似画像検索の原則」に関する 2 つの記事を読んで、自分の実践を要約した後に書かれました。色彩や数学などの分野に対する私の理解は浅いため、記事には間違いが含まれている可能性があります。間違った記述を見つけた場合は、メッセージを残していただければ、すぐに修正します。

<<:  このアルゴリズムはアーキテクチャを自動的に最適化し、エンジニアがニューラルネットワークを設計するのに役立ちます。

>>:  人工知能は、電力網とユビキタス電力のIoTの構築と開発にとって重要な方向性となるだろう

ブログ    
ブログ    
ブログ    
ブログ    

推薦する

詳細な分析: AI がイノベーションを容易にする方法

開発手段。イノベーションの結果は、企業が市場のニーズを満たす新製品を継続的に設計・生産することを奨励...

...

C# DES アルゴリズムの暗号化と復号化の例の分析

C# DES アルゴリズムの暗号化と復号化は、開発のセキュリティ部分として、その使用方法を理解する必...

ディープラーニングに関する面接で絶対に聞きたい12の質問

導入これら 12 の質問は、現在の面接で最も人気のある質問です。これらは非常に基本的な質問ですが、面...

【WOT2018】蘇寧ドットコム高超:AI技術+短編動画を電子商取引プラットフォームに応用

[51CTO.comより引用] 2013年頃、携帯電話やパソコンに短編動画が大量に登場し、低コスト、...

人事におけるAI技術の重要性

[[401318]]人工知能はリアルタイムで意思決定を行う能力があり、事前にプログラムされたアルゴリ...

...

...

AIとインフラストラクチャのゲームチェンジャーが市場で成熟しつつあります。

機械学習が「人間レベル」の能力に到達するには、多くのトレーニング反復とラベル付きデータが必要です。こ...

スマートテクノロジーが戦いに加わり、宇宙探査が新たな機会をもたらす

今日、現代科学技術の出現と発展、そしてさまざまなインテリジェント技術の登場により、人類の宇宙旅行はよ...

機械学習ガバナンスとは何ですか?

なぜ組織は機械学習のガバナンスに苦労するのでしょうか? 組織の機械学習ガバナンスに取り組もうとすると...

機械学習は数字を数え、マウスをクリックしてモデルをトレーニングし、残りはコンピューターに任せます

[[432947]] JAVA ベースで開発された Weka は、機械学習やデータマイニングに適した...

ソフトウェアが自動車を飲み込んでいる、伝統的な自動車産業は消滅の危機に瀕しているのでしょうか?

[[440100]]半導体チップの継続的な不足が世界の自動車生産の減少につながるとの予測が高まって...

...