我正在使用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);
提前致谢!
答案 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;
使用整数,位和位运算符是一个广泛的主题,这只是表面上的划分,但希望足以使这个特定情况下发生的事情变得更清楚。