查看文章 |
[原创]flash人脸抓取技术,半成品
2008-12-14 16:26
这几天,通过晨会,知道同事正在做一个贺卡的项目。 需要用户选择一张照片,然后将头像放大,缩小并移动到一个64×64的范围。 这些功能都通过javascript实现,然后上传到服务端,然后在flash端,将用户上传的头像,放置在一个头部容器中,这样,卡片中人物的头像就会随着预先设定的舞蹈运动。 但是通过指定64×64的方格而选定的头像是没有办法将背景去除的,我想到以前我做.NET项目的时候,用到的一个被称为木刻的算法,会将图像极端化,处 理成只有黑和白的两种颜色。而在人像摄影中,脸部的边缘部分会因为阴影的原因产生一个比较大的色差,最后使用木刻算法会有一个明显的边缘。那么我是否可以 利用这种算法,将人脸的轮廓取出呢? 带着这个问题,我开始了我的测试。 首先说说木刻算法:由于24位图片的储存规格实际是一个宽×长的矩阵,其中每个点储存的是一个24位颜色值,可以转化为红(Red),绿(Green),蓝(Blue)三个颜色的分量。 每个分量是个8bit值,即能保存0~255(2的8次方)种值。 而木刻算法就是通过找到R,G,B三个分量中最大值和最小值,然后得到(最大值+最小值)/2之后的中间值(还有一种算法是直接找到R,G,B三个分量的平均值)。 此中间值大于我们预先定义的一个阀值,则让颜色值为0xffffff(即白色),否则让颜色值为0(即黑色)。 以前我是用C#写的,现在需要先修改成AS3的代码: private function WoodCarving(bmpData:BitmapData):BitmapData { var newData:BitmapData = new BitmapData(bmpData.width,bmpData.height,false); for(var y:int = 0;y<bmpData.height;y++) { for(var x:int = 0;x<bmpData.width;x++) { //取得颜色值 var color:uint = bmpData.getPixel(x,y); //取得RGB分量 var r:uint = color>>16; var g:uint = color>>8 & 0xff; var b:uint = color & 0xff; var min:uint = r; var max:uint = r; if (g < min) min = g; if (b < min) min = b; if (g > max) max = g; if (b > max) max = b; //获取中间值 var gr:uint = (int)((min + max) / 2); if (gr < _value) gr = 0; else gr = 0xffffff; newData.setPixel(x,y,gr); } } return newData; } 这可以将 ![]() 转换为 ![]() 然后我的思路是建立一个3*3的矩阵(除去中间点的8个点)。 var arr:Array = [[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]]; 让检测点为中心点,探测每一个黑色(颜色值为0)的像素周围的8个点中,如果白色点超过2个以上,我将该点视为边缘点。 bmpData为木刻效果之后的图像数据: var shapeData:BitmapData = new BitmapData(bmpData.width,bmpData.height,false,0xffffff); var xPixel:Array = []; for(var y:int = 0;y<bmpData.height;y++) { for(var x:int = 0;x<bmpData.width;x++) { var tt:uint = bmpData.getPixel(x,y); if( tt == 0 ) { var blackCount:uint = 0; var whiteCount:uint = 0; for each( var zz:Array in arr ) { if( bmpData.getPixel(x + zz[0],y+zz[1]) == 0 ) { blackCount++; } else { whiteCount++; } } var dis:uint = blackCount > whiteCount ? (blackCount - whiteCount) : (whiteCount - blackCount); if( whiteCount > 2 ) { xPixel.push(x); shapeData.setPixel(x,y,0xff); } } } } 我将数据保存在shapeData这个新的bitmapData中。 将x坐标保存到xPixel中。 先将可能的轮廓点找到: 得到这个图像: ![]() 创建左数组var leftPixel:Array = []; 以及右数组var rightPixel:Array = []; 先找到图像的中心: xPixel.sort(Array.NUMERIC); var middleX:Number = (middleX[0] + middleX[openPixel.length-1])/2; 以保证leftPixel数组中保存的都是在middleX左边的坐标,而rightPixel数组中保存的都是在middleX右边的坐标。 获取leftPixel和rightPixel的数据信息: var lastPoint:Object; for(y = 0;y<shapeData.height;y++) { if(lastPoint ) { if( lastPoint.x > middleX ) rightPixel.push(lastPoint); } var count:uint = 0; for( x=0;x<shapeData.width;x++) { if( shapeData.getPixel(x,y) == 0xff ) { if( !count ) { if( x < middleX ) leftPixel.push({x:x,y:y}); } lastPoint = {x:x,y:y}; count++; } } } 最终使用画线的方法将需要做遮罩的区域绘制成黑色。 var shape:Shape = new Shape(); shape.graphics.beginFill(0,1); shape.graphics.lineStyle(1,1); for( var txx:int = 0;txx<rightPixel.length;txx++) { if( txx==0) { shape.graphics.moveTo(rightPixel[txx].x,rightPixel[txx].y); } else { shape.graphics.lineTo(rightPixel[txx].x,rightPixel[txx].y); } } for( txx = leftPixel.length-1;txx>=0;txx--) { shape.graphics.lineTo(leftPixel[txx].x,leftPixel[txx].y); } shape.graphics.endFill(); 并将其作为图像的遮罩: 最终效果: 我的方法在最终绘制的时候还存在一定问题,按我想象应该是可以采取顺时针找点的方式,但是我始终没有研究出确实的算法,所以人脸抓取只能算个半成品吧。 头像为公司一帅哥Eric,单身,收入稳定,有意者请于本人联系 ![]() |
最近读者:




