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

Ran*_*y W 2 javascript canvas bitwise-operators computer-vision jsfeat

我正在使用JSFeat计算机视觉库,我正在尝试将图像转换为灰度.函数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);
Run Code Online (Sandbox Code Playgroud)

提前致谢!

小智 5

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

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

速成班

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

0x00000000
Run Code Online (Sandbox Code Playgroud)

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

操作数

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

那么会发生什么:

<<意味着向左移位.在这种情况下,值为:

0xff
Run Code Online (Sandbox Code Playgroud)

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

0b11111111
Run Code Online (Sandbox Code Playgroud)

这与:

0x000000ff
Run Code Online (Sandbox Code Playgroud)

或二进制(遗憾的是,我们无法在JavaScript中本地表示位表示实际上,0bES6中有-prefix):

0b00000000 00000000 00000000 11111111
Run Code Online (Sandbox Code Playgroud)

然后位移到左侧24位位置,产生新值:

0b00000000 00000000 00000000 11111111
                         << 24 bit positions =
0b11111111 00000000 00000000 00000000
Run Code Online (Sandbox Code Playgroud)

要么

0xff000000
Run Code Online (Sandbox Code Playgroud)

那为什么这里有必要呢?嗯,这是一个很好的问题!

与canvas相关的32位值表示RGBA,每个组件可以具有0到255之间的值,或者以十六进制表示0x00和0xff之间的值.但是,由于目前大多数消费类CPU都使用little-endian字节顺序,因此颜色的每个组件在内存级别存储为ABGR而不是RGBA用于32位值.

我们通常用JavaScript等高级语言来抽象,但是由于我们现在通过类型化数组直接处理内存字节,我们也必须考虑这个方面,并且与注册表宽度有关(这里是32-位).

所以这里我们尝试将alpha通道设置为255(完全不透明),然后将其移位24位,使其变为正确的位置:

0xff000000
0xAABBGGRR
Run Code Online (Sandbox Code Playgroud)

(虽然,这是一个不必要的步骤,因为他们可以直接将其设置为0xff000000,这将更快,但anyhoo).

接下来我们使用OR(|)运算符结合位移.我们先移位以获得正确位位置的值,然后将其移到现有值上.

如果设置了现有位或新位,OR将设置一个位,否则它将保持为0. F.ex以现有值开始,现在保持alpha通道值:

0xff000000
Run Code Online (Sandbox Code Playgroud)

然后,我们想要将值为0xcc(十进制为204)的蓝色分量组合在一起,当前以32位表示为:

0x000000cc
Run Code Online (Sandbox Code Playgroud)

所以我们需要在这种情况下首先将它向左移16位:

0x000000cc
     << 16 bits
0x00cc0000
Run Code Online (Sandbox Code Playgroud)

当我们现在将该值与现有的alpha值相加时,我们得到:

   0xff000000
OR 0x00cc0000
 = 0xffcc0000
Run Code Online (Sandbox Code Playgroud)

由于目的地全是0位,所以只设置源(0xcc)的值,这就是我们想要的(我们可以使用AND来删除不需要的位,但这是另一天).

对于绿色和红色成分等等(它们在OR中的顺序并不重要).

那么这条线就行了,让我们说pix = 0xcc:

data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix;
Run Code Online (Sandbox Code Playgroud)

这意味着:

alpha     = 0xff000000  Alpha
pix       = 0x000000cc  Red
pix <<  8 = 0x0000cc00  Green
pix << 16 = 0x00cc0000  Blue
Run Code Online (Sandbox Code Playgroud)

和OR'ed一起将成为:

value     = 0xffcccccc
Run Code Online (Sandbox Code Playgroud)

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

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

data_u32[i] = 0xff000000 | (pix << 16) | (pix << 8) | pix;
Run Code Online (Sandbox Code Playgroud)

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