数据可视化 —— 小练习1 KMeans聚类并数据可视化图像像素点

news2025/1/10 20:46:30

数据可视化Task1

任务描述:对图片RGB点进行Kmeans聚类,并将结果数据可视化于前端浏览器上

实验平台

  1. Visual Studio Code
  2. HTML/CSS/Javascript
  3. Edge/Chorme/Firefox 浏览器
  4. Echart.min.js 3.8.5版 (过高的版本浏览器无法include,只能使用低版本的echart)

实验目的:制作出可交互的前端框架,使用者上传图片后,可获得图片数据的KMeans聚类可视化信息:如散点分类,中心点。

实验操作过程

  1. Visual Studio Code安装好 Preview on Web Server配件
  2. VSCode中对HTML文件右键,并点击vscode-preview-server: Launch on browser

在这里插入图片描述

  1. 可以对文件进行上传,后续下方会出现 “按钮”与“文本框”

在这里插入图片描述

  1. 输入2-10的数据后,点击生成KMeans图,可以获取三个可视化图片,若输入数字错误,会有错误提示

在这里插入图片描述

  1. 得到响应柱状图数据

在这里插入图片描述

  1. 并且可以得到相应的散点图分析,指向中心点的时候,还可以得到中心点数据

在这里插入图片描述

搭建前端框架

前端框架部分大致如下

head部分
作用主要为引用在线库

<head>
		<meta charset="utf-8">
		<title>lab_01</title>
		<script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.min.js"></script>   
		<script src="http://echarts.baidu.com/resource/echarts-gl-latest/dist/echarts-gl.min.js"></script> 
		<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
		<script type="module" src="./main.js" src > </script>
	</head>

style部分用于进行部分按钮的隐藏

<style>
		div
		{
			/* border:1px solid #c3c3c3; */
			position: relative;
			left : 70px;
			top : 120px;
		}
		.hidden
		{ 
			visibility:hidden;
		}
		.showChart
		{
			height: 500px;
			width: 800px ;
			border: 1px solid #333333;  
			position: absolute; 
			left : 800px;
			top : 0px;
		};
</style>

主体部分

<body>
		
		<div>
			
			<canvas class = '' id = "myCanvas" style="border:1px solid #c3c3c3; height : 500px; width : 700px"			>
				Your browser does not support the canvas element.
			</canvas>

			<canvas class="showChart" id="showTable" style="height: 500px;
			width: 700px ;">
			</canvas>

			<br>
			<input type="file" id= "myFile" style = "position: relative;"/>
			<br><br>
			<input type = 'submit' id = 'KMeans' value = '生成KMeans图'	class = 'hidden'/>
			<input type="text" id = 'KMeansNum' class = 'hidden' width = '300px' value= "2" />
			<br><br>
			<input type = 'button' id = 'barPlot' value = "柱状图" class = 'hidden'/>
			<input type = 'button' id = 'piePlot' value = "玫瑰饼状图" class = 'hidden'/>
			<input type = 'button' id = 'scatterPlotKMeans' value = "聚类散点图" class = 'hidden'/>
		</div>
		
	</body>

按钮响应事件

var myFile = document.getElementById('myFile');//获取对应的文件按钮元素
myFile.onchange = function (event){ //点击按钮相应的事件
	// what to do in the function
}

详情请看main.js文件

Kmeans聚类的实现

核心代码

var cnt = 0;
do{
	for(var i = 0; i < pixelArray.length; i++)
	{
		//对每个点进行遍历,看是否需要归到新类
		var min_dis = Infinity;
		var which_class = -1;
		var dR = 0, dG = 0, dB = 0;
		for(var j = 0; j < numOfClass; j++)
		{
			//使用RGB对应的欧氏空间距离
			//console.log(i, j);
			dR = pixelArray[i][0] - center_point[j][0];
			dG = pixelArray[i][1] - center_point[j][1];
			dB = pixelArray[i][2] - center_point[j][2];
			var dis = Math.pow(dR*dR + dB*dB + dG*dG, 0.5)
			//这里不用pow其实也是可以的
			if(dis < min_dis)
			{
				min_dis = dis;
				which_class = j;
			}
		}
		//遍历所有类之后更新节点
		class_of_point[i] = which_class;
		//记录每个点的新类,为后续更新铺垫
		eachClass[which_class][0] += pixelArray[i][0];
		eachClass[which_class][1] += pixelArray[i][1];
		eachClass[which_class][2] += pixelArray[i][2];
		eachClass[which_class][3] ++;
	}

	//对每个类进行中心点更新
	for(var j = 0; j < center_point.length; j++)
	{   
		var r = eachClass[j][0] / eachClass[j][3];
		var g = eachClass[j][1] / eachClass[j][3];
		var b = eachClass[j][2] / eachClass[j][3];
		center_point[j] = [r, g, b];
	}
	var cnt = 0;//记录变动幅度
	for(var i = 0; i < class_of_point.length; i++)
	{
		if(cmp_array[i] != class_of_point[i])
		{
			cnt++;
		}
	}
	//console.log(1);
	
	var condition = class_of_point.length / 1000;
	if(condition < 5)
	{
		condition = 5;
	}    
}while(cnt > condition)

