8

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

 3 years ago
source link: https://www.zhangxinxu.com/wordpress/2021/06/js-image-colorful-or-pure/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=9982
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

图像相似度占位

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

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

好,开始。

一、颜色相似度判断

无论是整体颜色算法,还是基于主色判断,都离不开颜色相似度的判断,这个判断有时候也被称为颜色差异判断(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

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

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

相似度判断截图

看起来还是有点味道的。

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

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

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

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

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

欢迎转发,欢迎分享。

2764.svg

(本篇完)1f44d.svg 是不是学到了很多?可以分享到微信
1f44a.svg 有话要说?点击这里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK