为了便于理解,每种算法都会经过“特征提取”和“特征比对”两个步骤进行。接下来将着重对每种算法的“特征提取”步骤进行详细解读,而“特征比对”则单独进行阐述。

 使用JS完成多种图片类似度算法(图片相似性算法) JS 图片 前端 第1张

在查找范畴,早已出现了“查找类似图片/类似产品”的相关功用,如 Google 搜图,百度搜图,淘宝的摄影搜产品等。要完成类似的核算图片类似度的功用,除了运用听起来巨大上的“人工智能”以外,其实通过 JS 和几种简略的算法,也能八九不离十地完成类似的作用。

在阅览本文之前,强烈建议先阅览完阮一峰于多年所编撰的《类似图片查找的原理》相关文章,本文所触及的算法也来源于其间。

体会地址:https://img-compare.netlify.com/

特征提取算法

为了便于了解,每种算法都会通过“特征提取”和“特征比对”两个过程进行。接下来将侧重对每种算法的“特征提取”过程进行具体解读,而“特征比对”则独自进行论述。

均匀哈希算法

参阅阮大的文章,“均匀哈希算法”主要由以下几步组成:

第一步,缩小尺度为8×8,以去除图片的细节,只保存结构、明暗等基本信息,摒弃不同尺度、份额带来的图片差异。

第二步,简化色彩。将缩小后的图片转为灰度图画。

第三步,核算均匀值。核算一切像素的灰度均匀值。

第四步,比较像素的灰度。将64个像素的灰度,与均匀值进行比较。大于或等于均匀值,记为1;小于均匀值,记为0。

第五步,核算哈希值。将上一步的比较成果,组合在一起,就构成了一个64位的整数,这便是这张图片的指纹。

第六步,核算哈希值的差异,得出类似度(汉明间隔或许余弦值)。

理解了“均匀哈希算法”的原理及过程今后,就可以开端编码工作了。为了让代码可读性更高,本文的一切比如我都将运用 typescript 来完成。

图片紧缩:

咱们选用 canvas 的 drawImage() 办法完成图片紧缩,后运用 getImageData() 办法获取 ImageData 方针。

  1. exportfunctioncompressImg(imgSrc:string,imgWidth:number=8):Promise<ImageData>{
  2. returnnewPromise((resolve,reject)=>{
  3. if(!imgSrc){
  4. reject('imgSrccannotbeempty!')
  5. }
  6. constcanvas=document.createElement('canvas')
  7. constctx=canvas.getContext('2d')
  8. constimg=newImage()
  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. constdata=ctx?.getImageData(0,0,imgWidth,imgWidth)asImageData
  15. resolve(data)
  16. }
  17. img.src=imgSrc
  18. })
  19. }

可能有读者会问,为什么运用 canvas 可以完成图片紧缩呢?简略来说,为了把“大图片”制作到“小画布”上,一些相邻且色彩附近的像素往往会被删减掉,然后有用减少了图片的信息量,因而可以完成紧缩的作用:

 使用JS完成多种图片类似度算法(图片相似性算法) JS 图片 前端 第2张

在上面的 compressImg() 函数中,咱们使用 new Image() 加载图片,然后设定一个预设的图片宽高值让图片紧缩到指定的巨细,最终获取到紧缩后的图片的 ImageData 数据——这也意味着咱们能获取到图片的每一个像素的信息。

关于 ImageData,可以参阅 MDN 的文档介绍。

图片灰度化

为了把五颜六色的图片转化成灰度图,咱们首要要理解“灰度图”的概念。在维基百科里是这么描绘灰度图画的:

在核算机范畴中,灰度(Gray scale)数字图画是每个像素只需一个采样色彩的图画。

