「项目拾遗」简单图形处理及图形绘制
引言
这个学期,我学习了图像处理的相关课程,对图像有了初步的认识,并且利用web知识,做了相关实践。
在线地址:moyuyc.github.io/htm/painter/
姓名:余聪
学号:19130126
实现技术说明
使用了前端HTML5 canvas API 以及后端Java Web搭建的后台服务。
也就是说,图像处理功能既有使用JavaScript语言实现的,也有利用Java语言实现的,但是都将以网页形式展现
具体技术说明如下图所示。
使用说明
- Image Choose
- 点击白色面板,选择图像起点 右侧Message出现该图片灰度分布情况
- 操作面板解释
- 注意 对于Server操作,由于需要将图像传输给服务器,所以对于图像大小有要求,请使用下面的图片(较小)进行测试
功能介绍
设置图像透明度(直接像素点操作)
- 如图
代码
alphaHandle = function () { setImageAlpha = function(index,alph){ if(paint.images && paint.images.length>index ) { var image = paint.images[index], img = paint.getImageData(image.x,image.y,image.width,image.height); for(var i=0;i<img.data.length;i+=4){ // 设置alph img.data[i+3] = alph; } paint.save(); paint.putImageData(img,image.x,image.y); paint.restore(); } }; var alph; while((alph=parseInt(prompt('please set images alpha. (0~255)',120)))>255 || alph<0); for(var i=0;i<paint.images.length;i++) setImageAlpha(i,alph); };
图像灰化(直接像素点操作)
- 如图
代码
imgGrayHandle = function () { paint.images.forEach(function (ele) { var imgData = paint.getImageData(ele.x,ele.y,ele.width,ele.height); for (var i=0;i<imgData.data.length;i+=4) { var r = imgData.data[i],g=imgData.data[i+1],b = imgData.data[i+2]; var value = (r+g+b)/3; //rgb平均值->灰度 imgData.data[i]=imgData.data[i+1]=imgData.data[i+2]=value; } paint.putImageData(imgData,ele.x,ele.y); }) };
高对比度(直接像素点操作)
- 如图
代码
hgHandle = function () { paint.images.forEach(function (ele) { var imgData = paint.getImageData(ele.x,ele.y,ele.width,ele.height); for (var i=0;i<imgData.data.length;i+=4) { // 取背景色的反作为前景色 imgData.data[i] = 255-imgData.data[i]; imgData.data[i+1] = 255-imgData.data[i+1]; imgData.data[i+2] = 255-imgData.data[i+2]; } paint.putImageData(imgData,ele.x,ele.y); }) };
浮雕效果(锐化滤波器)
- 如图
代码
function ConvolutionMatrix(input, matrix, divisor, offset) { // 创建一个输出的 imageData 对象 var output = document.createElement("canvas") .getContext('2d').createImageData(input); var w = input.width, h = input.height; var iD = input.data, oD = output.data; var m = matrix; // 对除了边缘的点之外的内部点的 RGB 进行操作,透明度在最后都设为 255 for (var y = 1; y < h - 1; y += 1) { for (var x = 1; x < w - 1; x += 1) { for (var c = 0; c < 3; c += 1) { var i = (y * w + x) * 4 + c; oD[i] = offset + (m[0] * iD[i - w * 4 - 4] + m[1] * iD[i - w * 4] + m[2] * iD[i - w * 4 + 4] + m[3] * iD[i - 4] + m[4] * iD[i] + m[5] * iD[i + 4] + m[6] * iD[i + w * 4 - 4] + m[7] * iD[i + w * 4] + m[8] * iD[i + w * 4 + 4]) / divisor; oD[(y * w + x) * 4 + 3] = 255; // 设置透明度 } } } return output; };
图像黑化(直接像素点操作)
- 如图
代码
blackHandle = function () { paint.images.forEach(function (ele) { var imgData = paint.getImageData(ele.x,ele.y,ele.width,ele.height); for (var i=0;i<imgData.data.length;i+=4) { var r = imgData.data[i],g=imgData.data[i+1],b = imgData.data[i+2]; var grey = r*0.3+g*0.59+b*0.11; // 取0或者255,非黑即白 imgData.data[i] = imgData.data[i+1] = imgData.data[i+2] = grey>125 ? 255 : 0; } paint.putImageData(imgData,ele.x,ele.y); }) }
模糊效果(线性平滑滤波器)
- 如图
代码
blurHandle = function (){ paint.images.forEach(function (ele) { var imgData = paint.getImageData(ele.x, ele.y, ele.width, ele.height); var blurR = 3, totalnum = (2 * blurR + 1) * (2 * blurR + 1); for (var i = blurR; i < ele.height - blurR; i++) for (var j = blurR; j < ele.width - blurR; j++) { var totalr = 0, totalg = 0, totalb = 0; //2*blurR 模糊的正方形长宽,total 范围内RGB的总和 for (var dx = -blurR; dx <= blurR; dx++) for (var dy = -blurR; dy <= blurR; dy++) { var x = i + dx var y = j + dy var p = (x * ele.width + y)*4; totalr += imgData.data[p + 0] totalg += imgData.data[p + 1] totalb += imgData.data[p + 2] } var p = (i*ele.width + j)*4; imgData.data[p+0] = totalr / totalnum; imgData.data[p+1] = totalg / totalnum; imgData.data[p+2] = totalb / totalnum; } paint.putImageData(imgData,ele.x,ele.y); }); };
马赛克效果(平滑滤波器)
如图
代码
```javascript mosaicHandle = function (){ paint.images.forEach(function (ele) { var imgData = paint.getImageData(ele.x, ele.y, ele.width, ele.height); var size = 16 var totalnum = size*size; for( var i = 0 ; i < ele.height ; i += size ) for( var j = 0 ; j < ele.width ; j += size ){ var totalr = 0 , totalg = 0 , totalb = 0 //以size为大小作为一个像素方格 for( var dx = 0 ; dx < size ; dx ++ ) for( var dy = 0 ; dy < size ; dy ++ ){ var x = i + dx; var y = j + dy; var p = x*ele.width + y totalr += imgData.data[p*4+0] totalg += imgData.data[p*4+1] totalb += imgData.data[p*4+2] } var p = i*ele.width+j var resr = totalr / totalnum var resg = totalg / totalnum var resb = totalb / totalnum; //将size大小内的像素点全部设为平均rgb for( var dx = 0 ; dx < size ; dx ++ ) for( var dy = 0 ; dy < size ; dy ++ ){ var x = i + dx var y = j + dy var p = x*ele.width + y imgData.data[p*4+0] = resr imgData.data[p*4+1] = resg imgData.data[p*4+2] = resb } } paint.putImageData(imgData,ele.x,ele.y); }) } ```
KMenus算法(矢量量化)
说明:KMenus算法是一个聚类算法,我们可以用该算法思想,找出图像中主要的rgb颜色。(可以将rgb想象为三维空间xyz,找出图像中rgb聚集的地方)然后把属于该聚类中的所有像素点全部赋值为聚类像素值,查看效果
如图
代码
```javascript kMeans : function(imgData){ var data = imgData.data, w = imgData.width, h = imgData.height; var clusters = (function getRandomClusters(k){ var rlt = []; while(k-->0) rlt.push([randomInt(256),randomInt(256),randomInt(256)]); return rlt; })(3); function getRGBDistance(clu,rgb){ return parseInt(Math.sqrt(Math.pow(rgb[0]-clu[0],2)+Math.pow(rgb[1]-clu[1],2),Math.pow(rgb[2]-clu[2],2)).toFixed(0)); } function getCenterCluster(rgbs){ var sumr= 0,sumg= 0,sumb=0; for(var i in rgbs){ sumr+=rgbs[i][0];sumg+=rgbs[i][1];sumb+=rgbs[i][2]; } return [parseInt((sumr/rgbs.length).toFixed(0)), parseInt((sumg/rgbs.length).toFixed(0)), parseInt((sumb/rgbs.length).toFixed(0))] } var forNewClus = new Array(clusters.length),forNewData = new Array(clusters.length); while(true) { for(var i=0;i<forNewClus.length;i++) { forNewClus[i] = []; forNewData[i] = []; } for (var i = 0; i < data.length; i += 4) { var myCluIndex, minDist = Number.MAX_VALUE, rgb = [data[i], data[i + 1], data[i + 2]]; for (var j = 0; j < clusters.length; j++) { var dist = getRGBDistance(clusters[j], rgb); if (dist < minDist) { myCluIndex = j; minDist = dist; } } forNewData[myCluIndex].push(i); forNewClus[myCluIndex].push(rgb); } var isUpdate=false; for(var i=0;i<forNewClus.length;i++) { if(forNewClus[i].length==0) continue; var c = getCenterCluster(forNewClus[i]); if(c[0]!=clusters[i][0]||c[1]!=clusters[i][1]||c[2]!=clusters[i][2]) { clusters[i] = c; isUpdate = true; } } if(!isUpdate) break; } for(var i=0;i<forNewData.length;i++) { for(var j=0;j<forNewClus[i].length;j++){ var val = forNewData[i][j]; data[val] = clusters[i][0]; data[val+1] = clusters[i][1]; data[val+2] = clusters[i][1]; } } console.log(forNewClus,clusters); return {c:clusters,md:imgData}; } ```
直方图均衡化(空域点处理)
说明:直方图均衡化:我们把一张图片对应的rgb像素点分成3个(对应rgb)256(0-255)等级,并且将等级绘制为直方图,我们把直方图变得分布均匀,这就是直方图均衡化。这样的图片往往具有高对比度。
如图
代码
```javascript //http://blog.csdn.net/jia20003/article/details/8119563 //http://hello-wangfeng.iteye.com/blog/1717150 average: function (imgData) { var data = imgData.data, w = imgData.width, h = imgData.height; var histogramR = [], histogramG = [], histogramB = []; for(var i=0; i<data.length; i+=4){ histogramR[data[i]] = histogramR[data[i]]+1 || 1; histogramG[data[i+1]] = histogramG[data[i+1]]+1 || 1; histogramB[data[i+2]] = histogramB[data[i+2]]+1 || 1; } //直方图均衡化 function getRate(grayHis,total,index) { var s = 0; for(var i=0;i<index;i++){ var v = grayHis[i]||0; s+=(v/total); } return Math.floor(s*255); } var total = w*h, newHisR = [], newHisG = [], newHisB = []; for(i=0; i<256; i++){ newHisR[i] = getRate(histogramR,total,i); newHisG[i] = getRate(histogramG,total,i); newHisB[i] = getRate(histogramB,total,i); } console.log([histogramR,histogramG,histogramB],[newHisR,newHisG,newHisB]); for(i=0; i<h; i++){ for(var j=0; j<w; j++){ var v = (i*w+j)<<2; data[v] = newHisR[data[v]]; data[v+1]=newHisG[data[v+1]]; data[v+1]=newHisB[data[v+2]]; } } return imgData; } ```
图像复原
说明:图像在形成、记录、处理和传输过程中,由于成像系统、记录设备、传输介质和处理方法的问题,导致图像质量下降,这种现象叫图像退化。而图像复原就是对退化的图像进行处理,尽可能的复原图像的本来面目。
如图
代码
```java //图像恢复 public int[] imRestore(int[] pixels, int iw, int ih) { double[] newPixels = new double [iw*ih]; double[] newKernel = new double [iw*ih]; //初始化 for(int j = 0; j < ih; j++) { for(int i = 0; i < iw; i++) { newPixels[i+j*iw] = pixels[i+j*iw]&0xff; if((i<5) && (j<5)) newKernel[i+j*iw] = 1.0/25; else newKernel[i+j*iw] = 0; } } //初始化 Complex[] complex = new Complex[iw*ih]; Complex[] comKernel = new Complex[iw*ih]; for(int i = 0;i < iw*ih; i++) { complex[i] = new Complex(0,0); comKernel[i] = new Complex(0,0); } //对原图像进行FFT (快速傅氏变换) fft2 = new FFT2(); fft2.setData2(iw, ih, newPixels); complex = fft2.getFFT2(); //对卷积核进行FFT fft2 = new FFT2(); fft2.setData2(iw, ih, newKernel); comKernel = fft2.getFFT2(); //逆滤波复原 for(int j = 0;j < ih; j++) { for(int i = 0; i < iw; i++) { double re = complex[i+j*iw].re; double im = complex[i+j*iw].im; double reKernel = comKernel[i+j*iw].re; double imKernel = comKernel[i+j*iw].im; double x = reKernel*reKernel+imKernel*imKernel; if(x > 1e-3) { double r = (re*reKernel+im*imKernel)/x; double m = (im*reKernel-re*imKernel)/x; complex[i+j*iw].re = r; complex[i+j*iw].im = m; } } } //进行FFT反变换 fft2 = new FFT2(); fft2.setData2i(iw, ih, complex); pixels = fft2.getPixels2i(); return pixels; } ```
边界检测(自定义方法)
说明:人们看一个物体是,首先感受的就是它的边缘,灰度或结构等信息的突变处称为边缘。边缘是一个区域的结束,也是另一个区域的开始,利用这种特征可以分割图像。 物体边缘上的这种变化可以用微分算子检测出来,通常用一阶或二阶导数来检测边缘。
如图
代码
```java //边界提取 public byte[] Bound(byte bw[], int iw, int ih) { int p, r; byte[] tem = new byte[iw*ih]; for(int j = 0;j < ih; j++) for(int i = 0; i < iw; i++) tem[i+j*iw] = bw[i+j*iw]; for(int j = 1; j < ih - 1; j++) { for (int i = 1; i < iw - 1; i++) { p = bw[i+j*iw]; if(p == 0)//如果当前象素是白色, 进入下一个循环 continue; else { // 检查周边的8-连通域 r = 1; LB: for(int k =-1;k<2;k++) { for(int l=-1;l<2;l++) { if(bw[i+k+(j+l)*iw] == 0) { r = 0; break LB;//跳出2重循环 } } } //如果都是黑点,判定为内部点,改变颜色 if(r == 1) tem[i+j*iw] = 0; } } } return tem; } ```
素描效果(roberts边界检测)
说明:上面说到边缘可以用微分算子检测,roberts算子就是基于一阶导数的边缘检测算子的一种。
如图
代码
//Roberts算法 public int[] robert(int[] px, int iw, int ih, int thresh, boolean flag) { ColorModel cm = ColorModel.getRGBdefault(); int r, r0, r1, r2, r3, g, g0, g1, g2, g3, b, b0, b1, b2, b3; for(int j = 1; j < ih-1; j++) { for(int i = 1; i < iw-1; i++) { r0 = cm.getRed(px[i+j*iw]); r1 = cm.getRed(px[i+(j+1)*iw]); r2 = cm.getRed(px[i+1+j*iw]); r3 = cm.getRed(px[i+1+(j+1)*iw]); /*--------------------------------------------* * ------------------------- * |r0:(i,j) |r1:(i, j+1) | * |-----------------------| 交叉 * |r2:(i+1,j)|r3:(i+1,j+1)| * ------------------------- *--------------------------------------------*/ r = (int)Math.sqrt((r0-r3)*(r0-r3)+(r1-r2)*(r1-r2)); g0 = cm.getGreen(px[i+j*iw]); g1 = cm.getGreen(px[i+(j+1)*iw]); g2 = cm.getGreen(px[i+1+j*iw]); g3 = cm.getGreen(px[i+1+(j+1)*iw]); g = (int)Math.sqrt((g0-g3)*(g0-g3)+(g1-g2)*(g1-g2)); b0 = cm.getBlue(px[i+j*iw]); b1 = cm.getBlue(px[i+(j+1)*iw]); b2 = cm.getBlue(px[i+1+j*iw]); b3 = cm.getBlue(px[i+1+(j+1)*iw]); b = (int)Math.sqrt((b0-b3)*(b0-b3)+(b1-b2)*(b1-b2)); if(flag) { if(r > thresh) r = 0;//黑色,边缘点 else r = 255; px[i+j*iw] = (255<<24)|(r<<16)|(r<<8)|r; } else px[i+j*iw] = (255<<24)|(r<<16)|(g<<8)|b; } } return px; }
其他
清空画板
选择图片下载
画板图片下载
添加文字
图形绘制,图形拖拽,图形填充
参考资料
- 数字图像处理——Java编程与实验 (孙燮华著)