用JavaScript來計算兩個圖像的類似度

最近看了阮一峯老師的類似圖片搜索的原理(二),其中介紹了經過內容特徵法來對比兩個圖片的類似性。html

大體步驟:算法

  1. 把圖片都縮放到50x50大小
  2. 轉成灰度圖片
  3. 利用"大津法"(Otsu's method)肯定閾值
  4. 經過閾值再對圖片進行二值化
  5. 對比兩個圖片對應位置像素,得出結果

接下來,看看用JS怎麼實現上面的步驟,理論部分就很少介紹了,仍是看類似圖片搜索的原理(二)canvas

首先,對圖片數據進行操做固然要使用canvas,因此先建立一個畫布和它的繪圖上下文網站

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

把圖片縮放渲染到畫布,獲得圖片像素數據:this

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設置相同的值,計算每一個像素的值的方法有不少,這裏使用加權算法:code

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改寫:htm

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:blog

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() + '%')
}

不知道是否是哪步出錯了,感受用這個方法計算出來的結果並不理想😅。實例demoget

相關文章
相關標籤/搜索