使用按位运算符修改图像像素(JSFeat)

时间:2016-06-02 06:08:56

标签: javascript canvas computer-vision bitwise-operators jsfeat

我正在使用JSFeat Computer Vision Library并尝试将图片转换为灰度图像。函数jsfeat.imgproc.grayscale输出到矩阵(下面是img_u8),其中每个元素都是0到255之间的整数。我不确定如何将这个矩阵应用于原始图像,所以我在{{3 }}。

以下是将图片转换为灰度的代码。我采用他们的方法更新原始图像中的像素,但我不明白它是如何工作的。

/**
 * 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);

提前致谢!

1 个答案:

答案 0 :(得分:5)

这是一个广泛的主题,但我会尝试大致涵盖基础知识,以了解这里发生的事情。

如我们所知,它使用32位整数值,这意味着您可以使用更少的CPU指令同时操作四个字节,因此在许多情况下可以提高整体性能。

速成课程

32位值通常标记为十六进制,如下所示:

0x00000000

表示从右边的位0开始到左边第31位的等效位。有点当然只能打开/关闭或关闭/取消设置。 4位是半字节,2个半字节是一个字节。十六进制值显示八位字节等于半字节,所以这里有8个半字节/八位字节= 4个字节或32位。不需要标注前缀为0的值,即值0xff与0x000000ff相同。

操作数

您可以直接对这些值进行位移和执行逻辑运算,例如AND,OR,NOT,XOR(使用汇编语言从指针/地址获取值并将其加载到注册表中,然后执行这些操作在该登记处)。

所以会发生这样的事情:

<<表示向左移位。在这种情况下,值为:

0xff

或二进制(位)表示(半字节0xf = 1111):

0b11111111

这与:

相同
0x000000ff

或二进制文件(遗憾的是,我们无法在JavaScript中原生地表示位代码,ES6中有0b - 前缀):

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都使用 little-endian 字节顺序,因此颜色的每个组件在内存级别存储为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来删除不需要的位,但是,这是另一天)。

绿色和红色组件等等(它们在OR中的顺序并不重要)。

所以这一行就行了,让我们说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&#39; ed将成为:

value     = 0xffcccccc

我们有一个灰色值,因为所有组件都具有相同的值。我们有正确的字节顺序,可以使用单个操作将其写回Uint32缓冲区(无论如何都是JS)。

你可以通过使用硬编码的alpha而不是参考来优化这一行,因为我们知道它的作用(如果alpha通道有所不同,那么当然你需要以同样的方式读取alpha分量值)其他值):

data_u32[i] = 0xff000000 | (pix << 16) | (pix << 8) | pix;

使用整数,位和位运算符是一个广泛的主题,这只是表面上的划分,但希望足以使这个特定情况下发生的事情变得更清楚。