最近看了阮一峰老师的相似图片搜索的原理(二),其中介绍了通过内容特征法来对比两个图片的相似性。
大致步骤:
- 把图片都缩放到50x50大小
- 转成灰度图片
- 利用"大津法"(Otsu's method)确定阈值
- 通过阈值再对图片进行二值化
- 对比两个图片对应位置像素,得出结果
接下来,看看用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'
}
图片灰度化,灰度就是图片每个像素的r
、g
、b
设置相同的值,计算每个像素的值的方法有很多,这里使用加权算法:
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。