牛骨文教育服务平台(让学习变的简单)

用Javascript实现人脸美容

      本文可视为《用HTML5实现人脸识别》的进阶,在人脸识别的基础上,我们将使用纯Javascript来实现如下的功能:

  • 识别和标注人脸以及五官

  • 对人脸进行美容

      从本文的内容中,你将意识到,Javascript能做的,能实现的,远远比你想象的多。

演示

一、实现

1、人脸识别

      Face.com有包括检测、识别在内的多个API接口,根据《用HTML5实现人脸识别》一文,我们已经可以实现图片上传,并得到检测的结果,结果如下:

返回的参数

      返回参数的详细解释参见http://developers.face.com/docs/api/return-values/,其中tags为多张照片的识别结果,每一个结果包括了耳朵、眼睛、嘴、鼻的中心位置,以及年龄、性别、是否佩戴眼镜、情绪、是否在笑等多种信息。

      上传图片并请求接口的代码如下。

function buildRequest(imgSrc) {
    document.getElementById("detect").disabled = true;
    document.getElementById("beauty").disabled = true;

    var canvas = document.getElementById("canvas");  
    var ctx = canvas.getContext("2d");  

    var imgObj = new Image();  
    imgObj.src = imgSrc;  
    canvas.width = imgObj.width; 
    canvas.height = imgObj.height;  
    ctx.drawImage(imgObj, 0, 0);  
    var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height); 

    document.getElementById("bigImg").style.width = imgObj.width;

    var data = canvas.toDataURL("image/jpeg", 1.0);  
    newblob = dataURItoBlob(data);  

    var formdata = new FormData();  
    formdata.append("api_key", "your key");  
    formdata.append("api_secret", "your secret");  
    formdata.append("filename","avatar.jpg");  

    formdata.append("file",newblob);   

    $.ajax({  
       url: "http://api.face.com/faces/detect.json?attributes=age_est,gender,mood,smiling,glasses",  
       data: formdata,  
       cache: false,  
       contentType: false,  
       processData: false,  
       dataType:"json",  
       type: "POST",  
       success: function (data) {  
           handleResult(data.photos[0]);  
       }
    }); 
}

人脸标注

      我们将根据人脸识别的结果对五官和面部进行标注。标注的方式有两种,一种是基于Canvas的绘图,一种是传统DIV方式标注。下面我们采用第二种方式,原理是在各个点上画一个3*3的DIV,DIV的背景色为红色,采用绝对定位。接口返回的五官数值为宽高所在点的百分比值,所以需要先进行换算。

      标注五官的方法如下:

function drawFacial(data) {
    var width = data.width;
    var height = data.height;

    var points = ["eye_left", "eye_right", "mouth_left", "mouth_center", "mouth_right", "nose", "ear_left", "ear_right"];

    for(var i = 0; i < points.length; i++) {
        var div = document.createElement("div");
        div.style.width = "3px";
        div.style.height = "3px";
        div.style.backgroundColor = "red";
        div.style.position = "absolute";
        div.className = "facePoint";

        var values = data.tags[0][points[i]];

        if(values != null) {
            var left_x = values.x;
            div.style.left = left_x * width / 100 - 1 + "px";
            var left_y = values.y;
            div.style.top = left_y * height / 100 - 1 + "px";

            document.body.appendChild(div);
        }
    }
}

      标注人脸区域的方法如下:

function drawFace(data) {
	var width = data.width;
	var height = data.height;

	var faceWidth = data.tags[0].width * width / 100;
	var faceHeight = data.tags[0].height * height / 100;
	var faceCenter = data.tags[0].center;

	var div = document.createElement("div");
	div.style.width = faceWidth + "px";
	div.style.height = faceHeight + "px";
	div.style.borderColor = "red";
	div.style.borderWidth = "1px";
	div.style.borderStyle = "dotted";
	div.style.position = "absolute";
	div.className = "faceBox";

	div.style.left = faceCenter.x * width / 100 - faceWidth / 2 - 1 + "px";
	div.style.top = faceCenter.y * height / 100 - faceHeight / 2 - 1 + "px";

	document.body.appendChild(div);
}

      从结果来看,Face.com的检测结果非常精准。

人脸美容

      对人脸我们采用两种效果叠加进行美容,分别是增加亮度和模糊度,这样可以让人脸看起来更白,皮肤更好。

1、  增加亮度

      增加亮度其实非常简单,只需要在像素点的RGB通道上增加一个固定的值。代码如下:

var imgPixels = ctx.getImageData(faceLeft, faceTop, faceWidth, faceHeight);  

var data = imgPixels.data;
adjustment = 5;

for (var i = 0; i < data.length; i += 4) {
	data[i] += adjustment;
	data[i+1] += adjustment;
	data[i+2] += adjustment;
} 

