用JavaScript来计算两个图像的相似度

59次阅读

共计 2237 个字符,预计需要花费 6 分钟才能阅读完成。

最近看了阮一峰老师的相似图片搜索的原理(二),其中介绍了通过内容特征法来对比两个图片的相似性。

大致步骤:

  1. 把图片都缩放到 50×50 大小
  2. 转成灰度图片
  3. 利用 ” 大津法 ”(Otsu’s method)确定阈值
  4. 通过阈值再对图片进行二值化
  5. 对比两个图片对应位置像素,得出结果

接下来,看看用 JS 怎么实现上面的步骤,理论部分就不多介绍了,还是看相似图片搜索的原理(二)

首先,对图片数据进行操作当然要使用canvas,所以先创建一个画布和它的绘图上下文

const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')

把图片缩放渲染到画布,得到图片像素数据:

function toZoom() {
  canvas.width = 50
  canvas.height = 50

  const img = new Image
  img.onload = function () {context.drawImage(this, 0, 0, this.width, this.height, 0, 0, 50, 50)
    const imageData = context.getImageData(0, 0, 50, 50)
  }
  img.src = 'test.jpg'
}

图片灰度化,灰度就是图片每个像素的 rgb 设置相同的值,计算每个像素的值的方法有很多,这里使用加权算法:

function toGray() {const grayData = []
  const data = imageData.data
  canvas.width = 50
  canvas.height = 50

  for (let i = 0; i < data.length; i += 4) {const gray = data[i] * .299 + data[i + 1] * .587 + data[i + 2] * .114 | 0
    data[i] = data[i + 1] = data[i + 2] = gray
    grayData.push(gray)
  }

  context.putImageData(imageData, 0, 0)

  return grayData
}

在对图片二值化,使之变成黑白图片之前,要先确定一个 阈值 ,根据这个阈值对图片二值化,能使图片的轮廓最明显。
文章中提到了通过 “ 大津法 ”(Otsu’s method) 来求得这个阈值,并给了一个实例网站,提供了 Java 版算法,用 JS 改写:

function toOtsu() {
  let ptr = 0
  let histData = Array(256).fill(0) // 记录 0 -256 每个灰度值的数量,初始值为 0
  let total = grayData.length

  while (ptr < total) {let h = 0xFF & grayData[ptr++]
    histData[h]++
  }

  let sum = 0        // 总数(灰度值 x 数量)
  for (let i = 0; i < 256; i++) {sum += i * histData[i]
  }


  let wB = 0         // 背景(小于阈值)的数量
  let wF = 0         // 前景(大于阈值)的数量
  let sumB = 0       // 背景图像(灰度 x 数量)总和
  let varMax = 0     // 存储最大类间方差值
  let threshold = 0  // 阈值

  for (let t = 0; t < 256; t++) {wB += histData[t]       // 背景(小于阈值)的数量累加
    if (wB === 0) continue
    wF = total - wB         // 前景(大于阈值)的数量累加
    if (wF === 0) break

    sumB += t * histData[t] // 背景(灰度 x 数量)累加

    let mB = sumB / wB          // 背景(小于阈值)的平均灰度
    let mF = (sum - sumB) / wF  // 前景(大于阈值)的平均灰度

    let varBetween = wB * wF * (mB - mF) ** 2  // 类间方差

    if (varBetween > varMax) {
      varMax = varBetween
      threshold = t
    }
  }

  return threshold
}

根据上面求得的 阈值 进行 二值化,小于阈值的灰度值为 0,大于阈值的灰度值为 255:

function toBinary() {const threshold = toOtsu(grayData, index)
  const imageData = context.createImageData(50, 50)
  const data = imageData.data
  const temp = []

  grayData.forEach((v, i) => {
    let gray = v > threshold ? 255 : 0
    data[i * 4] = data[i * 4 + 1] = data[i * 4 + 2] = gray
    data[i * 4 + 3] = 255
    temp.push(gray > 0 ? 0 : 1)
  })

  canvas.width = 50
  canvas.height = 50
  context.putImageData(imageData, 0, 0)
}

最后计算两个图像每个像素的值相同的占总数的百分比。

function toCompare() {
  let sameCount = 0
  // img1_data
  // img2_data

  const total = img1_data.length
  for (let i = 0; i < total; i++) {sameCount += img1_data[i] === img2_data[i]
  }

  console.log((sameCount / total * 100).toLocaleString() + '%')
}

不知道是不是哪步出错了,感觉用这个方法计算出来的结果并不理想????。实例 demo

正文完
 0