首页 > 其他分享 >基于聚类和神经网络的图像颜色提取和评分方案

基于聚类和神经网络的图像颜色提取和评分方案

时间:2023-06-08 19:07:28浏览次数:58  
标签:count category img 评分 colors len 神经网络 let 聚类


概述


图像颜色提取的前端React方案,基于canvas,使用K均值聚类算法提取主要颜色(颜色量子化),用离线训练的神经网络进行评分,附带可视化方案和相关动画。


详细



本文github地址:colorful-color

体验 demo



一、目录结构

基于聚类和神经网络的图像颜色提取和评分方案_聚类和神经网络

本项目是基于React编写的,build目录下有编译好的版本,编译源码前执行以下指令以安装依赖(需要安装好了nodenpm):



npm install






二、组件介绍

components目录下包含6个组件:

1. CanvasBubbleChart.js:一个基于canvas,用于颜色可视化的组件,使用方式:

<CanvasBubbleChart colors={this.state.colorsInfo}></CanvasBubbleChart>

colors 是输入的颜色信息,是一个数组,数组中的对象格式如下:



{ fre: "表示颜色的权重,也就是出现次数", h: "HSL中的色相", s: "HSL中的饱和度" l: "HSL中的明度" }




2. ColorBar.js:显示三个颜色,使用方式如下:

<ColorBar label="main color" ></ColorBar>

组件包含的如下state:



this.state = {
    colorStart: '',
    colorMiddle: '',
    colorEnd: ''
};

3. ColorCard.js:以卡片形式显示一系列颜色,使用方式如下:

<ColorCard colors={}></ColorCard>

colors 属性是数组,包含要显示的颜色:



colors = ["red", "blue", "#333"];





4. ImageInfo.js:显示提取结果的相关信息:

<ImageInfo processInfo={this.state.processInfo}></ImageInfo>

colors 属性是数组,包含要显示的颜色:


processInfo = {
        colors: 0,
        censusTime: 0,
        kmeansIteration:0,
        kmeansTime:0,
        top5Count: 0,
        showQr:false
},



5. ImageShowcase.js:用于显示用户选取的图像,最终的调色盘也会绘制在此组件的canvas中:

<ImageShowcase setScoreLayer={this.setScoreLayer} censusColors={this.censusColors} resetApp={this.resetApp}></ImageShowcase>

setScoreLayer 函数用于唤起评分结果页,censusColors 函数是颜色提取和评分的入口函数,resetApp 函数用于重置整个app,组件包含的状态如下:



this.state = { bgC: "", K: 6, isMounted: false, clusterColors:[], showSave:false };




6. ScoreLayer.js:用于显示用户选取的图像,最终的调色盘也会绘制在此组件的canvas中:

<ScoreLayer loopColors={this.state.loopColors} score={this.state.score} setScoreLayer={this.setScoreLayer}  showScoreLayer = {this.state.showScoreLayer}></ScoreLayer>

loopColors 属性用于唤起评分结果页,score 表示得分,showScoreLayer 用于控制全屏显示组件与否。




三、关键函数


3.1 读取图像

读取图像的逻辑在 ImageShowcase 类中,入口是 readFile 函数,核心在于对尺寸比例的控制,因为要使得移动端显示清晰,所以确定尺寸要考虑 window.devicePixelRatio ,代码如下:



let pixelRatio = this.pixelRatio;
let c_w = canvas.width;
let c_h = canvas.height;
let img_w = img.width > (c_w-_w)/pixelRatio ? (c_w-_w)/pixelRatio : img.width;
let img_h = img.height > (c_h-_h)/pixelRatio ? (c_h-_h)/pixelRatio : img.height;
let scale = (img_w / img.width < img_h / img.height) ? (img_w / img.width) : (img_h / img.height);
img_w = img.width * scale;
img_h = img.height * scale;
canvas.style.width = img_w + _w/pixelRatio + "px";
canvas.style.height = img_h + _h/pixelRatio + "px";
canvas.width = (img_w*pixelRatio + _w);
canvas.height = (img_h*pixelRatio + _h);
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img_w*pixelRatio, img_h*pixelRatio);





3.2 render

Demo的根组件是 ColorCensus.js ,里面包含了一个叫做 ColorCensus 的类,所谓关键函数就是 ColorCensus



