HTML5画布图像分割

4
我试图从一张图片中去除背景(如墙壁)的白色/灰色像素,以便只留下前景(例如我的手)。
我尝试操作画布像素,但由于阴影或其他背景不一致性,前景提取效果并不理想。我甚至尝试了绿色背景,但它并没有提高性能。
for (i = 0; i < imgData.width * imgData.height * 4; i += 4) {
    var r = imgDataNormal.data[i + 0];
    var g = imgDataNormal.data[i + 1];
    var b = imgDataNormal.data[i + 2];
    var a = imgDataNormal.data[i + 3];
    // compare rgb levels for green and set alphachannel to 0;
    selectedR = *a value 0 - 255*
    selectedG = *a value 0 - 255*
    selectedB = *a value 0 - 255*
    if (r <= selectedR || g >= selectedG || b <= selectedB) {
        a = 0;
    }
}

有没有一种高级的方法或库可以使图像背景透明?

示例图片

2个回答

3

将像素从RGB转换为HSL,然后对像素进行阈值过滤,通过将饱和度小于0.12的像素的alpha设置为完全透明来删除它们(使用0..1范围内的饱和度测量 - 该值是通过试错找到的)。

JavaScript

function rgbToHsl( r, g, b ){
  r /= 255, g /= 255, b /= 255;
  var max = Math.max(r, g, b), min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if(max == min){
      h = s = 0; // achromatic
  }else{
      var d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch(max){
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
      }
      h /= 6;
  }
  return [h, s, l];
}

function thresholdHsl(pixels,lower,upper){
  var d = pixels.data;
   var createTest = function( lower, upper ){
     return lower <= upper
            ? function(v){ return lower <= v && v <= upper; }
            : function(v){ return lower <= v || v <= upper; };
   }
   var h = createTest( lower[0], upper[0] );
   var s = createTest( lower[1], upper[1] );
   var l = createTest( lower[2], upper[2] );

  for (var i=0; i<d.length; i+=4) {
   var hsl = rgbToHsl( d[i], d[i+1], d[i+2] );
   if ( !h(hsl[0]) || !s(hsl[1]) || !l(hsl[2]) ){
     d[i+3] = 0;
   }
  }
}

var img = new Image();

img.onload = function() {
  var canvas = document.getElementById('myCanvas');
  var ctx    = canvas.getContext('2d');
  ctx.drawImage(img,0,0);
  var pixels = ctx.getImageData(0,0,canvas.width,canvas.height);
  thresholdHsl(pixels,[0,0.12,0],[1,1,1]);
  ctx.putImageData(pixels, 0, 0);
};
img.src = 'Hand.png';

HTML

<canvas id="myCanvas" width="638" height="475"></canvas>

输出 饱和度阈值

通过从图像中去除噪声来尝试清理边缘,可以进一步改善它。


2

你的代码中有一些错误:

  • 最后一行 a = 0 只是改变了一个局部变量,不会对数组产生影响。你需要直接修改数组。
  • 之后,为了看到结果,你需要将像素数组推回到上下文中。
  • 因为你需要(在这个例子中)检查白色/灰色背景(rgb(255, 255, 255)),所以需要判断像素值是否较大,而非较小。使用 and 运算符而非 or 运算符可以过滤更多阴影(它是灰色的,因此每个颜色分量几乎相同),而不会移除手本身,因为手的颜色更丰富。

以下是示例:

var imgData = context.getImageData(0, 0, canvas.width, canvas.height);

for (i = 0; i < imgData.width * imgData.height * 4; i += 4) {
    var r = imgData.data[i + 0];
    var g = imgData.data[i + 1];
    var b = imgData.data[i + 2];
    var a = imgData.data[i + 3];
    // compare rgb levels for green and set alphachannel to 0;
    selectedR = 105;
    selectedG = 105;
    selectedB = 105;
    if (r >= selectedR && g >= selectedG && b >= selectedB) {
        imgData.data[i + 3] = 0;
    }
}

context.putImageData(imgData, 0, 0);

那仍然不完美,因为有阴影,但它能用。

result


谢谢你的回答,但我已经能够实现这个结果了。我没有在这里包含那段代码,因为它仍然无法从图像中排除阴影。是否有可能优化这个结果,以便只获取手部而不包括阴影? - Developer
为了消除阴影,回到使用绿屏,然后将每个像素从RGB转换为HSV或HSL(通常可用的代码)。然后使用“色调”值查找色调为120度(+/-一点,取决于您的色度掩模的确切颜色)的像素,并排除这些像素。 - Alnitak
(阴影应该调整L或V,但不影响H) - Alnitak
1
你永远无法通过不良的图像源获得良好的结果(戴上我的视频专业帽子):确保您的图像源被正确照明,清晰,并且您已经将手臂下面的部分置于光线下(这将消除阴影)。对于后者,请使用带有白色漫反射器和光源的亚克力。请确保使用手动曝光(自动将尝试将白色降至80%灰度)。所有光源都需要具有相同的色彩平衡。如果可以使用建议的环境,我不建议使用绿屏,因为会出现颜色污染/偏色。HSL = 更好但昂贵。 - user1693593

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接