设为首页 加入收藏

TOP

如何对前端图片主题色进行提取?这篇文章详细告诉你(一)
2019-01-11 16:08:47 】 浏览:376
Tags:如何 前端 图片 主题 进行 提取 篇文章 详细 告诉

本文由云+社区发表

图片主题色在图片所占比例较大的页面中,能够配合图片起到很好视觉效果,给人一种和谐、一致的感觉。同时也可用在图像分类,搜索识别等方面。通常主题色的提取都是在后端完成的,前端将需要处理的图片以链接或id的形式提供给后端,后端通过运行相应的算法来提取出主题色后,再返回相应的结果。

这样可以满足大多数展示类的场景,但对于需要根据用户“定制”、“生成”的图片,这样的方式就有了一个上传图片---->后端计算---->返回结果的时间,等待时间也许就比较长了。由此,我尝试着利用 canvas在前端进行图片主题色的提取。

一、主题色算法

目前比较常用的主题色提取算法有:最小差值法、中位切分法、八叉树算法、聚类、色彩建模法等。其中聚类和色彩建模法需要对提取函数和样本、特征变量等进行调参和回归计算,用到 python的数值计算库 numpy和机器学习库 scikit-learn,用 python来实现相对比较简单,而目前这两种都没有成熟的js库,并且js本身也不擅长回归计算这种比较复杂的计算。我也就没有深入的研究,而主要将目光放在了前面的几个颜色量化算法上。

而最小差值法是在给定给定调色板的情况下找到与色差最小的颜色,使用的场景比较小,所以我主要看了中位切分法和八叉树算法,并进行了实践。

中位切分法

中位切分法通常是在图像处理中降低图像位元深度的算法,可用来将高位的图转换位低位的图,如将24bit的图转换为8bit的图。我们也可以用来提取图片的主题色,其原理是是将图像每个像素颜色看作是以R、G、B为坐标轴的一个三维空间中的点,由于三个颜色的取值范围为0~255,所以图像中的颜色都分布在这个颜色立方体内,如下图所示。

img

之后将RGB中最长的一边从颜色统计的中位数一切为二,使得到的两个长方体所包含的像素数量相同,如下图所示

img

重复这个过程直到切出长方体数量等于主题色数量为止,最后取每个长方体的中点即可。

img

在实际使用中如果只是按照中点进行切割,会出现有些长方体的体积很大但是像素数量很少的情况。解决的办法是在切割前对长方体进行优先级排序,排序的系数为体积 * 像素数。这样就可以基本解决此类问题了。

八叉树算法

八叉树算法也是在颜色量化中比较常见的,主要思路是将R、G、B通道的数值做二进制转换后逐行放下,可得到八列数字。如 #FF7880转换后为

R: 1111 1111
G: 0111 1000
B: 0000 0000

再将RGB通道逐列粘合,可以得到8个数字,即为该颜色在八叉树中的位置,如图。

img

在将所有颜色插入之后,再进行合并运算,直到得到所需要的颜色数量为止。

在实际操作中,由于需要对图像像素进行遍历后插入八叉树中,并且插入过程有较多的递归操作,所以比中位切分法要消耗更长的时间。

二、中位切分法实践

根据之前的介绍和网上的相关资料,此处贴上我自己理解实现的中位切分法代码,并且找了几张图片将结果与QQ音乐已有的魔法色相关算法进行比较,图一为中位切分法结果,图二为后台cgi返回结果

图一

img

图二

img

img

可以看到有一定的差异,但是差值相对都还比较小的,处理速度在pc上面还是比较快的,三张图分别在70ms,100ms,130ms左右。这里贴上代码,待后续批量处理进行对比之后再分析。

(function () {

    /**
     * 颜色盒子类
     *
     * @param {Array} colorRange    [[rMin, rMax],[gMin, gMax], [bMin, bMax]] 颜色范围
     * @param {any} total   像素总数, imageData / 4
     * @param {any} data    像素数据集合
     */
    function ColorBox(colorRange, total, data) {
        this.colorRange = colorRange;
        this.total = total;
        this.data = data;
        this.volume = (colorRange[0][1] - colorRange[0][0]) * (colorRange[1][1] - colorRange[1][0]) * (colorRange[2][1] - colorRange[2][0]);
        this.rank = this.total * (this.volume);
    }

    ColorBox.prototype.getColor = function () {
        var total = this.total;
        var data = this.data;

        var redCount = 0,
            greenCount = 0,
            blueCount = 0;

        for (var i = 0; i < total; i++) {
            redCount += data[i * 4];
            greenCount += data[i * 4 + 1];
            blueCount += data[i * 4 + 2];
        }

        return [parseInt(redCount / total), parseInt(greenCount / total), parseInt(blueCount / total)];
    }

    // 获取切割边
    function getCutSide(colorRange) {   // r:0,g:1,b:2
        var arr = [];
        for (var i = 0; i < 3; i++) {
            arr.push(colorRange[i][1] - colorRange[i][0]);
        }
        return arr.indexOf(Math.max(arr[0], arr[1], arr[2]));
    }

    // 切割颜色范围
    function cutRange(colorRange, colorSide, cutValue) {
        var arr1 = [];
        var arr2 = [];
        colorRange.forEach(function (item) {
            arr1.push(item.slice());
            arr2.push(item.slice());
        })
        arr1[colorSide][1] = cutValue;
        arr2[colorSide][0] = cutValue;
        return [arr1, arr2];
    }

    // 找到出现次数为中位数的颜色
    function getMedianColor(colorCountMap, total) {
        var arr = [];
        for (var key in colorCountMap) {
            arr.push({
                color: parseInt(key),
                count: colorCountMap[key]
            })
        }

        var sortArr = __quickSort(arr);
        var medianCount = 0;
        var medianColor = 0;
        var medianIndex = Math.floor(sortArr.length / 2)

        for (var i = 0; i <= medianIndex; i++) {
            medianCount += sortArr[i].count;
        }

        return {
            color: parseInt(sortArr[medianIndex].color),
            count: medianCount
        }

        // 另一种切割颜色判断方法,根据数量和差值的乘积进行判断,自己试验后发现效果不如中位数方法,但是少了排序,性能应该有所
首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Python常用的内置函数 下一篇Python 内置函数笔记

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目