render() {
    let mcProps =  this.colorToProps(this.state.mainColor);
    let acProps = this.colorToProps(this.state.averageColor);
    let footWrapClass = this.state.showQr ? 'show-qr' : '';
    footWrapClass += " foot-wrap";
    return (
      <div className="App">
         // 顶部的组件,用于显示图像和提取的主要颜色
        <ImageShowcase setScoreLayer={this.setScoreLayer} censusColors={this.censusColors} resetApp={this.resetApp}></ImageShowcase>
        // 评分结果组件
        <ScoreLayer loopColors={this.state.loopColors} score={this.state.score} setScoreLayer={this.setScoreLayer}  showScoreLayer = {this.state.showScoreLayer}></ScoreLayer>
        // 显示主要颜色与平均颜色
        <ColorBar label="main color" {...mcProps}></ColorBar>
        <ColorBar label="average color" {...acProps}></ColorBar>
        // 显示聚类后的主要颜色
        <ColorCard colors={this.state.clusterColors}></ColorCard>
        // 显示提取的信息
        <ImageInfo processInfo={this.state.processInfo}></ImageInfo>
         // 颜色可视化--color bubble
        <CanvasBubbleChart colors={this.state.colorsInfo}></CanvasBubbleChart>
        <div className={footWrapClass}>
          // 页面底部内容,不重要所以忽略
      </div>
    );
  }




3.3 censusColors

颜色提取与评分的入口函数,首先来看形参:



/**
   * [censusColors description]
   * @param  {Object}  ctx          context of canvas
   * @param  {number}  K            K for K-Means
   * @param  {number}  c_w          width of canvas
   * @param  {number}  c_h          height of canvas
   * @param  {Boolean} isHorizontal direction of image
   * @param  {function}  callBack     callback when census done
   */
  censusColors(ctx, K, c_w, c_h, isHorizontal, callBack) {}


颜色提取的第一步是统计图片的颜色信息,所以需要获取canvas中的图像数据,把颜色转换到 HSL 空间,降采样后编码得到不同颜色的 key ,看代码:



// 从canvas中获取图像数据
let w = c_w;
let h = c_h;
let imageDate;
let pixelRatio = window.devicePixelRatio || 1;
if(isHorizontal){
    imageDate = ctx.getImageData(0, 0, w, h-100*pixelRatio);
}else{
    imageDate = ctx.getImageData(0, 0, w-100*pixelRatio, h);
}

// 遍历图像以统计颜色信息
let rows = imageDate.height;
let cols = imageDate.width;
let keys = [];
let colors_info = [];
let pixel_step = (rows * cols < 600 * 600) ? 1 : 2;  // 降采样步长

// 遍历
for (let row = 1; row < rows - 1;) {
    for (let col = 1; col < cols - 1;) {
        r = imageDate.data[row * cols * 4 + col * 4];
        g = imageDate.data[row * cols * 4 + col * 4 + 1];
        b = imageDate.data[row * cols * 4 + col * 4 + 2];
        hsl = rgbToHsl(r,g,b);
        // 过滤太亮或太暗的颜色
        if("太亮或太暗的条件"){
            continue;
        }
        // 编码得到key
        h_key = Math.floor(hsl[0] / 10) * 10000;
        s_key = Math.floor(hsl[1] / 5) * 100;
        l_key = Math.floor(hsl[2] / 5);
        key = h_key + s_key + l_key;
        let index = keys.indexOf(key);
        if (index < 0) {
              // 没找到该颜色,将key加入到keys序列中
        } else {
              // 找到了key,更新颜色的fre(出现次数)值
        }
        col += pixel_step;
    }
    row += pixel_step;
}



接下来是对颜色信息进行排序和过滤:



// 按照出现次数从高到低排序
colors_info.sort(function(pre, next) {
    return next.fre - pre.fre;
});
// 过滤掉孤立的颜色
colors_info = colors_info.filter((color) => {
    // isolated color
    let flag = (color.fre < 5 - pixel_step) && (len > 400);
    return !flag;
});



之后便是进行聚类和评分了,后续会展开讲。


3.4 chooseSeedColors

因为K均值聚类对初始种子点比较敏感,为了尽快收敛和聚类的精确性,专门写了一个函数用于筛选种子点,核心代码如下:



// 从出现次数最高的颜色开始遍历
for (let i = 0; i < len; i++) {
      // 比较和已有种子点的差异
      for (; j < l; j++) {
        let h_diff = Math.abs(init_seed[j].h - color.h);
        let s_diff = Math.abs(init_seed[j].s - color.s);
        let l_diff = Math.abs(init_seed[j].l - color.l);
        if (h_diff + s_diff + l_diff < 45) {
          // 差异太小则跳过该颜色
          break;
        }
      }
      // 差异比较大则加入种子点数组
      if (j === l) {
        init_seed.push({
          h:color.h,
          s:color.s,
          l:color.l,
          category: color.category,
          fre: color.fre
        });
      }
      // 如果已经满足指定选取的种子点数量,则停止遍历
      if (init_seed.length >= num) {
        break;
      }
    }




3.5 K均值聚类

kMC函数是K均值聚类的实现,形参:


/**
   * [kMC description]
   * @param  {Array} colors   colors of image
   * @param  {Array} seeds    init seeds
   * @param  {number} max_step max iteration of KMeans
   * @return {Array}          results of KMeans
   */
  kMC(colors, seeds, max_step) {}

核心流程:



kMC(colors, seeds, max_step) {
    let iteration_count = 0;
    // 迭代
    while (iteration_count++ < max_step) {
      // filter seeds
      seeds = seeds.filter((seed) => {
        return seed;
      });

      // divide colors into different categories with duff's device
      let len = colors.length;
      let count = (len / 8) ^ 0;
      let start = len % 8;
      while (start--) {
        this.classifyColor(colors[start], seeds);
      }
      while (count--) {
        this.classifyColor(colors[--len], seeds);
        this.classifyColor(colors[--len], seeds);
        this.classifyColor(colors[--len], seeds);
        this.classifyColor(colors[--len], seeds);
        this.classifyColor(colors[--len], seeds);
        this.classifyColor(colors[--len], seeds);
        this.classifyColor(colors[--len], seeds);
        this.classifyColor(colors[--len], seeds);
      }

      // compute center of category
      len = colors.length;
      let hsl_count = [];
      let category;
      while (len--) {
        category = colors[len].category;
        if (!hsl_count[category]) {
          hsl_count[category] = {};
          hsl_count[category].h = 0;
          hsl_count[category].s = 0;
          hsl_count[category].l = 0;
          hsl_count[category].fre_count = colors[len].fre;
        } else {
          hsl_count[category].fre_count += colors[len].fre;
        }
      }
      len = colors.length;
      while (len--) {
        category = colors[len].category;
        hsl_count[category].h += colors[len].h*colors[len].fre/hsl_count[category].fre_count;
        hsl_count[category].s += colors[len].s*colors[len].fre/hsl_count[category].fre_count;
        hsl_count[category].l += colors[len].l*colors[len].fre/hsl_count[category].fre_count;
      }
      // 判断是否满足退出条件
      let flag = hsl_count.every((ele, index) => {
        return Math.abs(ele.h - seeds[index].h)<0.5 && Math.abs(ele.s - seeds[index].s)<0.5 && Math.abs(ele.l - seeds[index].l)<0.5;
      });
      // 新的聚类中心
      seeds = hsl_count.map((ele, index) => {
        return {
          h: ele.h,
          s: ele.s,
          l: ele.l,
          category: index,
          fre: ele.fre_count
        };
      });
      if (flag) {
        break;
      }
    }
    return [seeds,iteration_count];
  }



其中的 classifyColor


3.6 评分

评分部分的实现在 imageScore


let info = {
      colorCount: (Math.log10(colorInfo.length)), // 总的颜色数量
      average:0,  // 平均出现次数
      variance: 0,  // 标准差
      top50Count: 0,  // 前50种颜色占比(出现次数)
      top50Average: 0,  // 前50种颜色的平均出现次数
      top50Variance: 0,  // 前50种颜色出现次数的标准差
      top20Count: 0,  // 同上
      top20Average: 0,
      top20Variance: 0,
      top10Count: 0,  // 同上
      top10Average: 0,
      top10Variance: 0,
      top5Count: 0,  // 同上
      top5Average: 0,
      top5Variance: 0
};



