我正在使用JSFeat Computer Vision Library,并尝试将图像转换为灰度。函数jsfeat.imgproc.grayscale
输出到一个矩阵(下面的img_u8),其中每个元素都是0到255之间的整数。我不确定如何将此矩阵应用于原始图像,因此我在https://inspirit.github.io/jsfeat/sample_grayscale.htm处查看了它们的示例。
下面是我的代码将图像转换为灰度。我采用了他们的方法来更新原始图像中的像素,但是我不明白它是如何工作的。
/**
* I understand this stuff
*/
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let img = document.getElementById('img-in');
ctx.drawImage(img, 0, 0, img.width, img.height);
let imageData = ctx.getImageData(0, 0, img.width, img.height);
let img_u8 = new jsfeat.matrix_t(img.width, img.height, jsfeat.U8C1_t);
jsfeat.imgproc.grayscale(imageData.data, img.width, img.height, img_u8);
let data_u32 = new Uint32Array(imageData.data.buffer);
let i = img_u8.cols*img_u8.rows, pix = 0;
/**
* Their logic to update the pixel values of the original image
* I need help understanding how the following works
*/
let alpha = (0xff << 24);
while(--i >= 0) {
pix = img_u8.data[i];
data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix;
}
/**
* I understand this stuff
*/
context.putImageData(imageData, 0, 0);
提前致谢!
最佳答案
这是一个广泛的话题,但是我将尝试粗略地介绍基础知识,以了解此处发生的情况。
众所周知,它使用的是32位整数值,这意味着您可以使用更少的CPU指令同时对四个字节进行操作,因此在许多情况下可以提高整体性能。
速成类(class)
通常将32位值表示为十六进制,如下所示:
0x00000000
代表从右边的最低有效位0到左边的最高有效位31开始的等价位。当然,位只能是on / set / 1或off / unset / 0。 4位是半字节,2个半字节是一个字节。十六进制值的每个半字节都为一位,因此这里有8个半字节= 4字节或32位。与十进制表示法一样,前导0对值没有影响,即0xff
与0x000000ff
相同(0x
前缀也对值无效;这只是十六进制数字的传统C表示法,后来被大多数人接管了其他通用语言)。操作数
您可以对这些值进行位移位并直接执行诸如AND,OR,NOT,XOR之类的逻辑运算(用汇编语言,您将从指针/地址中获取值并将其加载到注册表中,然后在该注册表上执行这些操作)。
那么会发生什么:
<<
表示向左移位。在这种情况下,值为:0xff
或以二进制(位)表示(半字节0xf = 1111):0b11111111
这与以下内容相同:0x000000ff
或二进制格式(不幸的是,实际上我们无法在JavaScript中原生表示位表示,ES6中存在0b
-prefix):0b00000000 00000000 00000000 11111111
然后向左移24位,得到新值:0b00000000 00000000 00000000 11111111
<< 24 bit positions =
0b11111111 00000000 00000000 00000000
要么0xff000000
那么,为什么在这里需要这样做? 好,这是一个很好的问题!与 Canvas 相关的32位值表示RGBA,每个组件的值可以在0到255之间,或者以十六进制在0x00到0xff之间。但是,由于当今大多数消费类CPU使用低位字节序,因此每种颜色的分量都以ABGR而不是RGBA的32位值存储在内存级别。
通常,我们通常使用JavaScript之类的高级语言对此进行抽象,但是由于我们现在通过类型化数组直接处理内存字节,因此我们也必须考虑这一方面,并考虑注册表宽度(此处为32-位)。
因此,在这里我们尝试将Alpha通道设置为255(完全不透明),然后将其移位24位,使其处于正确的位置:
0xff000000
0xAABBGGRR
(不过,这在这里是不必要的步骤,因为他们也可以直接将其设置为0xff000000,这样会更快,但是可以)。接下来,我们将OR(
|
)运算符与移位结合使用。我们首先进行移位以使该值位于正确的位位置,然后将其与现有值进行“或”运算。如果设置了现有位或新位,则OR会设置一个位,否则它将保持为0。F.ex从现有值开始,现在保留alpha通道值:
0xff000000
然后,我们希望将蓝色值(例如0xcc(十进制为204))组合起来,当前以32位表示为:0x000000cc
因此,在这种情况下,我们需要先将其左移16位:0x000000cc
<< 16 bits
0x00cc0000
现在,当我们将该值与现有的alpha值进行或运算时,将得到: 0xff000000
OR 0x00cc0000
= 0xffcc0000
由于目标全是0位,因此只设置了源(0xcc)的值,这就是我们想要的(我们可以使用AND删除不需要的位,但这又是一天了)。以此类推,对于绿色和红色成分(对它们进行“或”运算的顺序并不重要)。
所以这行就做,让我们说
pix = 0xcc
:data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix;
转换为:alpha = 0xff000000 Alpha
pix = 0x000000cc Red
pix << 8 = 0x0000cc00 Green
pix << 16 = 0x00cc0000 Blue
和OR'ed在一起将变成:value = 0xffcccccc
由于所有组件的值相同,因此我们具有灰度值。我们具有正确的字节顺序,并且可以使用单个操作(无论如何在JS中)将其写回到Uint32缓冲区。您可以通过使用硬编码值(而不是引用)来优化此行,因为我们知道它的作用(如果alpha通道发生变化,那么您当然需要以与其他值相同的方式读取alpha分量值):
data_u32[i] = 0xff000000 | (pix << 16) | (pix << 8) | pix;
如前所述,使用整数,位和位运算符是一个广泛的话题,但这只是表面问题,但希望足以使它更加清楚这种情况下的情况。
关于javascript - 使用按位运算符(JSFeat)修改图像像素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37583915/