JS判断图像背景颜色单一还是丰富

这篇文章发布于 2021年06月27日,星期日,17:00,归类于 Canvas相关。 阅读 14364 次, 今日 1 次 13 条评论

 

图像相似度占位

最近接到一个需求,图片上显示文字,如果图片颜色比较单纯,则直接显示文字,如果图片花里花哨的,则文字显示区域高斯模糊,这样方便文字显示。

这里讲下我是怎么实现的,注意,本文介绍的方法一定不是最好的方法,原因见结语。

好,开始。

一、颜色相似度判断

无论是整体颜色算法,还是基于主色判断,都离不开颜色相似度的判断,这个判断有时候也被称为颜色差异判断(Color Difference),常见算法有下面这几个。

1. 纯粹的颜色距离判断

rgb色值距离

就是取R、G、B三个色值的平方差。

2. 加上权重

因为人类对R、G、B不同色值差异的感受程度是不一样的,因此,也有的算法会通过增加或减少权重的方式计算颜色的相似度,一种做法是使用2、4、3权重法,具体算法如下图所示:

色值权重距离计算

3. 更复杂的权重计算

复杂权重计算

我则是使用的第三种权重计算方法。

该方法相关代码如下所示(如果要兼容IE,请自行调整语法,或者babel转下):

const colorDistance = function (arrRGB1, arrRGB2) {
    let [r1, g1, b1] = arrRGB1;
    let [r2, g2, b2] = arrRGB2;

    let rmean = (r1 + r2) / 2;

    let r = r1 - r2;
    let g = g1 - g2;
    let b = b1 - b2;

    return Math.sqrt((2 + rmean / 256) * r * r  + 4 * g * g + (2 + (255 - rmean) / 256) * b * b);
}

好,现在有了颜色相似度算法了,那怎么计算整片区域的色值都是相似的呢。

算法其实很多,由于我之前使用过color-thief,就是取图像主色的JS项目,于是决定基于主色判断整片区域的色值是否是相似的。

二、主色判断法

color-thief的项目地址是:https://github.com/lokesh/color-thief

目前Star数量接近1万了,基本上就是图像取主色最佳实现了。

例如,有下图所示的一张图:

相似度颜色取色

我们取这张图像的前4种颜色,就会有如下图所示的结果:

取的主色示意

此时,我们只需要对这4种颜色进行相似度判断,如果都非常相似,则图像就可以认为颜色单一。

这4个色值分别是:

  • rgb(73,42,50)
  • rgb(104,46,51)
  • rgb(129,49,52)
  • rgb(52,37,48)

相似度计算方法如下,两两计算,然后取平均值(依赖于上面出现的colorDistance()方法)。

// 计算平均颜色距离
let arrLocalDominantColor = [[73,42,50], [104,46,51], [129,49,52], [52,37,48]];
let arrDistance = [];
arrLocalDominantColor.forEach(function (arrRGB) {
    arrLocalDominantColor.forEach(function (arrRGB2) {
        if (arrRGB2 != arrRGB) {
            arrDistance.push(colorDistance(arrRGB, arrRGB2));
        }
    });
});

// 求和
let sum = arrDistance.reduce(function (prev, curv) {
    return prev + curv;
});

console.log('平均相似度' + Math.round(100 * sum / arrDistance.length) / 100);

计算结果为:68.45

相似度平均值

基本上,按照我自己的实践,平均相似度小于100就可以认为是近似纯色了。

至此,似乎问题已经解决了,然而,并没有这么简单。

比方说下面这张素材图:

另外的相似度判断图

从视觉上来看,应该认为是纯色,文字可以直接显示。

但是,最终的取色结果却不是这样的,很多高亮的线条会被认为是一种重要的配色。

另外一张图的主色

4种颜色(rgb(54,56,52)、rgb(166,172,124)、rgb(132,136,98)、rgb(122,126,112))的相似度计算值的平均值高达162.66。

是一个较大的数值。

很显然,此时单纯通过主色判断图像是否接近纯色是不安全的做法,那有没有什么进一步突破的手段呢?

三、巧用浏览器本身算法

有一种方法是4个颜色互相对比相似度,剔除那个和其他3个色值差异最大的色值,使用剩余的3个值进行平均相似度计算。

这种算法可以一定程度上提高整图相似度的判断度,但是还不够准确。

还是这张图:
另外的相似度判断图

其四个主色是:
另外一张图的主色

可以看到,如果使用上面的算法,移除的颜色居然是占据比重最大的深色,这就有些不对了。

所以,我没有使用上面的方法,而是采用了一种更加巧妙的方法,可以增强颜色相似度的判断。

尺寸缩小绘制判别法

这个方法就是想办法缩小原图的尺寸,这样,浏览器会自动进行一定范围内的色彩融合,这样,图像中占比面积比较小的元素的颜色就可能会被冲淡,甚至不可见。

例如,拍了张星空图片,上面有很多星星,如果把这种图片缩小,则星星就会因为像素点不足,直接就看不到了。

例如,上图原图尺寸是 518px * 347px,那我就可以把Canvas画布的尺寸设置为1/13,把图像绘制上去,作为取色图。

var canvasWidth = 518 / 13;
var canvasHeight = 347 / 13;

此时的取色结果是这样子的:

尺寸缩小后的取色

此时4种颜色的相似度计算值仅仅是25.26,就非常符合我们的视觉感受效果了。

说明

尺寸缩小的比例系数建议使用13, 17, 23这样的质数,可以尽可能地触发颜色的补偿计算,结果会更加准确。

四、整理了个JS给大家使用

做人做到底,送佛送到西。

很多人对原理什么的并不特别关心,关心的更多的是直接一个方法一把梭,效果就出来。

OK,为了满足这方面的需求,我特意整了个JS,可以判断图像,或者图像部分区域是不是在视觉上接近纯色。

项目地址是:https://gitee.com/zhangxinxu/image-similarity

欢迎大家关注我的gitee账户。

其中的image-similarity.js就是引用就能有结果的JS了。

JS文件地址示意

使用说明

image-similarity.js的执行依赖于 color-thief.js,本项目中的 color-thief.js 做过一点自定义,使其支持部分区域取色的选择,使用原项目JS可能会没有效果。

使用示意:

<script src="./color-thief.js"></script>
<script src="./image-similarity.js"></script>

全局暴露了2个方法,一个是 imageSimilarityValue() 方法,语法如下:

imageSimilarityValue(src, bounding)

其中: src 是任意格式的图像地址。 bounding 是图像上局部区域的尺寸设置,格式是数组,需要4个数组项,都是数值,分别表示坐标和宽高,例如 [10, 10, 300, 100] 表示判断原始图像左上角坐标是 10,10,宽高是 300×100 的矩形区域的视觉色彩是否丰富。

返回值是个Promise,通常使用示意。

imageSimilarityValue(src, bounding).then(result => {
    // result
});

其中 result 是个对象,格式如下所示:

{
    colors: ['rgb(0,0,0)', ...],
    similarity: 0-255
}

colors 是图像限制在特定尺寸后选取的4个主要颜色,similarity是这些颜色的平均相似度值。

第二个方法是imageSimilarity()方法,语法如下:

imageSimilarity(src, bounding).then(similarity => {
    // similarity是数值
});

这里的 similarity 是整数值,范围从0-5,分别表示相似的程度,值越小则越相似。

这是当前JS项目内置的规则:

// 0 极度相似
// 1 相似
// 2 不太相似
// 3 不相似
// 4 差异较大

imageSimilarity() 方法底层依赖的就是 imageSimilarityValue() 方法,这里的 similarity 其实就是把 imageSimilarityValue() 方法中返回的 similarity 值和 50 相处取了个整。

为什么取50?全是我自己的感觉,所以,这个相似度的阈值选择可能是不准确的,大家可以根据自己实际需求进行调整。

demo示意

做了2个demo演示页面,给大家看看效果。

首先是整体图像判断是不是视觉上接近纯色,您可以狠狠地点击这里:选择图片判断是否接近纯色demo

点击文件选择框,选择任意的图像,就可以看到判断结果了,例如下面一些结果截图:

颜色相似

服饰色彩

image-similarity.js还支持判断图片局部区域的颜色是否视觉相似度比较高。

您可以狠狠地点击这里:图片部分区域颜色是否近似demo

大家可以拖拽图片上的黄色框框,可以看看对应面积内容和最后的对比结果是否接近。

下面几张图是效果示意(点击图片可以切换素材):

相似度判断截图

看起来还是有点味道的。

五、结语

本文一开头有提到“一定不是最好的方法”,原因在于,在一个图像所有像素点信息都已知的情况下,显然,基于算法,对整体的相似度进行计算是最为精准的。

然而,这是理论上的分析结果,执行层面就没这么简单了,我对算法之类的东西关注并不多,这就超出了我的能力范畴了,我觉得应该有人已经做过类似的事情了,如果有谁知道的,可以反馈下,不甚感谢。

一个东西好不好,还要看好不好上手,容不容易懂,如果从这个角度看,本文这种基于主色判断颜色相似度的方法其实也是很不错的方法。

说不定,从实践结果来看,要比纯算法计算的还要精准。

好了,就说这些,感谢大家的阅读。

欢迎转发,欢迎分享。

(本篇完)

分享到:


发表评论(目前13 条评论)

  1. pinkky说道:

    有的图片还是判断不准,不知道什么原因

  2. zousifang说道:

    以前遇到过类似的问题,就是文字加一个1像素的外发光。

  3. 染色球说道:

    一般接到这样的需求,不都是想着怎么解决提出这个需求的人么?大佬你为何还要想下去?

  4. mmm说道:

    可曾试过在HSV颜色空间下做事情?

  5. 0x219说道:

    想给张老师提包的第1501天

  6. memoryza说道:

    赞,文字显示区域高斯模糊 在花里胡哨的图片中效果明显吗?哈哈哈我第一想法是类似打码的虚背景 + 文字 应该会更清晰

  7. holy说道:

    学到了

  8. 雷锋说道:

    明目张胆的发布vpn广告,不会被请喝茶吗?

  9. 醉暖人心说道:

    有点恐怖

  10. dodoto说道:

    关于取主色调,我发现用中位切分法切一刀后,取像素量最大的那块计算平均值就能满足了

  11. yeye说道:

    太厉害了,我要是遇到这种需求,直接说实现不了。。。