需要注意的是:

  1. 有些指标的数量级太大需要使用 Math.log10() 减少量级;
  2. 所有指标需要归一化到0~1之间,为了保留尽可能多的信息采用除最大值的归一化方法

神经网络评分:

this.net.run(info)



神经网络采用的是 brain ,离线网络存储在util文件夹下的 trainData.js




四、SVG和动画

svg动画在 ScoreLayer.js

<svg className="circle" viewBox="0,0,120,120">
    <defs>
        <linearGradient key="basic0" id="basic0" x1="0" y1="70%" x2="100%" y2="30%">
            <stop offset="0%" style={{stopColor:this.props.loopColors.bc0[0],stopOpacity:1}}></stop>
            <stop offset="100%" style={{stopColor:this.props.loopColors.bc0[1],stopOpacity:1}}></stop>
        </linearGradient>
        <linearGradient key="basic1" id="basic1" x1="0" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" style={{stopColor:this.props.loopColors.bc1[0],stopOpacity:1}} />
            <stop offset="100%" style={{stopColor:this.props.loopColors.bc1[1],stopOpacity:1}}/>
        </linearGradient>
        <linearGradient key="basic2" id="basic2" x1="0" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" style={{stopColor:this.props.loopColors.bc2[0],stopOpacity:1}} />
            <stop offset="100%" style={{stopColor:this.props.loopColors.bc2[1],stopOpacity:1}} />
        </linearGradient>
        <linearGradient key="basic3" id="basic3" x1="0" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" style={{stopColor:this.props.loopColors.bc3[0],stopOpacity:0.9}} />
            <stop offset="100%" style={{stopColor:this.props.loopColors.bc3[1],stopOpacity:0.9}} />
        </linearGradient>
    </defs>
    <g>
        <path className="c1" d="m 40 25.35 q 20 -25.35 40 0 q 31.96 4.65 20 34.64 q 11.96 30 -20 34.64 q -20 25.35 -40 0 q -31.96 -4.65 -20 -34.64 q -11.96 -30 20 -34.64"/>
        <animateTransform attributeName="transform" begin="0s" dur="17s" type="rotate" values="0 60 60;-180 59 59;-360 60 60" repeatCount="indefinite"/>
    </g>
    <g>
        <path className="c2" d="m 40 25.35 q 20 -25.35 40 0 q 31.96 4.65 20 34.64 q 11.96 30 -20 34.64 q -20 25.35 -40 0 q -31.96 -4.65 -20 -34.64 q -11.96 -30 20 -34.64"/>
        <animateTransform attributeName="transform" begin="0s" dur="13s" type="rotate" values="0 60 60;180 61 59;360 60 60" repeatCount="indefinite"/>
    </g>
    <g>
        <path className="c3" d="m 40 25.35 q 20 -25.35 40 0 q 31.96 4.65 20 34.64 q 11.96 30 -20 34.64 q -20 25.35 -40 0 q -31.96 -4.65 -20 -34.64 q -11.96 -30 20 -34.64"/>
        <animateTransform attributeName="transform" begin="0s" dur="11s" type="rotate" values="0 60 60;-180 59 61;-360 60 60" repeatCount="indefinite"/>
    </g>
    <g>
        <circle cx="60" cy="60" r="45" className="c0">
            <animate attributeName="r" values="45;40;45" dur="11s" repeatCount="indefinite" />
        </circle>
    </g>
    <text className="scores" x="50%" y="50%" textAnchor="middle" dominantBaseline="middle">{this.props.score ? this.props.score.toFixed(1) : 'score...'}</text>
</svg>

SVG部分可参考我的codepen,canvas动画本身也不简单,代码也都是我自己实现的,详细的可参考这个codepen


五、运行结果


操作:

我已经把编译好的代码打包到 colorful-color 目录下的 index.html

先选取图片,然后点击紫色button执行demo。

基于聚类和神经网络的图像颜色提取和评分方案_聚类_02

结果:

基于聚类和神经网络的图像颜色提取和评分方案_数组_03

有任何问题可以到github提issue。




标签:count,category,img,评分,colors,len,神经网络,let,聚类
From: https://blog.51cto.com/u_7583030/6442232

相关文章

  • 神经网络的计算步骤
    神经网络的计算步骤输入层1、数据输入隐藏层正向传播:输入端向输出端传递信息2、得到输出值:直线方程进行预测反向传播:输出端向输入端传递信息3、得到误差:损失函数计算误差4、得到偏导数:计算图高效计算偏导数5、更新权重偏置:梯度下降法学习,最小化误差输出层 一个神经网络分为输入......
  • 神经网络:softmax激活函数
    softmax的作用:将多分类的输出值转换为范围在[0,1]和为1的概率分布soft反方词hardhardmax从一组数据中找到最大值softmax为每一个分类提供一个概率值,表示每个分类的可能性。所有分类的概念值之和是1.优点在x轴上一个很小的变化,可以导致y轴上很大的变化,将输出的数值拉开距离。在深......
  • Matlab用深度学习循环神经网络RNN长短期记忆LSTM进行波形时间序列数据预测|附代码数据
    全文链接:http://tecdat.cn/?p=27279最近我们被客户要求撰写关于深度学习循环神经网络RNN的研究报告,包括一些图形和统计输出。此示例说明如何使用长短期记忆(LSTM)网络预测时间序列LSTM神经网络架构和原理及其在Python中的预测应用LSTM网络是一种循环神经网络(RNN),它通过循......
  • 数据分享|MATLAB、R基于Copula方法和k-means聚类的股票选择研究上证A股数据|附代码数
    全文链接:http://tecdat.cn/?p=31733最近我们被客户要求撰写关于Copula的研究报告,包括一些图形和统计输出。Copula方法是测度金融市场间尾部相关性比较有效的方法,而且可用于研究非正态、非线性以及尾部非对称等较复杂的相依特征关系因此,Copula方法开始逐渐代替多元GARCH模型的相......
  • P5333 [JSOI2019]神经网络
    P5333[JSOI2019]神经网络SolutionEGF表示有标号排列。对每棵树分别算出划分成\(i\)条链的方案数,记为\(f_i\)。具体地:设\(dp[u][i][0/1/2]\)表示在\(u\)子树内拆分成\(i\)条已结束的链,\(0\):已拼完,无法再延伸\(1\):单点,可继续向上扩展\(2\):长度\(>1\)的链......
  • Python中TensorFlow的长短期记忆神经网络(LSTM)、指数移动平均法预测股票市场和可视化
    原文链接:http://tecdat.cn/?p=23689最近我们被客户要求撰写关于LSTM的研究报告,包括一些图形和统计输出。本文探索Python中的长短期记忆(LSTM)网络,以及如何使用它们来进行股市预测 ( 点击文末“阅读原文”获取完整代码数据******** )。在本文中,你将看到如何使用一个被称为长短时......
  • DeepBurning:神经网络系列学习加速器自动生成
    介绍一下这篇论文所做的工作。Introduction首先是背景方面,现在出现了CNN、RNN、LSTM等多种神经网络,如何使用硬件加速的方法让这些网络跑的更快?现在已经有的工作:1.GPGPU加速矩阵乘法,可以处理非常大规模的CNN和多种GPU支持的学习框架,但缺点是硬件开销非常大,难以应用在嵌入式......
  • 神经网络模型
    神经网络介绍T.Kohonen于1988年在NeuralNetworks创刊号上给出了神经网络的定义:神经网络是由具有适应性的简单单元组成的广泛并互连的网络,它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。神经网络中最基本的成分是神经元(neuron)模型(即上述定义中的“简单单元”),包......
  • 百度倾力出品|《神经网络机器翻译技术及产业应用》正式上线
    随着经济社会的国际交流合作日益密切,人们迫切需要高质量、高效率的跨语言信息获取和传播工具。《神经网络机器翻译技术及产业应用》以产业需求为牵引,分析了新时期机器翻译的产业需求特点和挑战,介绍了神经网络翻译的基本理论、前沿技术以及面向产业应用的实用系统开发方法。《神经网......
  • Arm NN 成功适配 openEuler Embedded,提供高性能神经网络推理能力
    近期,RISC-VSIG完成了ArmNN在openEulerEmbedded系统的适配,于2023年1月合入系统构建工程代码库,经测试验证可用,实现了神经网络加速库在openEulerEmbedded嵌入式系统上的加速和优化。系统构建工程下载地址:https://gitee.com/openeuler/yocto-meta-openeuler支持ArmN......