源码和文件分布

在这里插入图片描述

front_part.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>lab_01</title>
		
		<script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.min.js"></script>   
		<script src="http://echarts.baidu.com/resource/echarts-gl-latest/dist/echarts-gl.min.js"></script> 
		<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
		<script type="module" src="./main.js" src > </script>
		
	</head>
	
	<style>
		div
		{
			/* border:1px solid #c3c3c3; */
			position: relative;
			left : 70px;
			top : 120px;
			
		}
		.hidden
		{ 
			
			visibility:hidden;
		}
		.showChart
		{
			height: 500px;
			width: 800px ;
			border: 1px solid #333333;  
			position: absolute; 
			left : 800px;
			top : 0px;
		};

	</style>
	
	<body>
		
		<div>
			
			<canvas class = '' id = "myCanvas" style="border:1px solid #c3c3c3; height : 500px; width : 700px"
				
			>
				Your browser does not support the canvas element.
			</canvas>

			<canvas class="showChart" id="showTable" style="height: 500px;
			width: 700px ;">
			</canvas>

			<br>
			<input type="file" id= "myFile" style = "position: relative;"/>
			<br><br>
			
			<input 
				type = 'submit' id = 'KMeans' value = '生成KMeans图'
				class = 'hidden'
				/>

				<!-- 这里input模块里面 style一次 只能赋值一个,不然无法点击,就很奇怪-->
			<input type="text" id = 'KMeansNum' class = 'hidden'
				width = '300px'
				value= "请输入2-10的数字" />
				<!-- "请输入2-10的数字" -->
			
			<br><br>
			
			<input 
				type = 'button' id = 'barPlot' value = "柱状图" class = 'hidden'
				/>
			<input 
				type = 'button' id = 'piePlot' value = "玫瑰饼状图" class = 'hidden'
				/>
			<input 
				type = 'button' id = 'scatterPlotKMeans' value = "聚类散点图" class = 'hidden'
				/>
		
		</div>
		
	</body>
</html>

main.js


import * as KMeans from './include/KMeans.js';;
import * as CC from './include/CreateCharts.js';
// import * as echarts from 'https://cdn.bootcdn.net/ajax/libs/echarts/5.4.2/echarts.min.js';
// import * as echarts_gl from 'https://cdn.bootcdn.net/ajax/libs/echarts-gl/2.0.8/echarts-gl.min.js';



var myCanvas = document.getElementById('myCanvas');
var myFile = document.getElementById('myFile');
var myKMeans = document.getElementById('KMeans');
var myKMeansNum = document.getElementById('KMeansNum');

//var myOriginalPicture = document.getElementById('originalPicture');
var myBarPlotBotton = document.getElementById('barPlot');
var myPiePlotBotton = document.getElementById('piePlot');
var myScatterPlotBottonKMeans = document.getElementById('scatterPlotKMeans');
var pixelArray = [];

var myShowTable = document.getElementById('showTable');

// console.log(myCanvas);
// var myChart = echarts.init(myCanvas);
/*  
    onchange事件,用于监视myFile这个变量
    处理前端输入数据部分 
    本来想把function打包放入别的include内部,然后调用外部函数
    但是查了很多资料发现应该是不可以的
*/

var to_cmp_class_num;


myFile.onchange = function (event)
{
	var selectedFile = event.target.files[0];//files属性是一个描述用户已选文件信息的数组,未开多选模式的话只有一个元素
	//console.log(selectedFile, "File.onchange\n");
	
	var reader = new FileReader(); //创建了一个FileReader对象,存于reader
	reader.readAsDataURL(selectedFile);// 文件内容读取成Data URL
	// 把隐藏的按钮显示出来
	myKMeans.style.visibility = 'visible';
    myKMeansNum.style.visibility = 'visible';
	
	reader.onload = function putImageToCanvas(event)
	{
		 var img = new Image();//图片对象
		 img.src = event.target.result;// reader.onload的传递
		 img.onload = function()
		 {
			//console.log(img.scr, "\n");
			//与lab02课堂实验部分无较大差异 canvas的尺寸设置成图像的尺寸
			myCanvas.width = Math.min(700, img.width);
			myCanvas.height = Math.min(500, img.height);
			//console.log(img.width * img.height);
			var context = myCanvas.getContext('2d');
			context.drawImage(img, 0, 0);
			var imgdata = context.getImageData(0, 0, img.width, img.height);//getImageData获取图像像素数据供JavaScript处理
		 
			var len = imgdata.data.length;
			var cnt = 0;
			for(var i = 0; i < len; i += 4)
			{
				var tempArray = [ imgdata.data[i], imgdata.data[i + 1], imgdata.data[i + 2] ];
				pixelArray[cnt++] = tempArray;
			}
			//console.log(pixelArray[0]);
		 }
	}
    return;
}//

