2

我正在使用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);

提前致谢!

4

1 回答 1

8

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

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

速成课程

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

0x00000000

并且表示从右侧的最低有效位 0 到左侧的最高有效位 31 的等效位。位当然只能是 on/set/1 或 off/unset/0。4位是一个半字节,2个半字节是一个字节。十六进制值将每个半字节作为一个数字,所以这里有 8 个半字节 = 4 个字节或 32 位。与十进制表示法一样,前导 0 对值没有影响,即0xff0x000000ff(0x前缀对值也没有影响;它只是十六进制数字的传统 C 表示法,然后被大多数其他常见语言取代)。

操作数

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

所以会发生这样的事情:

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

0xff

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

0b11111111

这与以下内容相同:

0x000000ff

或二进制(不幸的是,我们不能在 JavaScript 中原生地表示位表示实际上,0b在 ES6 中有 -prefix):

0b00000000 00000000 00000000 11111111

然后位移到左 24 位位置,得到新值:

0b00000000 00000000 00000000 11111111
                         << 24 bit positions =
0b11111111 00000000 00000000 00000000

或者

0xff000000

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

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

当然,我们通常会在诸如 JavaScript 之类的高级语言中对此进行抽象,但由于我们现在通过类型化数组直接处理内存字节,因此我们也必须考虑这方面,以及与注册表宽度(此处为 32-位)。

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

0xff000000
0xAABBGGRR

(虽然,这是一个不必要的步骤,因为他们也可以直接将其设置为 0xff000000,这样会更快,但无论如何)。

接下来我们将 OR ( |) 运算符与位移结合使用。我们首先移位以获取正确位位置的值,然后将其与现有值进行 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'ed 顺序无关紧要)。

所以这条线确实如此,让我们说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

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

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

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

如上所述,使用整数、位和位运算符是一个广泛的话题,这只是触及表面,但希望足以让人们更清楚地了解在这种特殊情况下发生了什么。

于 2016-06-02T07:08:09.630 回答