2、  添加模糊效果

      模糊效果较为复杂,平常我们常用到的包括均值模糊和高斯模糊。在HTML5Rocks上有一篇很棒的文章《IMAGE FILTERSWITH CANVAS》,里面有各种图像处理效果,我们可以直接应用里面的模糊效果(也即3*3的均值模糊)。

原图

锐化

索贝尔滤镜

      代码如下:

var Filters = {};

Filters.convolute = function(pixels, weights, opaque) {
  var side = Math.round(Math.sqrt(weights.length));
  var halfSide = Math.floor(side/2);
  var src = pixels.data;
  var sw = pixels.width;
  var sh = pixels.height;
  // pad output by the convolution matrix
  var w = sw;
  var h = sh;
  var output = Filters.createImageData(w, h);
  var dst = output.data;
  // go through the destination image pixels
  var alphaFac = opaque ? 1 : 0;
  for (var y=0; y<h; y++) {
    for (var x=0; x<w; x++) {
      var sy = y;
      var sx = x;
      var dstOff = (y*w+x)*4;
      // calculate the weighed sum of the source image pixels that
      // fall under the convolution matrix
      var r=0, g=0, b=0, a=0;
      for (var cy=0; cy<side; cy++) {
        for (var cx=0; cx<side; cx++) {
          var scy = sy + cy - halfSide;
          var scx = sx + cx - halfSide;
          if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
            var srcOff = (scy*sw+scx)*4;
            var wt = weights[cy*side+cx];
            r += src[srcOff] * wt;
            g += src[srcOff+1] * wt;
            b += src[srcOff+2] * wt;
            a += src[srcOff+3] * wt;
          }
        }
      }
      dst[dstOff] = r;
      dst[dstOff+1] = g;
      dst[dstOff+2] = b;
      dst[dstOff+3] = a + alphaFac*(255-a);
    }
  }
  return output;
};

Filters.tmpCanvas = document.createElement("canvas");
Filters.tmpCtx = Filters.tmpCanvas.getContext("2d");

Filters.createImageData = function(w,h) {
  return this.tmpCtx.createImageData(w,h);
};

      最后对Canvas里的图像叠加两种效果的代码如下:

var width = data.width;
var height = data.height;

var faceWidth = data.tags[0].width * width / 100;
var faceHeight = data.tags[0].height * height / 100;
var faceCenter = data.tags[0].center;

var faceLeft = faceCenter.x * width / 100 - faceWidth / 2;
var faceTop = faceCenter.y * height / 100 - faceHeight / 2;

var canvas = document.getElementById("canvas");  
var ctx = canvas.getContext("2d");  
var imgPixels = ctx.getImageData(faceLeft, faceTop, faceWidth, faceHeight);  

var data = imgPixels.data;
adjustment = 5;

for (var i = 0; i < data.length; i += 4) {
	data[i] += adjustment;
	data[i+1] += adjustment;
	data[i+2] += adjustment;
} 
imgPixels = Filters.convolute(imgPixels, [ 1/9, 1/9, 1/9,
	1/9, 1/9, 1/9,
	1/9, 1/9, 1/9 ], 1);
	
ctx.putImageData(imgPixels, faceLeft, faceTop, 1, 1, faceWidth - 3, faceHeight - 3);  

      这样,我们完成了所有的代码。是不是相当的有成就感?看起来我们完成了相当新颖而酷炫的工作。

二、思考

1、  美容滤镜是否有其他实现的方式?

      之前我介绍过CSS3滤镜,它有虚化和亮度调节两个方法可以方便直接的为图片添加滤镜效果,可以在《遇见CSS3滤镜》这篇文章里了解到。

2、我们还可以实现哪些美容效果?

1)去红眼

      检测到眼睛位置后,我们可以采用一定范围内的滤镜,把这个范围内的红色转换为黑色,从而实现去红眼效果。

2)眼睛放大

      检测到眼睛位置后,我们还可以采用一定的算法,实现眼睛放大的效果。要体验这个效果,可以下载百度魔图客户端体验。

百度魔图-强大的图片处理客户端

3)背景虚化

      背景虚化效果基本和我们文中提到的效果是相反的方式。

3、  文中的效果是否还可以优化?

      例如是否可以把文中美容的区域将正方形变为椭圆形,更为贴近人脸的形状?其实并不难,读者有兴趣可深入思考和实现。

4、  除了美容,我们还能完成什么样的功能?

      Face.com提供了三个功能演示:人脸Tag、情景照片、人脸查找。对于应用开发者来说,从来不缺乏创意,例如Face.com图片检测返回的参数里包括图片旋转角度,我们可以利用这个参数实现图片批量自动旋转的功能(我最近在整理旅游的照片时就深深的理解了这个功能的便利性和必要性)。或者基于微博的图片爱情速配App?我期待更有创意和使用价值idea的出现。 

扩展阅读

本文来自蒋宇捷的博客,转载请注明。