var K_ARRAY;//用来存两个数组,一个是每个点对应的类,第二个是每个类的中心点
// 获取Kmeans的参数数量之后实现Kmeans聚类,顺便再产生对应的按钮来切换图片


var forBarChart = [];

myKMeans.onclick = function(event)
{
	//console.log("myKMeans.onchange调用成功");
	var numOfClass = myKMeansNum.value;
	
	if(isNaN(numOfClass) || numOfClass < 2 || numOfClass > 10)
	{
		alert("Please enter a number between 2 and 10");
	}
	else 
	{
		// if(to_cmp_class_num == numOfClass)//等于的话没必要重新算一遍
		// {
		// 	CC.barChart(myShowTable, forBarChart);
		// 	return;
		// }
		//else
		{
			to_cmp_class_num = numOfClass;
			forBarChart = [];

			//myOriginalPicture.style.visibility = 'visible';
			myBarPlotBotton.style.visibility = 'visible';
			myScatterPlotBottonKMeans.style.visibility = 'visible';
			myPiePlotBotton.style.visibility = 'visible';

			//要把初始图像展现出来
	
			// 实现K means聚类 
				//KMeans_array = KMeans.KMeans()
				
				
			// pixelArray.sort();
			
			K_ARRAY = KMeans.KMeans(pixelArray, numOfClass);
			
			for(var i = 0; i < numOfClass; i++)
			{
				forBarChart[i] = 0;
			}
			for(var i = 0; i < K_ARRAY.category.length; i++)
			{
				forBarChart[K_ARRAY.category[i]] += 1;
			}	
			//console.log(forBarChart); 
			forBarChart.sort(function(a, b){return a-b});	
			//console.log(forBarChart); 
			
			//将K means聚类结果转换为可视化图片
			//初始散点
	
			//这里先尝试画出来柱状图
			var newArr = JSON.parse(JSON.stringify(forBarChart));
			CC.barChart(myShowTable, newArr)
			//CC.scatterChart(myShowTable);
		}
		
	}
}


myBarPlotBotton.onclick = function(numOfClass)
{
	var numOfClass = myKMeansNum.value;
	if(isNaN(numOfClass) || numOfClass < 2 || numOfClass > 10)
	{
		alert("Please enter a number between 2 and 10");
		return;
	}
	
	//console.log("problem 1")
	// if(to_cmp_class_num == numOfClass)//等于的话没必要重新算一遍
	// {
	// 	var newArr = JSON.parse(JSON.stringify(forBarChart));
	// 	CC.barChart(myShowTable, newArr)
	// 	return;
	// }
	// else 
	{
		//console.log("problem 3")
		to_cmp_class_num = numOfClass;
		forBarChart = [];

		K_ARRAY = KMeans.KMeans(pixelArray, numOfClass);
		for(var i = 0; i < numOfClass; i++)
		{
			forBarChart[i] = 0;
		}
		for(var i = 0; i < K_ARRAY.category.length; i++)
		{
			forBarChart[K_ARRAY.category[i]] += 1;
		}	
		forBarChart.sort(function(a, b){return a-b});	
		var newArr = JSON.parse(JSON.stringify(forBarChart));
		CC.barChart(myShowTable, newArr)
	}
	
}

myPiePlotBotton.onclick = function(numOfClass)
{
	var numOfClass = myKMeansNum.value;
	if(isNaN(numOfClass) || numOfClass < 2 || numOfClass > 10)
	{
		alert("Please enter a number between 2 and 10");
		return;
	}
	
	//console.log("problem 1")
	// if(to_cmp_class_num == numOfClass)//等于的话没必要重新算一遍
	// {
	// 	var newArr = JSON.parse(JSON.stringify(forBarChart));
	// 	CC.barChart(myShowTable, newArr)
	// 	return;
	// }
	// else 
	{
		//console.log("problem 3")
		to_cmp_class_num = numOfClass;
		forBarChart = [];

		K_ARRAY = KMeans.KMeans(pixelArray, numOfClass);
		for(var i = 0; i < numOfClass; i++)
		{
			forBarChart[i] = 0;
		}
		for(var i = 0; i < K_ARRAY.category.length; i++)
		{
			forBarChart[K_ARRAY.category[i]] += 1;
		}	
		forBarChart.sort(function(a, b){return a-b});	
		var newArr = JSON.parse(JSON.stringify(forBarChart));
		CC.pieChart(myShowTable, newArr)
	}
	
}


