数据可视化Task1
任务描述:对图片RGB点进行Kmeans聚类,并将结果数据可视化于前端浏览器上
实验平台:
- Visual Studio Code
- HTML/CSS/Javascript
- Edge/Chorme/Firefox 浏览器
- Echart.min.js 3.8.5版 (过高的版本浏览器无法include,只能使用低版本的echart)
实验目的:制作出可交互的前端框架,使用者上传图片后,可获得图片数据的KMeans聚类可视化信息:如散点分类,中心点。
实验操作过程
- Visual Studio Code安装好
Preview on Web Server
配件 - VSCode中对HTML文件右键,并点击
vscode-preview-server: Launch on browser
- 可以对文件进行上传,后续下方会出现 “按钮”与“文本框”
- 输入2-10的数据后,点击生成KMeans图,可以获取三个可视化图片,若输入数字错误,会有错误提示
- 得到响应柱状图数据
- 并且可以得到相应的散点图分析,指向中心点的时候,还可以得到中心点数据
搭建前端框架
前端框架部分大致如下
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
};
}