大部分情况下,任何的色彩都可以通过三种色彩通道(R, G, B)的亮度以及一个色彩空间(A)来组成,而一个像素只显示一种色彩,因而可以得到“像素 => RGBA”的对应联系。而“每个像素只需一个采样色彩”,则意味着组成这个像素的三原色通道亮度持平,因而只需要算出 RGB 的均匀值即可:

  1. //依据RGBA数组生成ImageData
  2. exportfunctioncreateImgData(dataDetail:number[]){
  3. constcanvas=document.createElement('canvas')
  4. constctx=canvas.getContext('2d')
  5. constimgWidth=Math.sqrt(dataDetail.length/4)
  6. constnewImageData=ctx?.createImageData(imgWidth,imgWidth)asImageData
  7. for(leti=0;i<dataDetail.length;i+=4){
  8. letR=dataDetail[i]
  9. letG=dataDetail[i+1]
  10. letB=dataDetail[i+2]
  11. letAlpha=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. returnnewImageData
  18. }
  19. exportfunctioncreateGrayscale(imgData:ImageData){
  20. constnewData:number[]=Array(imgData.data.length)
  21. newData.fill(0)
  22. imgData.data.forEach((_data,index)=>{
  23. if((index+1)%4===0){
  24. constR=imgData.data[index-3]
  25. constG=imgData.data[index-2]
  26. constB=imgData.data[index-1]
  27. constgray=~~((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. returncreateImgData(newData)
  35. }

ImageData.data 是一个 Uint8ClampedArray 数组,可以了解为“RGBA数组”,数组中的每个数字取值为0~255,每4个数字为一组,表明一个像素的 RGBA 值。因为ImageData 为只读方针,所以要别的写一个 creaetImageData() 办法,使用 context.createImageData() 来创立新的 ImageData 方针。

拿到灰度图画今后,就可以进行指纹提取的操作了。

指纹提取

在“均匀哈希算法”中,若灰度图的某个像素的灰度值大于均匀值,则视为1,否则为0。把这部分信息组合起来便是图片的指纹。因为咱们现已拿到了灰度图的 ImageData 方针,要提取指纹也就变得很简略了:

  1. exportfunctiongetHashFingerprint(imgData:ImageData){
  2. constgrayList=imgData.data.reduce((pre:number[],cur,index)=>{
  3. if((index+1)%4===0){
  4. pre.push(imgData.data[index-1])
  5. }
  6. returnpre
  7. },[])
  8. constlength=grayList.length
  9. constgrayAverage=grayList.reduce((pre,next)=>(pre+next),0)/length
  10. returngrayList.map(gray=>(gray>=grayAverage?1:0)).join('')
  11. }

 使用JS完成多种图片类似度算法(图片相似性算法) JS 图片 前端 第3张

通过上述一连串的过程,咱们便可以通过“均匀哈希算法”获取到一张图片的指纹信息(示例是巨细为8×8的灰度图):

 使用JS完成多种图片类似度算法(图片相似性算法) JS 图片 前端 第4张

感知哈希算法

关于“感知哈希算法”的具体介绍,可以参阅这篇文章:《依据感知哈希算法的视觉方针盯梢》。

 使用JS完成多种图片类似度算法(图片相似性算法) JS 图片 前端 第5张

简略来说,该算法通过离散余弦改换今后,把图画从像素域转化到了频率域,而携带了有用信息的低频成分会会集在 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. functionmemoizeCosines(N:number,cosMap:any){
  2. cosMapcosMap=cosMap||{}
  3. cosMap[N]=newArray(N*N)
  4. letPI_N=Math.PI/N
  5. for(letk=0;k<N;k++){
  6. for(letn=0;n<N;n++){
  7. cosMap[N][n+(k*N)]=Math.cos(PI_N*(n+0.5)*k)
  8. }
  9. }
  10. returncosMap
  11. }
  12. functiondct(signal:number[],scale:number=2){
  13. letL=signal.length
  14. letcosMap:any=null
  15. if(!cosMap||!cosMap[L]){
  16. cosMap=memoizeCosines(L,cosMap)
  17. }
  18. letcoefficients=signal.map(function(){return0})
  19. returncoefficients.map(function(_,ix){
  20. returnscale*signal.reduce(function(prev,cur,index){
  21. returnprev+(cur*cosMap[L][index+(ix*L)])
  22. },0)
  23. })
  24. }

然后增加两个矩阵处理办法,分别是把通过 DCT 办法生成的一维数组升维成二维数组(矩阵),以及从矩阵中获取其“左上角”内容。

  1. //一维数组升维
  2. functioncreateMatrix(arr:number[]){
  3. constlength=arr.length
  4. constmatrixWidth=Math.sqrt(length)
  5. constmatrix=[]
  6. for(leti=0;i<matrixWidth;i++){
  7. const_temp=arr.slice(i*matrixWidth,i*matrixWidth+matrixWidth)
  8. matrix.push(_temp)
  9. }
  10. returnmatrix
  11. }
  12. //从矩阵中获取其“左上角”巨细为range×range的内容
  13. functiongetMatrixRange(matrix:number[][],range:number=1){
  14. constrangeMatrix=[]
  15. for(leti=0;i<range;i++){
  16. for(letj=0;j<range;j++){
  17. rangeMatrix.push(matrix[i][j])
  18. }
  19. }
  20. returnrangeMatrix
  21. }

复用之前在“均匀哈希算法”中所写的灰度图转化函数createGrayscale(),咱们可以获取“感知哈希算法”的特征值:

  1. exportfunctiongetPHashFingerprint(imgData:ImageData){
  2. constdctdctData=dct(imgData.dataasany)
  3. constdctMatrix=createMatrix(dctData)
  4. constrangeMatrix=getMatrixRange(dctMatrix,dctMatrix.length/8)
  5. constrangeAve=rangeMatrix.reduce((pre,cur)=>pre+cur,0)/rangeMatrix.length
  6. returnrangeMatrix.map(val=>(val>=rangeAve?1:0)).join('')
  7. }

 使用JS完成多种图片类似度算法(图片相似性算法) JS 图片 前端 第6张

色彩散布法

首要摘录一段阮大关于“色彩散布法“的描绘:

 使用JS完成多种图片类似度算法(图片相似性算法) JS 图片 前端 第7张

阮大把256种色彩取值简化成了4种。依据这个原理,咱们在进行色彩散布法的算法设计时,可以把这个区间的区分设置为可修正的,仅有的要求便是区间的数量有必要可以被256整除。算法如下:

  1. //区分色彩区间,默许区间数目为4个
  2. //把256种色彩取值简化为4种
  3. exportfunctionsimplifyColorData(imgData:ImageData,zoneAmount:number=4){
  4. constcolorZoneDataList:number[]=[]
  5. constzoneStep=256/zoneAmount
  6. constzoneBorder=[0]//区间鸿沟
  7. for(leti=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(leti=0;i<zoneBorder.length;i++){
转载请说明出处
知优网 » 使用JS完成多种图片类似度算法(图片相似性算法)

发表评论

您需要后才能发表评论