myScatterPlotBottonKMeans.onclick = function()
{
	//console.log("test, myscatter plot bottom Kmeans")
	var numOfClass = myKMeansNum.value;
	forBarChart = [];
	
	if(isNaN(numOfClass) || numOfClass < 2 || numOfClass > 10)
	{
		alert("Please enter a number between 2 and 10");
		return;
	}


	// if(to_cmp_class_num == numOfClass)//等于的话没必要重新算一遍
	// {
	// 	CC.scatterChart(myShowTable, pixelArray, K_ARRAY.category, K_ARRAY.mean_point);
	// 	return;
	// }
	// else 
	{
		to_cmp_class_num = numOfClass;
		K_ARRAY = KMeans.KMeans(pixelArray, numOfClass);
		CC.scatterChart(myShowTable, pixelArray, K_ARRAY.category, K_ARRAY.mean_point);
	}
}

// 剩下的几个按钮用于切换不同的图片

CreateCharts.js

//import * as echarts from './forBar/echarts.js'


// export const test = function(variable)
// {
//     console.log("CC", variable);
//     return variable;
// }

const color_for_scatter = [
    '#9943ff',  '#ff7739', '#82ff1c',
    '#cf1e18', '#1f13f8',  '#11f9d2',
    '#bc1fff', '#7f1c10', '#ffde39', '#8f9f5e',
    
]

// const color_for_scatter = [
//     '#63E398', '#934b43'
// ]

const color_for_bar = [
    '#1881b6', '#87a2b4', '#557073',
    '#154a72', '#a57db7',
    '#68abcc', '#caacc4',
    '#9b8fa7', '#7c537f',  '#607b8f'
]

export const barChart = function(myCanvas, forBarChart)
{
    var dom = myCanvas;  //document.getElementById('container');
    var myChart
    if(typeof myChart == 'undefined')
    {
        myChart = echarts.init(
            dom
        );
    }
    myChart.clear();
     
    myChart.resize(
        {
            width: this.width,
            height: this.height
        }
    )
    var app = {};
    
    var option;

    var class_index = []; // 横坐标 xAxis
    for(var i = 0; i < forBarChart.length; i++)
    {
        class_index[i] = i;
        // class_index[i] = 
        // {
        //     value : i,
        // }
    }
    for(var i = 0; i < forBarChart.length; i++)
    {
        var tmp = forBarChart[i];
        forBarChart[i] = 
        {
            value : tmp,
            itemStyle :
            {
                color : color_for_bar[i]
            }

        }
    }

    
    option = {

        grid : {
            left: '3%',
            right: '4%',
            bottom: '3%',
            containLabel:true
        },
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            }
        },
        xAxis: [{
            type: 'category',
            data: class_index,
            show:true,
            // axisTick: {
            //     alignWithLabel: true
            // },
            // axisLabel:{
            //     interval:(index,value) =>{return false}//设置X轴数据不显示
            // }
        }],
        yAxis: [{
            type: 'value',
            show:true
        }],
        grid:{
            containLabel:true
        },
        
        series: [{
            name: "Total",
            data: forBarChart,
            type: 'bar',
            barWidth: '50%',
            // emphasis要卸载itemstyle里面
            //我麻了
            itemStyle:{
                normal:{
                    color:function(para){
                        return color_for_bar[para.dataIndex % color_for_bar.length];
                    },
                    label : {
                        show:false
                    }
                },
                emphasis: {
                    label : {
                        show : true
                    }
                },
            },
        }]


    };

    if (option && typeof option === 'object') 
    {
        myChart.setOption(option);
    }

    window.addEventListener('resize', myChart.resize);
}

export const pieChart = function(myCanvas, forBarChart)
{
    var dom = myCanvas;  //document.getElementById('container');
    var myChart
    if(typeof myChart == 'undefined')
    {
        myChart = echarts.init(
            dom, 
            null, 
            {
                renderer: 'canvas',
                useDirtyRect: false
            }
        );
    }
    myChart.clear();
     
    myChart.resize(
        {
            width: this.width,
            height: this.height
        }
    )
    
    var option;
    var class_index = []; // 横坐标 xAxis
    for(var i = 0; i < forBarChart.length; i++)
    {
        class_index[i] = 
        {
            value : i,
        }
    }
    for(var i = 0; i < forBarChart.length; i++)
    {
        var tmp = forBarChart[i];
        forBarChart[i] = 
        {
            value : tmp,
            name : "category " + i,
            // itemStyle :
            // {
            //     color : color_for_bar[i]
            // }
        }
    }

    
    option = {
        tooltip: {
          trigger: 'item'
        },
        legend: {
          top: '5%',
          left: 'center'
        },
        series: [
          {
            name: 'Access From',
            type: 'pie',
            radius: ['40%', '70%'],
            avoidLabelOverlap: false,
            itemStyle: {
              borderRadius: 10,
              borderColor: '#fff',
              borderWidth: 2
            },
            label: {
              show: false,
              position: 'center'
            },
            emphasis: {
              label: {
                show: true,
                fontSize: 40,
                fontWeight: 'bold'
              }
            },
            labelLine: {
              show: false
            },
            data: forBarChart
            // [
            //   { value: 1048, name: 'Search Engine' },
            //   { value: 735, name: 'Direct' },
            //   { value: 580, name: 'Email' },
            //   { value: 484, name: 'Union Ads' },
            //   { value: 300, name: 'Video Ads' }
            // ]
            ,
            itemStyle:{
                normal:{
                    label : {
                        show:true
                    }
                },
                emphasis: {
                    label : {
                        show : true,
                        //color :  'rgba(0,0,0,1)',
                        formatter : function(para){
                            // console.log(para);
                            // console.log(para.data);

                            return para.data.value;
                        }
                    }
                },
            },
          }
        ]
      };

    if (option && typeof option === 'object') 
    {
        myChart.setOption(option);
    }

    window.addEventListener('resize', myChart.resize);
}

