已解决:从线性数组中获取相邻像素(如 context.getImageData 中)

Posted

技术标签:

【中文标题】已解决:从线性数组中获取相邻像素(如 context.getImageData 中)【英文标题】:SOLVED: get neighboring pixels from linear array (as in context.getImageData) 【发布时间】:2022-01-10 05:18:56 【问题描述】:

我有一张图片,中间有一个红色的实心圆圈。我想读取该图像,分析像素,然后仅使用圆圈的轮廓将其写回画布。所以我正在检查像素是否被红色通道值为 255 的像素包围。如果是,我将其设为透明。如果不是,我将像素着色为青色。

我使用this post,作为编写脚本的参考。

我做错了什么。它不是用青色勾勒圆圈,并使中心透明,而是将所有白色像素着色为青色。

非常感谢您的帮助!

代码:

<img   id="input" src="img/input.png" >
<canvas  id="myCanvas" style="border:1px solid #d3d3d3;" ></canvas>
let neighbors = []

  function isSurrounded(index, imageData, data) 
    neighbors = [] // clear array
    neighbors[0] = data[index - imageData.width * 4 - 4] // Upper left
    neighbors[1] = data[index - imageData.width * 4]     // Upper middle
    neighbors[2] = data[index - imageData.width * 4 + 4] // Upper right
    neighbors[3] = data[index - 4] // left
    neighbors[4] = data[index + 4] // right
    neighbors[5] = data[index + imageData.width * 4 - 4] // Lower left
    neighbors[6] = data[index + imageData.width * 4]     // lower middle
    neighbors[7] = data[index + imageData.width * 4 + 4] // Lower right

    // check red channel
    for (let i = 0; i < neighbors.length; i++) 
      let redPixel = neighbors[i]
      if (redPixel !== 255) 
        return false
      
    
    return true
  

  let img = document.getElementById("input")
  img.onload = function () 
    let c = document.getElementById("myCanvas")
    let ctx = c.getContext("2d")
    ctx.drawImage(img, 0, 0)
    let imgData = ctx.getImageData(0, 0, c.width, c.height)
    let pxl = imgData.data
    for (let i = 0; i < pxl.length; i += 4) 
      if (isSurrounded(i, imgData, pxl)) 
        pxl[i] = 0
        pxl[i + 1] = 255
        pxl[i + 2] = 255
        pxl[i + 3] = 255
       else 
        pxl[i + 3] = 0
      
    
    ctx.putImageData(imgData, 0, 0)
  

【问题讨论】:

【参考方案1】:

也许另一种方法会奏效。我没有为此使用实际图像,以免处理安全问题,但该过程应该仍然有效。

在处理img.data 时,我喜欢做的第一件事是创建一个数组,为每个像素提供自己的数组;一个二维数组。即 [[0, 0, 0, 0,], [255, 0, 255, 0]...]。就我个人而言,它更容易处理。

  let imgData = ctx.getImageData(0, 0, c.width, c.height);
  let data = imgData.data.reduce(
    (pixel, key, index) =>
      (index % 4 == 0
        ? pixel.push([key])
        : pixel[pixel.length - 1].push(key)) && pixel,
    []
  );

无论您如何处理data 数组(1d 或 2d),您都需要检查左右上下的像素的透明度是否为 0。现在,如果您使用的图像没有没有透明度,那么你需要纠正它。画布在您上色之前自动透明。

for (let i = 0; i < data.length; i++) 
    if (data[i][0] >= 250 && data[i + 1][3] === 0) 
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;

虽然我用纯红色为圆圈着色,但在靠近边缘的情况下,r 的值不会是 255,但可能会略低。我也想看看右边的像素是否透明。如果是这样,那就意味着它必须在圆的边缘。对所有面都这样做,如果为真,我将像素设置为蓝色。

完成后,我可以遍历数组并将所有剩余的红色像素更改为透明。

data.forEach((px) => 
    if (px[0] >= 250) 
      px[0] = 0;
      px[1] = 0;
      px[2] = 0;
      px[3] = 0;
    
  );

现在我们需要创建一个新的图像数据并将我们的新数组设置为它的数据,然后再绘制它。我在这里使用flat(),因为我的数据数组是二维的,为此我们需要它是一维的。

  let newImage = ctx.createImageData(170, 170);
  newImage.data.set(data.flat());
  ctx.putImageData(newImage, 0, 0);

现在你应该有一个轮廓的圆圈

let c = document.getElementById("myCanvas");
let ctx = c.getContext("2d");

function drawCircle() 
  ctx.fillStyle = "red";
  ctx.arc(c.width / 2, c.height / 2, 70, 0, Math.PI * 2);
  ctx.fill();

drawCircle();

window.onload = function () 
  let imgData = ctx.getImageData(0, 0, c.width, c.height);
  let data = imgData.data.reduce(
    (pixel, key, index) =>
      (index % 4 == 0
        ? pixel.push([key])
        : pixel[pixel.length - 1].push(key)) && pixel,
    []
  );
  for (let i = 0; i < data.length; i++) 
    if (data[i][0] >= 250 && data[i + 1][3] === 0) 
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
    
    if (data[i][0] >= 250 && data[i - 1][3] === 0) 
      //  console.log(data[i-1])
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
    
    if (data[i][0] >= 250 && data[i - 170][3] === 0) 
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
    
    if (data[i][0] >= 250 && data[i + 170][3] === 0) 
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
    
  
  data.forEach((px) => 
    if (px[0] >= 250) 
      px[0] = 0;
      px[1] = 0;
      px[2] = 0;
      px[3] = 0;
    
  );
  let newImage = ctx.createImageData(170, 170);
  newImage.data.set(data.flat());
  ctx.putImageData(newImage, 0, 0);
;
&lt;canvas height="170" id="myCanvas" style="border:1px solid #d3d3d3;" width="170"&gt;&lt;/canvas&gt;

【讨论】:

非常感谢您的代码和详尽的解释。当我将它与我的图像一起使用时,它不断抛出错误,例如,data[i - 170][3] 未定义。我发现修复它并不像在循环顶部设置i=171 那样简单。我在每个部分周围放置了 try-catch 块,但结果圆圈缺少像素。你知道如何解决吗? 我可能是因为你的圈子走到了画布的边缘。在这种情况下,第一行像素之前或最后一行之后没有像素。因此,如果参考中的像素位于顶行,则尝试从中减去 170 将是未定义的,因为那里不存在像素。您需要在if 语句中添加一个条件,以首先检查它是否存在if (data[i - 170] &amp;&amp;...)。如果这不是导致它的原因,请告诉我。 嘿@Justin,只是想让你知道我不必这样做data[i - 170]。我的形象错了。我忘了让圆圈外的区域透明。一旦我解决了这个问题,它就起作用了。再次 - 非常感谢! :) ?

以上是关于已解决:从线性数组中获取相邻像素(如 context.getImageData 中)的主要内容,如果未能解决你的问题,请参考以下文章

用于图像中的超像素的相邻和非相邻超像素

2.6线性滤波器

是否有复制存储在二维数组中的相邻像素值的算法?

从 YUV 字节数组中获取像素矩阵

基于接缝裁剪的图像压缩 算法导论

Java - 从图像中获取像素数组