使用按位运算符修改图像像素 (JSFeat)
Modifying image pixels using bitwise operators (JSFeat)
我正在使用JSFeat Computer Vision Library and am trying to convert an image to greyscale. The function jsfeat.imgproc.grayscale
outputs to a matrix (img_u8 below), where each element is an integer between 0 and 255. I was unsure how to apply this matrix to the original image so I went looking through their example at 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 指令同时对四个字节进行操作,因此在许多情况下可以提高整体性能。
速成班
32 位值通常表示为十六进制,如下所示:
0x00000000
and 表示从右边的最低有效位 0 开始到左边的最高有效位 31 的等价位。 A位当然只能是on/set/1或off/unset/0。 4位是一个半字节,2个半字节是一个字节。十六进制值将每个半字节作为一个数字,所以这里有 8 个半字节 = 4 个字节或 32 位。在十进制表示法中,前导 0 对值没有影响,即 0xff
与 0x000000ff
相同(0x
前缀对值也没有影响;它只是传统的十六进制数的 C 表示法,后来被大多数其他通用语言采用)。
操作数
您可以直接对这些值进行位移和执行逻辑运算,例如 AND、OR、NOT、XOR(在汇编语言中,您将从 pointer/address 中获取值并将其加载到注册表中,然后对该注册表执行这些操作)。
那么会发生什么:
<<
表示向左移位。在这种情况下,值为:
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 之间。然而,由于今天大多数消费者 CPUs 使用 little-endian 字节顺序,颜色的每个组件在内存级别存储为 ABGR 而不是 RGBA 的 32 位值。
当然,我们通常使用诸如 JavaScript 之类的高级语言对此进行抽象,但由于我们现在通过 类型化数组 直接处理内存字节我们也必须考虑这方面,以及与注册表宽度(此处为 32 位)相关的问题。
所以这里我们尝试将 alpha 通道设置为 255(完全不透明),然后将其移动 24 位,使其处于正确的位置:
0xff000000
0xAABBGGRR
(虽然,这是一个不必要的步骤,因为他们也可以直接将其设置为 0xff000000,这样会更快,但无论如何)。
接下来我们使用 OR (|
) 运算符结合位移。我们首先移动以获得正确位位置中的值,然后将其与现有值进行或运算。
如果设置了现有位或新位,则 OR 将设置位,否则它将保持为 0。F.ex 从现有值开始,现在保存 alpha 通道值:
0xff000000
然后我们想要合并当前以 32 位表示的值 0xcc(十进制为 204)的蓝色分量:
0x000000cc
所以在这种情况下我们需要先将它向左移动16位:
0x000000cc
<< 16 bits
0x00cc0000
当我们现在将该值与现有的 alpha 值进行 OR 运算时,我们得到:
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 通道不同,那么您当然需要读取相同的 alpha 分量值其他值的方式):
data_u32[i] = 0xff000000 | (pix << 16) | (pix << 8) | pix;
如前所述,使用整数、位和位运算符是一个广泛的话题,这只是触及表面,但希望足以让人们更清楚地了解在这种特殊情况下发生的事情。
我正在使用JSFeat Computer Vision Library and am trying to convert an image to greyscale. The function jsfeat.imgproc.grayscale
outputs to a matrix (img_u8 below), where each element is an integer between 0 and 255. I was unsure how to apply this matrix to the original image so I went looking through their example at 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 指令同时对四个字节进行操作,因此在许多情况下可以提高整体性能。
速成班
32 位值通常表示为十六进制,如下所示:
0x00000000
and 表示从右边的最低有效位 0 开始到左边的最高有效位 31 的等价位。 A位当然只能是on/set/1或off/unset/0。 4位是一个半字节,2个半字节是一个字节。十六进制值将每个半字节作为一个数字,所以这里有 8 个半字节 = 4 个字节或 32 位。在十进制表示法中,前导 0 对值没有影响,即 0xff
与 0x000000ff
相同(0x
前缀对值也没有影响;它只是传统的十六进制数的 C 表示法,后来被大多数其他通用语言采用)。
操作数
您可以直接对这些值进行位移和执行逻辑运算,例如 AND、OR、NOT、XOR(在汇编语言中,您将从 pointer/address 中获取值并将其加载到注册表中,然后对该注册表执行这些操作)。
那么会发生什么:
<<
表示向左移位。在这种情况下,值为:
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 之间。然而,由于今天大多数消费者 CPUs 使用 little-endian 字节顺序,颜色的每个组件在内存级别存储为 ABGR 而不是 RGBA 的 32 位值。
当然,我们通常使用诸如 JavaScript 之类的高级语言对此进行抽象,但由于我们现在通过 类型化数组 直接处理内存字节我们也必须考虑这方面,以及与注册表宽度(此处为 32 位)相关的问题。
所以这里我们尝试将 alpha 通道设置为 255(完全不透明),然后将其移动 24 位,使其处于正确的位置:
0xff000000
0xAABBGGRR
(虽然,这是一个不必要的步骤,因为他们也可以直接将其设置为 0xff000000,这样会更快,但无论如何)。
接下来我们使用 OR (|
) 运算符结合位移。我们首先移动以获得正确位位置中的值,然后将其与现有值进行或运算。
如果设置了现有位或新位,则 OR 将设置位,否则它将保持为 0。F.ex 从现有值开始,现在保存 alpha 通道值:
0xff000000
然后我们想要合并当前以 32 位表示的值 0xcc(十进制为 204)的蓝色分量:
0x000000cc
所以在这种情况下我们需要先将它向左移动16位:
0x000000cc
<< 16 bits
0x00cc0000
当我们现在将该值与现有的 alpha 值进行 OR 运算时,我们得到:
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 通道不同,那么您当然需要读取相同的 alpha 分量值其他值的方式):
data_u32[i] = 0xff000000 | (pix << 16) | (pix << 8) | pix;
如前所述,使用整数、位和位运算符是一个广泛的话题,这只是触及表面,但希望足以让人们更清楚地了解在这种特殊情况下发生的事情。