// addChart ();
// $("#btn").on("click", function () {
//     $("#main").remove();
//     var newM = $("<div id='main' style='width: 100%;height:800px; '></div>");
//     newM.appendTo($("body"));
//     addChart();
// });

function duplicate_for_array(pixelArray, category)
{
    let map = new Map();
    let after_duplicate = new Array();  // 数组用于返回结果
    let after_category = new Array()
    for (let i = 0; i < pixelArray.length; i++) {
        var duplicate = pixelArray[i][0] + ' ' + pixelArray[i][1] + ' ' + pixelArray[i][2];

        if (map.has(duplicate)) {  // 如果有该key值
            map.set(duplicate, true);
        } else {
            map.set(duplicate, false); // 如果没有该key值
            after_duplicate.push(pixelArray[i]);
            after_category.push(category[i]);
        }
    }

    // 获得去重的点和数据

    return {
        point : after_duplicate,
        category : after_category
    };
}

function hexToRgba(hex, opacity) {
    return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
            + parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
}

export const scatterChart = function (myCanvas, pixelArray, category, mean_point) {
    var dom = myCanvas; //echarts.init(document.getElementById('myCan'));
   
    var myChart
    if(typeof myChart == 'undefined')
    {
        myChart = echarts.init(
            dom
        );
    }
    myChart.clear();

    myChart.resize(
        {
            width: this.width,
            height: this.height,
            top: this.top,
            left : this.left
        }
    )
    //console.log(myChart);
    // var data = [
    //     [12, 23, 43],
    //     [43, 545, 65],
    //     [92, 23, 33]
    // ];

    //data = pixelArray;
    // 去重
    var after_duplicate = duplicate_for_array(pixelArray, category);
    console.log(after_duplicate.point.length);
    console.log(after_duplicate.category.length);
    
    
    
    //改data,每个class是一类点
    var test_array = []
    for(var i = 0; i < mean_point.length; i++)
    {
        test_array.push([]);
        test_array[i].push(mean_point);
    }

    for(var i = 0; i < after_duplicate.point.length; i++)
    {
        var index = after_duplicate.category[i];
        test_array[index].push(after_duplicate.point[i]);
    }
    console.log(test_array[0].length, test_array[0][0]);
    
    
    //将中心点和去重后的散点分给对应的类
    var data = [] 
    // for(var i = 0; i < 10; i++)
    // {
    //     data.push([]);
    // }
    // for(var i = 0; i < mean_point.length; i++)
    // {
    //     //中心点固定放第一位
    //     data[i].push(mean_point[i]);
    //     console.log(data[i]);
    // }
    // for(var i = 0; i < after_duplicate.point.length; i++)
    // {
    //     var index = after_duplicate.category[i]; //第i个点在哪个类
    //     data[index].push(after_duplicate.point[i]);
    // }
    //console.log(data);

    data[0] = after_duplicate.point;
    data[1] = mean_point;



    myChart.setOption({
        tooltip: {},
        xAxis3D: {
            name: "R",
            type: 'value'
        },

        yAxis3D: {
            name: "G",
            type: 'value',
              scale: true
        },

        zAxis3D: {
            name: "B",
            type: 'value',
        },

        grid3D: {
            // left: 10,
            // containLabel: true,
            // bottom: 10,
            // top: 10,
            // right: 30,
            axisLine: {
                lineStyle: {
                    color: '#000'//轴线颜色
                }
            },
            axisPointer: {
                lineStyle: {
                    color: '#f00'//坐标轴指示线
                },
                show: false//不坐标轴指示线
            },
            viewControl: {
                autoRotate: true,//旋转展示
                autoRotateAfterStill:0.5,//在鼠标静止操作后恢复自动旋转的时间间隔。在开启 autoRotate 后有效
                projection: 'orthographic',
                beta: 10,
                distance: 500
                
            },
            boxWidth: 200,
            boxHeight: 100,
            boxDepth: 200,
            // top: -100
        },

        series: [
            
            {
                type: 'scatter3D',

                data: data[0],//test_array[0],
                symbolSize : function(arr, para)
                {
                    var index = arr.length;
                    return 1.5;
                }
                ,
                
                emphasis: {
                    itemStyle: 
                    {
                        color : 'rgba(0, 0, 0, 1)',
                    },
                    label : {
                        show : true,
                        formatter:function(para){
                            return (para.data[0]) + ' ' 
                                + (para.data[1]) + ' ' + (para.data[2]);
                        }
                    }
                },


                itemStyle: {
                    borderWidth: 0.1,
                    borderColor: 'rgba(255,255,255,0.8)'//边框样式
                },
                itemStyle: {
                    color: function(para){
                        var index = para.dataIndex;
                        var color_index = after_duplicate.category[index];
                        var color = color_for_scatter[color_index];
                        var opacity = 0.3;

                        var rgba = hexToRgba(color, opacity);
                        return rgba;
                        }
                    },
                
            },
            {
                type: 'scatter3D',

                data: data[1],//平均点
                symbolSize : 50,
                
                emphasis: {
                    itemStyle: 
                    {
                        color : 'rgba(0, 0, 0, 1)',
                    },
                    label : {
                        show : true,
                        formatter:function(para){
                            return (para.data[0]).toFixed(2) + ' ' 
                                + (para.data[1]).toFixed(2) + ' ' + (para.data[2]).toFixed(2);
                        }
                    }
                },

                itemStyle: {
                    borderWidth: 0.1,
                    borderColor: 'rgba(255,255,255,0.8)'//边框样式
                },
                itemStyle: {
                    color: function(para){
                        //var index = para.dataIndex;
                        var color = color_for_scatter[para.dataIndex];
                        var opacity = 1;

                        var rgba = hexToRgba(color, opacity);//转换为rgba
                        return rgba;
                        }
                    }
            },
    
    ],

        backgroundColor: { //设置渐变
            type: 'radial',
            x: 0.3,
            y: 0.3,
            r: 2.0,
            colorStops: [
                {
                    offset: 0,
                    color: '#f7f8fa'
                },
                {
                    offset: 1,
                    color: '#cdd0d5'
                }
            ]
        },
        //backgroundColor: "#f0f0f0"
    });
}

