精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

利用JS實現多種圖片相似度算法

開發 前端 算法
為了便于理解,每種算法都會經過“特征提取”和“特征比對”兩個步驟進行。接下來將著重對每種算法的“特征提取”步驟進行詳細解讀,而“特征比對”則單獨進行闡述。

 

在搜索領域,早已出現了“查找相似圖片/相似商品”的相關功能,如 Google 搜圖,百度搜圖,淘寶的拍照搜商品等。要實現類似的計算圖片相似度的功能,除了使用聽起來高大上的“人工智能”以外,其實通過 js 和幾種簡單的算法,也能八九不離十地實現類似的效果。

在閱讀本文之前,強烈建議先閱讀完阮一峰于多年所撰寫的《相似圖片搜索的原理》相關文章,本文所涉及的算法也來源于其中。

體驗地址:https://img-compare.netlify.com/

特征提取算法

為了便于理解,每種算法都會經過“特征提取”和“特征比對”兩個步驟進行。接下來將著重對每種算法的“特征提取”步驟進行詳細解讀,而“特征比對”則單獨進行闡述。

平均哈希算法

參考阮大的文章,“平均哈希算法”主要由以下幾步組成:

第一步,縮小尺寸為8×8,以去除圖片的細節,只保留結構、明暗等基本信息,摒棄不同尺寸、比例帶來的圖片差異。

第二步,簡化色彩。將縮小后的圖片轉為灰度圖像。

第三步,計算平均值。計算所有像素的灰度平均值。

第四步,比較像素的灰度。將64個像素的灰度,與平均值進行比較。大于或等于平均值,記為1;小于平均值,記為0。

第五步,計算哈希值。將上一步的比較結果,組合在一起,就構成了一個64位的整數,這就是這張圖片的指紋。

第六步,計算哈希值的差異,得出相似度(漢明距離或者余弦值)。

明白了“平均哈希算法”的原理及步驟以后,就可以開始編碼工作了。為了讓代碼可讀性更高,本文的所有例子我都將使用 typescript 來實現。

圖片壓縮:

我們采用 canvas 的 drawImage() 方法實現圖片壓縮,后使用 getImageData() 方法獲取 ImageData 對象。 

  1. export function compressImg (imgSrc: string, imgWidth: number = 8): Promise<ImageData> {  
  2.   return new Promise((resolve, reject) => {  
  3.     if (!imgSrc) {  
  4.       reject('imgSrc can not be empty!')  
  5.     }  
  6.     const canvas = document.createElement('canvas')  
  7.     const ctx = canvas.getContext('2d')  
  8.     const img = new Image()  
  9.     img.crossOrigin = 'Anonymous'  
  10.     img.onload = function () {  
  11.       canvas.width = imgWidth  
  12.       canvas.height = imgWidth  
  13.       ctx?.drawImage(img, 0, 0, imgWidth, imgWidth)  
  14.       const data = ctx?.getImageData(0, 0, imgWidth, imgWidth) as ImageData  
  15.       resolve(data)  
  16.     }  
  17.     img.src = imgSrc  
  18.   })  

可能有讀者會問,為什么使用 canvas 可以實現圖片壓縮呢?簡單來說,為了把“大圖片”繪制到“小畫布”上,一些相鄰且顏色相近的像素往往會被刪減掉,從而有效減少了圖片的信息量,因此能夠實現壓縮的效果:

在上面的 compressImg() 函數中,我們利用 new Image() 加載圖片,然后設定一個預設的圖片寬高值讓圖片壓縮到指定的大小,最后獲取到壓縮后的圖片的 ImageData 數據——這也意味著我們能獲取到圖片的每一個像素的信息。

關于 ImageData,可以參考 MDN 的文檔介紹

圖片灰度化

為了把彩色的圖片轉化成灰度圖,我們首先要明白“灰度圖”的概念。在維基百科里是這么描述灰度圖像的:

在計算機領域中,灰度(Gray scale)數字圖像是每個像素只有一個采樣顏色的圖像。

大部分情況下,任何的顏色都可以通過三種顏色通道(R, G, B)的亮度以及一個色彩空間(A)來組成,而一個像素只顯示一種顏色,因此可以得到“像素 => RGBA”的對應關系。而“每個像素只有一個采樣顏色”,則意味著組成這個像素的三原色通道亮度相等,因此只需要算出 RGB 的平均值即可: 

  1. // 根據 RGBA 數組生成 ImageData  
  2. export function createImgData (dataDetail: number[]) {  
  3.   const canvas = document.createElement('canvas')  
  4.   const ctx = canvas.getContext('2d')  
  5.   const imgWidth = Math.sqrt(dataDetail.length / 4)  
  6.   const newImageData = ctx?.createImageData(imgWidth, imgWidth) as ImageData  
  7.   for (let i = 0; i < dataDetail.length; i += 4) {  
  8.     let R = dataDetail[i]  
  9.     let G = dataDetail[i + 1]  
  10.     let B = dataDetail[i + 2]  
  11.     let Alpha = dataDetail[i + 3]  
  12.     newImageData.data[i] = R  
  13.     newImageData.data[i + 1] = G  
  14.     newImageData.data[i + 2] = B  
  15.     newImageData.data[i + 3] = Alpha  
  16.   }  
  17.   return newImageData  
  18.  
  19. export function createGrayscale (imgData: ImageData) {  
  20.   const newData: number[] = Array(imgData.data.length)  
  21.   newData.fill(0)  
  22.   imgData.data.forEach((_data, index) => {  
  23.     if ((index + 1) % 4 === 0) {  
  24.       const R = imgData.data[index - 3]  
  25.       const G = imgData.data[index - 2]  
  26.       const B = imgData.data[index - 1] 
  27.        const gray = ~~((R + G + B) / 3)  
  28.       newData[index - 3] = gray  
  29.       newData[index - 2] = gray  
  30.       newData[index - 1] = gray 
  31.        newData[index] = 255 // Alpha 值固定為255  
  32.     }  
  33.   })  
  34.   return createImgData(newData)  

ImageData.data 是一個 Uint8ClampedArray 數組,可以理解為“RGBA數組”,數組中的每個數字取值為0~255,每4個數字為一組,表示一個像素的 RGBA 值。由于ImageData 為只讀對象,所以要另外寫一個 creaetImageData() 方法,利用 context.createImageData() 來創建新的 ImageData 對象。

拿到灰度圖像以后,就可以進行指紋提取的操作了。

指紋提取

在“平均哈希算法”中,若灰度圖的某個像素的灰度值大于平均值,則視為1,否則為0。把這部分信息組合起來就是圖片的指紋。由于我們已經拿到了灰度圖的 ImageData 對象,要提取指紋也就變得很容易了: 

  1. export function getHashFingerprint (imgData: ImageData) {  
  2.   const grayList = imgData.data.reduce((pre: number[], cur, index) => {  
  3.     if ((index + 1) % 4 === 0) {  
  4.       pre.push(imgData.data[index - 1])  
  5.     }  
  6.     return pre  
  7.   }, [])  
  8.   const length = grayList.length  
  9.   const grayAverage = grayList.reduce((pre, next) => (pre + next), 0) / length  
  10.   return grayList.map(gray => (gray >= grayAverage ? 1 : 0)).join('')  

通過上述一連串的步驟,我們便可以通過“平均哈希算法”獲取到一張圖片的指紋信息(示例是大小為8×8的灰度圖):

感知哈希算法

關于“感知哈希算法”的詳細介紹,可以參考這篇文章:《基于感知哈希算法的視覺目標跟蹤》。

簡單來說,該算法經過離散余弦變換以后,把圖像從像素域轉化到了頻率域,而攜帶了有效信息的低頻成分會集中在 DCT 矩陣的左上角,因此我們可以利用這個特性提取圖片的特征。

該算法的步驟如下:

  •   縮小尺寸:pHash以小圖片開始,但圖片大于88,3232是最好的。這樣做的目的是簡化了DCT的計算,而不是減小頻率。
  •   簡化色彩:將圖片轉化成灰度圖像,進一步簡化計算量。
  •   計算DCT:計算圖片的DCT變換,得到32*32的DCT系數矩陣。
  •   縮小DCT:雖然DCT的結果是3232大小的矩陣,但我們只要保留左上角的88的矩陣,這部分呈現了圖片中的最低頻率。
  •   計算平均值:如同均值哈希一樣,計算DCT的均值。
  •   計算hash值:這是最主要的一步,根據8*8的DCT矩陣,設置0或1的64位的hash值,大于等于DCT均值的設為”1”,小于DCT均值的設為“0”。組合在一起,就構成了一個64位的整數,這就是這張圖片的指紋。

回到代碼中,首先添加一個 DCT 方法: 

  1. function memoizeCosines (N: number, cosMap: any) {  
  2.   cosMapcosMap = cosMap || {}  
  3.   cosMap[N] = new Array(N * N)  
  4.   let PI_N = Math.PI / N  
  5.   for (let k = 0; k < N; k++) {  
  6.     for (let n = 0; n < N; n++) {  
  7.       cosMap[N][n + (k * N)] = Math.cos(PI_N * (n + 0.5) * k)  
  8.     }  
  9.   }  
  10.   return cosMap  
  11.  
  12. function dct (signal: number[], scale: number = 2) {  
  13.   let L = signal.length  
  14.   let cosMap: any = null  
  15.   if (!cosMap || !cosMap[L]) {  
  16.     cosMap = memoizeCosines(L, cosMap)  
  17.   }  
  18.   let coefficients = signal.map(function () { return 0 })  
  19.   return coefficients.map(function (_, ix) {  
  20.     return scale * signal.reduce(function (prev, cur, index) {  
  21.       return prev + (cur * cosMap[L][index + (ix * L)])  
  22.     }, 0)  
  23.   })  

然后添加兩個矩陣處理方法,分別是把經過 DCT 方法生成的一維數組升維成二維數組(矩陣),以及從矩陣中獲取其“左上角”內容。 

  1. // 一維數組升維  
  2. function createMatrix (arr: number[]) {  
  3.   const length = arr.length  
  4.   const matrixWidth = Math.sqrt(length)  
  5.   const matrix = []  
  6.   for (let i = 0; i < matrixWidth; i++) {  
  7.     const _temp = arr.slice(i * matrixWidth, i * matrixWidth + matrixWidth)  
  8.     matrix.push(_temp)  
  9.   }  
  10.   return matrix  
  11.  
  12. // 從矩陣中獲取其“左上角”大小為 range × range 的內容  
  13. function getMatrixRange (matrix: number[][], range: number = 1) {  
  14.   const rangeMatrix = []  
  15.   for (let i = 0; i < range; i++) {  
  16.     for (let j = 0; j < range; j++) {  
  17.       rangeMatrix.push(matrix[i][j]) 
  18.      }  
  19.   }  
  20.   return rangeMatrix  

復用之前在“平均哈希算法”中所寫的灰度圖轉化函數createGrayscale(),我們可以獲取“感知哈希算法”的特征值: 

  1. export function getPHashFingerprint (imgData: ImageData) {  
  2.   const dctdctData = dct(imgData.data as any)  
  3.   const dctMatrix = createMatrix(dctData)  
  4.   const rangeMatrix = getMatrixRange(dctMatrix, dctMatrix.length / 8)  
  5.   const rangeAve = rangeMatrix.reduce((pre, cur) => pre + cur, 0) / rangeMatrix.length  
  6.   return rangeMatrix.map(val => (val >= rangeAve ? 1 : 0)).join('')  

顏色分布法

首先摘抄一段阮大關于“顏色分布法“的描述:

阮大把256種顏色取值簡化成了4種。基于這個原理,我們在進行顏色分布法的算法設計時,可以把這個區間的劃分設置為可修改的,唯一的要求就是區間的數量必須能夠被256整除。算法如下:

 

  1. // 劃分顏色區間,默認區間數目為4個  
  2. // 把256種顏色取值簡化為4種  
  3. export function simplifyColorData (imgData: ImageData, zoneAmount: number = 4) {  
  4.   const colorZoneDataList: number[] = []  
  5.   const zoneStep = 256 / zoneAmount  
  6.   const zoneBorder = [0] // 區間邊界 
  7.    for (let i = 1; i <= zoneAmount; i++) {  
  8.     zoneBorder.push(zoneStep * i - 1)  
  9.   }  
  10.   imgData.data.forEach((data, index) => {  
  11.     if ((index + 1) % 4 !== 0) {  
  12.       for (let i = 0; i < zoneBorder.length; i++) {  
  13.         if (data > zoneBorder[i] && data <= zoneBorder[i + 1]) {  
  14.           data = i  
  15.         }  
  16.       }  
  17.     }  
  18.     colorZoneDataList.push(data)  
  19.   })  
  20.   return colorZoneDataList  

把顏色取值進行簡化以后,就可以把它們歸類到不同的分組里面去: 

  1. export function seperateListToColorZone (simplifiedDataList: number[]) {  
  2.   const zonedList: string[] = []  
  3.   let tempZone: number[] = []  
  4.   simplifiedDataList.forEach((data, index) => {  
  5.     if ((index + 1) % 4 !== 0) {  
  6.       tempZone.push(data)  
  7.     } else { 
  8.        zonedList.push(JSON.stringify(tempZone))  
  9.       tempZone = []  
  10.     }  
  11.   })  
  12.   return zonedList  

最后只需要統計每個相同的分組的總數即可: 

  1. export function getFingerprint (zonedList: string[], zoneAmount: number = 16) {  
  2.   const colorSeperateMap: {  
  3.     [key: string]: number  
  4.   } = {}  
  5.   for (let i = 0; i < zoneAmount; i++) {  
  6.     for (let j = 0; j < zoneAmount; j++) {  
  7.       for (let k = 0; k < zoneAmount; k++) {  
  8.         colorSeperateMap[JSON.stringify([i, j, k])] = 0  
  9.       }  
  10.     }  
  11.   }  
  12.   zonedList.forEach(zone => {  
  13.     colorSeperateMap[zone]++  
  14.   })  
  15.   return Object.values(colorSeperateMap)  

內容特征法

”內容特征法“是指把圖片轉化為灰度圖后再轉化為”二值圖“,然后根據像素的取值(黑或白)形成指紋后進行比對的方法。這種算法的核心是找到一個“閾值”去生成二值圖。

對于生成灰度圖,有別于在“平均哈希算法”中提到的取 RGB 均值的辦法,在這里我們使用加權的方式去實現。為什么要這么做呢?這里涉及到顏色學的一些概念。

具體可以參考這篇《Grayscale to RGB Conversion》,下面簡單梳理一下。

采用 RGB 均值的灰度圖是最簡單的一種辦法,但是它忽略了紅、綠、藍三種顏色的波長以及對整體圖像的影響。以下面圖為示例,如果直接取得 RGB 的均值作為灰度,那么處理后的灰度圖整體來說會偏暗,對后續生成二值圖會產生較大的干擾。

那么怎么改善這種情況呢?答案就是為 RGB 三種顏色添加不同的權重。鑒于紅光有著更長的波長,而綠光波長更短且對視覺的刺激相對更小,所以我們要有意地減小紅光的權重而提升綠光的權重。經過統計,比較好的權重配比是 R:G:B = 0.299:0.587:0.114。

于是我們可以得到灰度處理函數: 

  1. enum GrayscaleWeight {  
  2.   R = .299,  
  3.   G = .587,  
  4.   B = .114  
  5.  
  6. function toGray (imgData: ImageData) {  
  7.   const grayData = []  
  8.   const data = imgData.data  
  9.   for (let i = 0; i < data.length; i += 4) {  
  10.     const gray = ~~(data[i] * GrayscaleWeight.R + data[i + 1] * GrayscaleWeight.G + data[i + 2] * GrayscaleWeight.B)  
  11.     data[i] = data[i + 1] = data[i + 2] = gray  
  12.     grayData.push(gray)  
  13.   }  
  14.   return grayData  

上述函數返回一個 grayData 數組,里面每個元素代表一個像素的灰度值(因為 RBG 取值相同,所以只需要一個值即可)。接下來則使用“大津法”(Otsu's method)去計算二值圖的閾值。關于“大津法”,阮大的文章已經說得很詳細,在這里就不展開了。我在這個地方找到了“大津法”的 Java 實現,后來稍作修改,把它改為了 js 版本: 

  1. / OTSU algorithm  
  2. // rewrite from http://www.labbookpages.co.uk/software/imgProc/otsuThreshold.html  
  3. export function OTSUAlgorithm (imgData: ImageData) {  
  4.   const grayData = toGray(imgData)  
  5.   let ptr = 0  
  6.   let histData = Array(256).fill(0)  
  7.   let total = grayData.length  
  8.   while (ptr < total) {  
  9.     let h = 0xFF & grayData[ptr++]  
  10.     histData[h]++  
  11.   }  
  12.   let sum = 0  
  13.   for (let i = 0; i < 256; i++) {  
  14.     sum += i * histData[i]  
  15.   }  
  16.   let wB = 0  
  17.   let wF = 0  
  18.   let sumB = 0  
  19.   let varMax = 0  
  20.   let threshold = 0  
  21.   for (let t = 0; t < 256; t++) {  
  22.     wB += histData[t]  
  23.     if (wB === 0) continue  
  24.     wF = total - wB  
  25.     if (wF === 0) break  
  26.     sumB += t * histData[t]  
  27.     let mB = sumB / wB  
  28.     let mF = (sum - sumB) / wF  
  29.     let varBetween = wB * wF * (mB - mF) ** 2  
  30.     if (varBetween > varMax) {  
  31.       varMax = varBetween  
  32.       tthreshold = t  
  33.     }  
  34.   }  
  35.   return threshold  

OTSUAlgorithm() 函數接收一個 ImageData 對象,經過上一步的 toGray() 方法獲取到灰度值列表以后,根據“大津法”算出最佳閾值然后返回。接下來使用這個閾值對原圖進行處理,即可獲取二值圖。 

  1. export function binaryzation (imgData: ImageData, threshold: number) {  
  2.   const canvas = document.createElement('canvas')  
  3.   const ctx = canvas.getContext('2d') 
  4.    const imgWidth = Math.sqrt(imgData.data.length / 4)  
  5.   const newImageData = ctx?.createImageData(imgWidth, imgWidth) as ImageData  
  6.   for (let i = 0; i < imgData.data.length; i += 4) {  
  7.     let R = imgData.data[i]  
  8.     let G = imgData.data[i + 1]  
  9.     let B = imgData.data[i + 2]  
  10.     let Alpha = imgData.data[i + 3]  
  11.     let sum = (R + G + B) / 3  
  12.     newImageData.data[i] = sum > threshold ? 255 : 0  
  13.     newImageData.data[i + 1] = sum > threshold ? 255 : 0  
  14.     newImageData.data[i + 2] = sum > threshold ? 255 : 0  
  15.     newImageData.data[i + 3] = Alpha  
  16.   }  
  17.   return newImageData  

若圖片大小為 N×N,根據二值圖“非黑即白”的特性,我們便可以得到一個 N×N 的 0-1 矩陣,也就是指紋:

特征比對算法

經過不同的方式取得不同類型的圖片指紋(特征)以后,應該怎么去比對呢?這里將介紹三種比對算法,然后分析這幾種算法都適用于哪些情況。

漢明距離

摘一段維基百科關于“漢明距離”的描述:

在信息論中,兩個等長字符串之間的漢明距離(英語:Hamming distance)是兩個字符串對應位置的不同字符的個數。換句話說,它就是將一個字符串變換成另外一個字符串所需要替換的字符個數。

例如:

  • 1011101與1001001之間的漢明距離是2。
  • 2143896與2233796之間的漢明距離是3。
  • "toned"與"roses"之間的漢明距離是3。

明白了含義以后,我們可以寫出計算漢明距離的方法: 

  1. export function hammingDistance (str1: string, str2: string) {  
  2.   let distance = 0  
  3.   const str1str1Arr = str1.split('')  
  4.   const str2str2Arr = str2.split('')  
  5.   str1Arr.forEach((letter, index) => {  
  6.     if (letter !== str2Arr[index]) {  
  7.       distance++  
  8.     }  
  9.   })  
  10.   return distance  

使用這個 hammingDistance() 方法,來驗證下維基百科上的例子:

驗證結果符合預期。

知道了漢明距離,也就可以知道兩個等長字符串之間的相似度了(漢明距離越小,相似度越大):

相似度 = (字符串長度 - 漢明距離) / 字符串長度

余弦相似度

從維基百科中我們可以了解到關于余弦相似度的定義:

余弦相似性通過測量兩個向量的夾角的余弦值來度量它們之間的相似性。0度角的余弦值是1,而其他任何角度的余弦值都不大于1;并且其最小值是-1。從而兩個向量之間的角度的余弦值確定兩個向量是否大致指向相同的方向。兩個向量有相同的指向時,余弦相似度的值為1;兩個向量夾角為90°時,余弦相似度的值為0;兩個向量指向完全相反的方向時,余弦相似度的值為-1。這結果是與向量的長度無關的,僅僅與向量的指向方向相關。余弦相似度通常用于正空間,因此給出的值為0到1之間。

注意這上下界對任何維度的向量空間中都適用,而且余弦相似性最常用于高維正空間。

余弦相似度可以計算出兩個向量之間的夾角,從而很直觀地表示兩個向量在方向上是否相似,這對于計算兩個 N×N 的 0-1 矩陣的相似度來說非常有用。根據余弦相似度的公式,我們可以把它的 js 實現寫出來: 

  1. export function 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.   const length = sampleFingerprint.length  
  4.   let innerProduct = 0  
  5.   for (let i = 0; i < length; i++) {  
  6.     innerProduct += sampleFingerprint[i] * targetFingerprint[i]  
  7.   }  
  8.   let vecA = 0  
  9.   let vecB = 0  
  10.   for (let i = 0; i < length; i++) {  
  11.     vecA += sampleFingerprint[i] ** 2  
  12.     vecB += targetFingerprint[i] ** 2  
  13.   }  
  14.   const outerProduct = Math.sqrt(vecA) * Math.sqrt(vecB)  
  15.   return innerProduct / outerProduct  

種比對算法的適用場景

明白了“漢明距離”和“余弦相似度”這兩種特征比對算法以后,我們就要去看看它們分別適用于哪些特征提取算法的場景。

首先來看“顏色分布法”。在“顏色分布法”里面,我們把一張圖的顏色進行區間劃分,通過統計不同顏色區間的數量來獲取特征,那么這里的特征值就和“數量”有關,也就是非 0-1 矩陣。

顯然,要比較兩個“顏色分布法”特征的相似度,“漢明距離”是不適用的,只能通過“余弦相似度”來進行計算。

接下來看“平均哈希算法”和“內容特征法”。從結果來說,這兩種特征提取算法都能獲得一個 N×N 的 0-1 矩陣,且矩陣內元素的值和“數量”無關,只有 0-1 之分。所以它們同時適用于通過“漢明距離”和“余弦相似度”來計算相似度。

計算精度

明白了如何提取圖片的特征以及如何進行比對以后,最重要的就是要了解它們對于相似度的計算精度。

本文所講的相似度僅僅是通過客觀的算法來實現,而判斷兩張圖片“像不像”卻是一個很主觀的問題。于是我寫了一個簡單的服務,可以自行把兩張圖按照不同的算法和精度去計算相似度:

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

經過對不同素材的多方比對,我得出了下列幾個非常主觀的結論。

  •  對于兩張顏色較為豐富,細節較多的圖片來說,“顏色分布法”的計算結果是最符合直覺的。  

  •  對于兩張內容相近但顏色差異較大的圖片來說,“內容特征法”和“平均/感知哈希算法”都能得到符合直覺的結果。 

  •  針對“顏色分布法“,區間的劃分數量對計算結果影響較大,選擇合適的區間很重要。   

總結一下,三種特征提取算法和兩種特征比對算法各有優劣,在實際應用中應該針對不同的情況靈活選用。

總結

本文是在拜讀阮一峰的兩篇《相似圖片搜索的原理》之后,經過自己的實踐總結以后而成。由于對色彩、數學等領域的了解只停留在淺顯的層面,文章難免有謬誤之處,如果有發現表述得不正確的地方,歡迎留言指出,我會及時予以更正。

 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2025-08-04 09:42:42

2022-02-18 08:26:12

TopK數組面試題

2017-02-09 16:16:24

Java負載均衡算法

2015-10-15 10:27:12

文本相似度判定

2024-09-23 14:36:20

2025-01-14 13:51:44

2020-05-07 09:45:16

前端JS圖片壓縮

2022-02-15 13:55:08

圖片濾鏡glfx.jslena.js

2020-06-15 18:00:36

transformbannerJavascript

2022-08-05 19:27:22

通用API鴻蒙

2023-11-21 16:06:04

計算機視覺人工智能

2018-05-28 15:33:09

無監督學習算法Python

2021-03-08 15:39:58

人工智能科技數據

2010-07-22 12:19:07

2010-03-09 16:26:08

Python列表

2023-10-10 15:33:55

機器學習相似性度量

2021-12-03 07:27:30

全景瀏覽Three.js

2013-08-28 13:44:42

數據算法

2013-08-29 14:28:58

海量數據simhash

2010-08-16 16:39:48

DIV內容居中
點贊
收藏

51CTO技術棧公眾號

男人天堂中文字幕| 欧美大片免费播放器| 黄色网址在线免费播放| 国产一区二区在线电影| 欧美麻豆久久久久久中文 | 国产毛片精品一区| 久久久影视精品| 人妻一区二区视频| 日本99精品| 色呦呦国产精品| 亚洲一区不卡在线| 91无套直看片红桃| 激情久久婷婷| 在线观看日韩av| 成人在线观看一区二区| 成人开心激情| 亚洲一区在线观看免费 | 国内外免费激情视频| 在线免费观看黄色网址| 成人黄色网址在线观看| 国产精品三级在线| 奇米影视第四色777| 久久免费av| 日韩精品免费在线观看| 三级黄色片播放| 久九九久频精品短视频| 亚洲日本韩国一区| 日本成人三级电影网站| 性欧美18一19性猛交| 日本v片在线高清不卡在线观看| 久久成人精品视频| 丰满的亚洲女人毛茸茸| 欧美一级二级三级视频| 日韩一区二区三区视频在线观看| 三级4级全黄60分钟| 激情av在线| 亚洲人成人一区二区在线观看| 日本日本精品二区免费| 天堂在线观看免费视频| 国产激情视频一区二区在线观看| 国产精品爽爽ⅴa在线观看| 欧美日韩一二三四区| 国内综合精品午夜久久资源| 久久精品国产一区| www成人啪啪18软件| 国产精品中文字幕亚洲欧美| 日韩经典中文字幕在线观看| fc2成人免费视频| 午夜久久av| 欧美一区二区视频在线观看2020 | 精彩视频一区二区三区| 国产精品2018| 国产免费一区二区三区四区五区| 日韩午夜免费| 久久久久免费视频| 加勒比av在线播放| 午夜亚洲福利| 欧美日韩aaaa| 国产在线视频第一页| 欧美另类女人| 欧美极品第一页| 真实国产乱子伦对白在线| 偷偷www综合久久久久久久| 日韩在线视频网站| a级在线免费观看| 精品盗摄女厕tp美女嘘嘘| 亚洲人成免费电影| xxxx日本黄色| 日韩大片在线播放| 久久影院在线观看| 黄页网站免费观看| 亚洲毛片一区| 日本久久久久久| 成人免费一级片| 日本亚洲免费观看| 成人免费视频网址| 亚洲第一天堂影院| 91在线码无精品| 日韩电影大全在线观看| 亚洲xxxxxx| 亚洲欧洲中文日韩久久av乱码| 麻豆映画在线观看| 91在线三级| 亚洲成av人片观看| 少妇激情一区二区三区| 天堂久久一区| 欧美r级在线观看| 国产偷人妻精品一区| 欧美精品一区二区久久| 中文字幕精品在线| 九九热最新地址| 日韩五码在线| 国产日韩中文字幕| 欧性猛交ⅹxxx乱大交| 久久久亚洲综合| 色哺乳xxxxhd奶水米仓惠香| 国产福利片在线观看| 欧美三级日韩三级国产三级| japan高清日本乱xxxxx| 先锋影音国产精品| 久久精品久久精品亚洲人| 国产污视频在线看| 日韩激情av在线| 91免费看蜜桃| 成人性生交大片免费看午夜| 亚洲精品亚洲人成人网| 大肉大捧一进一出好爽动态图| 男人天堂久久| 日韩精品在线第一页| 伊人在线视频观看| 老鸭窝毛片一区二区三区| 91牛牛免费视频| 九九热视频在线观看| 夜夜精品视频一区二区| 亚洲综合婷婷久久| 亚欧日韩另类中文欧美| 美女福利视频一区| 亚洲精品国产精品乱码视色| 成人免费视频视频| 波多野结衣三级在线| 国产日韩电影| 亚洲第一福利在线观看| 在线观看黄网址| 久久午夜激情| 国产麻豆乱码精品一区二区三区| 免费a级人成a大片在线观看| 一本大道久久a久久精二百| 伊人av在线播放| 欧美电影《睫毛膏》| 青青青国产精品一区二区| 亚洲国产精品二区| 亚洲丝袜美腿综合| 91最新在线观看| 妖精一区二区三区精品视频 | 久久综合色一本| 青草在线视频| 欧美一区二区三区小说| 亚洲aaa视频| 人人狠狠综合久久亚洲| 欧美中日韩免费视频| 漫画在线观看av| 精品国产免费视频| 久久久久性色av无码一区二区| 精品一区二区三区欧美| 亚洲va韩国va欧美va精四季| 丝袜美腿一区| 亚洲精品一二区| 欧美一区二区激情视频| 成人动漫在线一区| av免费看网址| 狼人精品一区二区三区在线| 久久青草精品视频免费观看| 免费a视频在线观看| 一区二区成人在线观看| 久久久精品人妻一区二区三区| 一区二区三区在线电影| 2022国产精品| 亚洲无线看天堂av| 精品剧情在线观看| 国产无套粉嫩白浆内谢| av成人动漫在线观看| 国产人妻777人伦精品hd| 国产香蕉精品| 2019最新中文字幕| 国产中文字幕在线| 欧美网站一区二区| 91av手机在线| 国产成人免费在线视频| 久久亚洲a v| 国产一区二区三区亚洲| 日本sm极度另类视频| 国产在线91| 91麻豆精品国产91久久久久久久久 | 日本学生初尝黑人巨免费视频| 成人永久aaa| 逼特逼视频在线| 欧美一级淫片| 亚洲一区二区少妇| 成人一级福利| 这里只有精品在线播放| 国产91视频在线| 午夜国产不卡在线观看视频| 91精品人妻一区二区三区蜜桃欧美| 青青草97国产精品免费观看无弹窗版| 亚洲一区二区四区| 91综合久久爱com| 欧美专区在线播放| 日韩欧美小视频| 精品久久一区二区| 樱花视频在线免费观看| 亚洲欧洲中文日韩久久av乱码| 中文字幕三级电影| 日本人妖一区二区| 国产乱子伦精品视频| 西野翔中文久久精品字幕| 国产精品综合不卡av| 欧美1234区| 在线色欧美三级视频| 国产福利免费视频| 色噜噜偷拍精品综合在线| 亚洲人做受高潮| 成人av手机在线观看| 一级黄色香蕉视频| 国自产拍偷拍福利精品免费一| 欧美日韩一区二| 日本一区二区三区播放| 国产91在线播放精品91| 日韩电影免费观看| 中文日韩电影网站| 天天综合永久入口| 91精品国产综合久久福利| 久久夜靖品2区| 亚洲色图视频网| 久久精品—区二区三区舞蹈| 成人网页在线观看| 最新国产黄色网址| 久久婷婷影院| 国产精品无码一区二区在线| 亚洲在线久久| 相泽南亚洲一区二区在线播放| 伦理一区二区三区| 99国产视频| www.久久爱.com| 国产成人精品综合| 欧美日韩在线观看首页| 欧美乱大交xxxxx| 在线观看av的网站| 亚洲天堂精品在线| 亚洲日本中文字幕在线| 精品免费视频.| 99热在线只有精品| 欧美情侣在线播放| 一区二区视频播放| 91国内精品野花午夜精品| 一级片中文字幕| 午夜视频一区二区| 久久久久久久久久久久久久免费看| 中文字幕在线不卡| 一二三四国产精品| 国产欧美一区二区三区在线老狼 | 国产露脸91国语对白| 在线视频综合导航| 欧美一区二区三区不卡视频| 精品日韩视频在线观看| 国产午夜福利一区二区| 伊人婷婷欧美激情| 久久久久久久黄色| 一区二区高清在线| 久久精品国产亚洲AV无码麻豆| 亚洲视频免费观看| 男人操女人的视频网站| 亚洲视频精选在线| 欧美丰满熟妇bbbbbb| 亚洲人成网站影音先锋播放| 无码黑人精品一区二区| 亚洲免费电影在线| 久久国产精品波多野结衣| 亚洲综合视频在线观看| 国产网站在线看| 欧美午夜www高清视频| 日本高清不卡码| 欧洲国产伦久久久久久久| 中文天堂在线播放| 欧美人妖巨大在线| 国内毛片毛片毛片毛片| 精品国产一区二区三区久久影院| 囯产精品久久久久久| 亚洲黄色av女优在线观看| 清纯唯美亚洲色图| 一区二区三区久久精品| 美女隐私在线观看| 欧美高清电影在线看| 黄色在线网站噜噜噜| 日本成熟性欧美| 黄色成人在线观看网站| 91视频免费网站| 久久porn| 日韩久久精品一区二区三区| 久久精品免费一区二区三区| 91亚洲精品国产| 美女诱惑一区| 中文字幕22页| 成人黄色在线看| 精品熟妇无码av免费久久| 亚洲精品成人精品456| 国产成人无码精品| 欧美性色aⅴ视频一区日韩精品| 国产精品人人爽| 亚洲成色999久久网站| 国产福利在线视频| 欧美另类xxx| 日韩成人亚洲| 999视频在线免费观看| 亚洲伊人春色| 青青在线免费视频| 老鸭窝亚洲一区二区三区| 日韩av片专区| 北岛玲一区二区三区四区| 青青草自拍偷拍| 亚洲一区二区五区| 中文字幕人成人乱码亚洲电影| 日韩欧美三级在线| av在线中文| 久久全球大尺度高清视频| 少妇高潮一区二区三区99| 精品蜜桃传媒| 一区二区三区四区电影| 免费观看成人在线视频| 国产一区二区三区综合| 蜜桃无码一区二区三区| 亚洲综合色在线| 波多野结衣一区二区三区四区| 精品嫩草影院久久| 久久bbxx| 国产精品视频xxxx| 首页亚洲中字| 国产精品无码电影在线观看| 蜜臀精品一区二区三区在线观看 | 欧美贵妇videos办公室| 欧美网站免费| 品久久久久久久久久96高清| 亚洲视频观看| 日本77777| 中文字幕不卡在线| 日韩久久中文字幕| 亚洲第一区在线| 色黄网站在线观看| 91欧美精品午夜性色福利在线| 精品久久精品| 北条麻妃在线视频| 久久一夜天堂av一区二区三区 | 色综合夜色一区| 欧美一级特黄aaaaaa| 欧美激情免费看| 国产精品xnxxcom| 伊人久久av导航| 蜜臀av亚洲一区中文字幕| 91网站免费视频| 欧美日韩亚洲视频一区| 欧美少妇bbw| 欧美激情欧美激情在线五月| 91麻豆精品一二三区在线| 五月天亚洲综合情| 日韩黄色在线观看| 少妇一级黄色片| 91国产成人在线| 成人精品一区二区| 国产va免费精品高清在线| 伊人久久大香线蕉| 成熟老妇女视频| 国产肉丝袜一区二区| 中文字幕在线一| 久久精品国产亚洲精品| 日本一区精品视频| 你真棒插曲来救救我在线观看| 丁香亚洲综合激情啪啪综合| 精品在线视频免费观看| 亚洲国产日韩欧美在线图片| 高清精品在线| 欧美日韩免费精品| 人人精品人人爱| 亚洲精品电影院| 91精品国产日韩91久久久久久| 怡红院在线播放| 国产乱码精品一区二区三区日韩精品 | 夜夜爽www精品| 国产一区二区视频在线播放| 麻豆一区产品精品蜜桃的特点| 精品久久99ma| 天堂中文av在线资源库| 天天综合色天天综合色hd| 美国毛片一区二区三区| 少妇aaaaa| 日韩av在线免播放器| 亚洲高清黄色| 在线综合视频网站| 国产ts人妖一区二区| 欧美啪啪小视频| 日韩中文字幕久久| 伊色综合久久之综合久久| 男女超爽视频免费播放| 国产清纯白嫩初高生在线观看91| 91精品视频免费在线观看| 欧美激情2020午夜免费观看| 日韩黄色网络| 久久久久xxxx| 偷拍日韩校园综合在线| 国产大学生校花援交在线播放| 91麻豆国产精品| 亚洲在线免费| 希岛爱理中文字幕| 日韩的一区二区| 日韩福利影视| av之家在线观看| 国产精品久久久久久久蜜臀 | 国产精品88av| 精品久久久久久久久久久久久久久久 | 色呦呦日韩精品| 永久免费网站在线| 欧美在线一二三区|