KMeans.js





// export const test = function ()
// {
// 	console.log("??");
// }

export const test = function(variable)
{
    console.log("KMeans", variable);
    return variable;
}

export const KMeans = function(pixelArray , numOfClass = 2)
{
    if(typeof(a) == "undefine")
    {
        alert("The pixel array is undefine")
        return []
    }
    else if(!Array.isArray(pixelArray))
    {
        alert("The pixel array is not an array")
        return [];
    }
    else if(numOfClass < 2 || numOfClass > 10)
    {
        alert("The number of classes should be between 2 and 10");
        return [];
    }
    else if(numOfClass > pixelArray.length)
    {
        alert("The number of uploaded image points should be greater than the number of clusters");
        return [];
    }

    var class_of_point = [];//表明对应的点在哪个类
    var center_point = [];//每个类的中心点
    var cmp_array = class_of_point;//用于Kmeans判断点聚类是否进行更新

    for(var i = 0; i < numOfClass; i++)
    {
        var dis = (pixelArray.length - 1) / (numOfClass - 1); //间隔
        var first_point = Math.floor(dis * i); //直接平均分数组取各个平均点作为初始点
        
        center_point.push(pixelArray[first_point]);
        class_of_point[first_point] = i;
        //
    }
    

    var cnt = 0;
    do{
        cmp_array = JSON.parse(JSON.stringify(class_of_point));
        //要用深拷贝,浅拷贝是直接引用
        var eachClass = [];
        for(var j = 0; j < numOfClass; j++)
        {   
            eachClass[j] = [0,0,0,0];//记录R G B 以及对应的类有多少个点
        }

        for(var i = 0; i < pixelArray.length; i++)
        {
            //对每个点进行遍历,看是否需要归到新类
            var min_dis = Infinity;
            var which_class = -1;
            var dR = 0, dG = 0, dB = 0;
            for(var j = 0; j < numOfClass; j++)
            {
                //使用RGB对应的欧氏空间距离
                //console.log(i, j);
                dR = pixelArray[i][0] - center_point[j][0];
                dG = pixelArray[i][1] - center_point[j][1];
                dB = pixelArray[i][2] - center_point[j][2];
                var dis = Math.pow(dR*dR + dB*dB + dG*dG, 0.5)
                //这里不用pow其实也是可以的
                if(dis < min_dis)
                {
                    min_dis = dis;
                    which_class = j;
                }
            }
            //遍历所有类之后更新节点
            class_of_point[i] = which_class;
            //记录每个点的新类,为后续更新铺垫
            eachClass[which_class][0] += pixelArray[i][0];
            eachClass[which_class][1] += pixelArray[i][1];
            eachClass[which_class][2] += pixelArray[i][2];
            eachClass[which_class][3] ++;
        }
        //需要注意,如果有的类没有点了,需要重新分配一个中心点给他
        //这里直接随机分配了
        for(var j = 0; j < numOfClass; j++)
        {   
            if(eachClass[j][3] == 0);//记录R G B 以及对应的类有多少个点
            {
                var ran = Math.floor(Math.random() * pixelArray.length);
                var point = pixelArray[ran];
                class_of_point[point] = j;
                //console.log(point,ran);

                var which_class = class_of_point[ran];
                eachClass[j][0] += point[0];
                eachClass[j][1] += point[1];
                eachClass[j][2] += point[2];
                eachClass[j][3] += 1;

                eachClass[which_class][0] -= point[0];
                eachClass[which_class][1] -= point[1];
                eachClass[which_class][2] -= point[2];
                eachClass[which_class][3] -= 1;
            }
        }

        //对每个类进行中心点更新
        for(var j = 0; j < center_point.length; j++)
        {   
            var r = eachClass[j][0] / eachClass[j][3];
            var g = eachClass[j][1] / eachClass[j][3];
            var b = eachClass[j][2] / eachClass[j][3];
            center_point[j] = [r, g, b];
        }
        //console.log(center_point);
        var cnt = 0;//记录变动幅度
        for(var i = 0; i < class_of_point.length; i++)
        {
            if(cmp_array[i] != class_of_point[i])
            {
                cnt++;
            }
        }
        //console.log(1);
        
        var condition = class_of_point.length / 1000;
        if(condition < 5)
        {
            condition = 5;
        }    
    }while(cnt > condition)
    
    
    
    return {
        category : class_of_point,
        mean_point : center_point
    };
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/472088.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python采集豆Top250电影数据 , 看看质量及评分

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 本次案例涉及点: 1、动态数据抓包演示 2、csv文件保存 3、requests模块的使用 4、parsel解析数据的使用 环境介绍&#xff1a; 软件安装 python 3.8 pycharm 不会安装的可以文末名片我获取哦 &#x1f60e; 模块使用…

windows安装flutter

在flutter官网下载flutter 在 Windows 操作系统上安装和配置 Flutter 开发环境 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 下载文件后&#xff0c;解压文件把文件存放在指定位置 打开flutter_console.bat文件 输入flutter doctor flutter报错提示&#xff08;…

unity-VRTK学习日记1(VRTK4|无头盔开发模拟器SpatialSimulator)

目录 前言 前期准备 1.配置VRTK4&#xff1a; 2.把几个插件给安装好 查看自己安装了哪些VRTK插件 3.添加模拟器 操作方法 操作方法&#xff1a;人话 前言 VRTK的较早版本不适用于Unity的新版本&#xff0c;原因好像是Unity将VR开发功能并入到自家的XR下了。就是之前更新…

即时通讯独立系统源码包含Android 、iOS、PC

demo软件园每日更新资源,请看到最后就能获取你想要的: 1.经典版哇呼星聊即时通讯独立系统源码 包含Android 、iOS、PC 自带教程 哇呼星聊即时通讯系统源码 AndroidiOSPC三端 附教程 服务器最低配置4H4G 这套安装跟shiku一样 1.安装宝塔&#xff0c;只安装Nginx&#xff0c;其他…

力扣,合并石头最低成本算法题

1&#xff1a;这个题有题解&#xff0c;自己可以去看力扣&#xff0c;合并石头 2&#xff1a;网上也有视频自己去看视频讲解 3&#xff1a;下面我自己的一些理解 4&#xff1a;原需求&#xff1a; 5&#xff1a;代码&#xff1a;使用贪心算法和最小堆来求解&#xff1a; im…

FreeRTOS系统学习-内核篇.01-数据结构---列表与列表项定义详解-链表节点插入实验

# 内核篇.01 列表与列表项 为什么要学列表&#xff1f;链表单向链表双向链表 FreeRTOS 中链表的实现节点节点初始化尾节点根节点链表根节点初始化将节点插入到链表的尾部将节点按照升序排列插入到链表将节点从链表删除节点带参宏小函数 链表节点插入实验实验现象 为什么要学列表…

TPM-TPM-Profile-PTP协议-2

TCG_PCClient_Device_Driver_Design_Principles_TPM2p0_v1p1_r4_211104_final.pdf 4 简介 本文档补充了 TCG PC Client Platform TPM Profile for TPM 2.0 Specification [PTP]&#xff1b; 特别是&#xff0c;本文档为有兴趣开发 DD 的 DD 编写者提供指导&#xff0c;以便与旨…

【C++】带你先入门类和对象(上)

作者&#xff1a;小树苗渴望变成参天大树 作者宣言&#xff1a;认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; 文章目录 前言一、面向过程和面向对象初步认识二、类的引入三、类的定义四.类的访问限…

java后端为前端提供接口,将数据以树形结构返回(工具类hutool.core.lang.tree)

用于后端java的实用、简洁、通俗易懂的树形工具类使用笔记 设计需求是做一个类似于部门管理的树形结构&#xff0c;后端设计表写逻辑&#xff0c;为前端提供接口&#xff0c;将数据以树形结构返回 这里直接上代码&#xff0c;基本可以直接拿去用&#xff0c;把父id和名称做对…

Oracle之可视化ETL任务调度设计接口实现方案

背景 以前的项目有这么一个需求,线上的任务需要灵活的可视化配置,而一般的ETL任务调度需要写JOB的SQL脚本(需要对Oracle的dbms_job比较熟悉),而维护成本比较高,虽然可以查看执行的信息,但是权限比较高,不利于项目后台数据的安全。所以通过需求背景,自研设计任务调度平…

小程序按钮重复点击解决方案

文章目录 前言一、为什么会发生重复点击二、针对以上问题怎么处理1、分析解决方法&#xff1a;1. 反馈2.禁用 三、最优解决总结 前言 小程序是直面用户便捷的应用&#xff0c;而在用户使用时往往都会涉及到关键节点的按钮点击&#xff0c;例如&#xff0c;注册登录时&#xff…

服务百万商家的系统,发布风险如何规避?微盟全链路灰度实践

一分钟精华速览 全链路灰度发布是指在微服务体系架构中&#xff0c;应用的新、旧版本间平滑过渡的一种发布方式。由于微服务之间依赖关系错综复杂&#xff0c;一次发布可能会涉及多个服务升级&#xff0c;所以在发布前进行小规模的生产环境验证&#xff0c;让新版本的应用实例…

锂电材料浆料匀浆搅拌设备轴承经常故障如何处理?

锂电材料浆料匀浆搅拌设备是锂电池生产中重要的设备之一&#xff0c;用于将活性材料、导电剂、粘结剂和溶剂混合成均匀的浆料&#xff0c;是电极制备过程中不可或缺的步骤。然而&#xff0c;由于高速搅拌和化学腐蚀等因素的影响&#xff0c;轴承经常会出现故障&#xff0c;导致…

Java基础部分面试题(2023最新)

一、Java概述 1. 谈谈你对 Java 平台的理解&#xff1f; ① 平台无关性&#xff08;一次编译到处运行&#xff09; ② GC&#xff08;垃圾自动回收机制&#xff0c;不像C那样需要手动去释放堆内存&#xff09; ③ 语言特性&#xff08;泛型、反射、Lambda 表达式&#xff09; …

Dubbo 简易环境搭建以及使用(2)

目录 环境搭建 Dubbo的3种使用方式&#xff1a; 1. XML配置的方式&#xff0c;一般用于Spring MVC工程 2. 配置文件的方式 &#xff08;spring boot工程&#xff09; 3. 注解方式 Dubbo 控制台 环境搭建 本篇将介绍Spring boot zookeeper Dubbo 简易环境的搭建以及使用…

同等学力申硕如何择校?

我们常常会在一些艰难选择面前不知所措&#xff0c;比如说不知道选择什么样的院校学习&#xff0c;不知道选择什么样的专业学习&#xff0c;不知道同等学力申硕的具体过程&#xff0c;不知道这个过程中你要付出多大的代价&#xff0c;更不知道学习中的一些关键环节等&#xff0…

飞利浦水健康携净水新品重磅亮相AWE2023

2023年度中国家电及消费电子博览会&#xff08;AWE2023&#xff09;于4月27日在上海新国际博览中心正式开幕。其中&#xff0c;飞利浦水健康携全屋高阶净水G5系列、厨下净水器U22Pro、冰热矿净四合一台式净饮机等新品悉数亮相&#xff0c;在暌违2年的AWE舞台上&#xff0c;为行…

垃圾分类算法训练及部署

垃圾分类算法训练及部署 创建模型与编译模型训练及保存模型生成模型应用 创建模型与编译 数据加载进模型后定义模型结构&#xff0c;并优化损失函数。直接调用VGG-16模型作为卷积神经网络&#xff0c;包括13个卷积层、3个全连接层、5个池化层&#xff0c;后接全连接层&#xf…

终于把 vue-router 运行原理讲明白了(一)!!!

一、vue-router 用法 1.1 首先我们需要在我们的项目中&#xff0c;下载vue-router&#xff0c;我在项目中使用的是3.x版本 1.2 在项目中引入&#xff0c;并实例化路由实例&#xff0c;贴代码如下 1.3 下面代码有两个重点部分&#xff0c;等在第三部分具体分析 &#xff08;1&a…

FAMI-Pose训练

之前写过FAMI-Pose的论文解析&#xff0c;最近跑了一下官方代码&#xff0c;链接是&#xff1a;FAMI-Pose&#xff0c;但有很多问题&#xff0c;感觉是不是作者上传错了。这篇博客讲一下FAMI-Pose的训练。 运行 首先&#xff0c;安装环境&#xff0c;这个根据官方requirement…