将图像转换为灰度(感知图像哈希)

coverting Image to grayscale (Perceptual image hashes)

我正在研究感知图像哈希。首先,我缩小图像尺寸以去除高频。然后,我将图像缩小到 (8*8),这样总共有 64 个像素。我使用以下代码行。

private void button1_Click(object sender, EventArgs e)
{
    Image img = pictureBox1.Image;
    img = resizeImage(img, new Size(8,8));
    pictureBox2.Image = img;            
}

public static Image resizeImage(Image imgToResize, Size size)
{
    return (Image)(new Bitmap(imgToResize, size));
}

现在我想减少颜色,以便将微小的 (8x8) 图片转换为灰度,并希望将散列从 64 像素(64 红色、64 绿色和 64 蓝色)更改为 64 种总颜色。在这里,我卡住了。

如果您要 'perceptual' 哈希,并且只需要处理 64 个值,使用更高级的算法可能会很有趣。其背后的理论大致解释here.

灰色=(0.2126×红色2.2+0.7152×绿色2.2+0.0722×蓝色2.2)1/2.2

作为代码,这将变成

public static Byte GetGreyValue(Byte red, Byte green, Byte blue)
{
    Double redFactor = 0.2126d * Math.Pow(red, 2.2d);
    Double grnFactor = 0.7152d * Math.Pow(green, 2.2d);
    Double bluFactor = 0.0722d * Math.Pow(blue, 2.2d);
    Double grey = Math.Pow(redFactor + grnFactor + bluFactor, 1d / 2.2);
    return (Byte)Math.Max(0, Math.Min(255, Math.Round(grey, MidpointRounding.AwayFromZero)));
}

现在您可以遍历已调整大小的图像中的所有字节并将它们转换为灰色。使用 LockBits,这并不难。您只需复制字节,每四个(每个 ARGB 字节四边形)迭代它们,取出 RGB 组件,将它们放入 'make grey' 函数,将结果放入您从中获取的 RGB 点,然后编写完成后将字节数组返回到图像中。

public static Bitmap GetGreyImage(Image img, Int32 width, Int32 height)
{
    // get image data
    Bitmap b = new Bitmap(img, width, height);
    BitmapData sourceData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
    Int32 stride = sourceData.Stride;
    Byte[] data = new Byte[stride * b.Height];
    // Copy bytes from image into data array
    Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
    // iterate over array and convert to gray
    for (Int32 y = 0; y < height; y++)
    {
        Int32 offset = y * stride;
        for (Int32 x = 0; x < width; x++)
        {
            // "ARGB" is little-endian Int32, so the actual byte order is B,G,R,A
            Byte colB = data[offset + 0]; // B
            Byte colG = data[offset + 1]; // G
            Byte colR = data[offset + 2]; // R
            Int32 colA = data[offset + 3]; // A
            // Optional improvement: set pixels to black if color
            // is considered too transparent to matter.
            Byte grayValue = colA < 128 ? 0 : GetGreyValue(colR, colG, colB);
            data[offset + 0] = grayValue; // B
            data[offset + 1] = grayValue; // G
            data[offset + 2] = grayValue; // R
            data[offset + 3] = 0xFF; // A
            offset += 4;
        }
    }
    // Copy bytes from data array back into image
    Marshal.Copy(data, 0, sourceData.Scan0, data.Length);
    b.UnlockBits(sourceData);
    return b;
}

如果使用 "change from 64 red, 64 green, and 64 blue to 64 total colors" 你的意思是你想要一个 8 位(调色板)图像,其中每个字节是一个像素,那么你将不得不简单地将在那里生成的值保存在一个新字节中数组而不是将它们写回,然后用 Format8bppIndexed 格式创建一个新的 8x8 位图,在第二个 BitmapData 对象中打开它,然后将其写入其中。

请注意,8 位图像需要一个调色板,因此您需要检查图像的标准生成的 8 位调色板并将其更改为从 (0,0,0) 到淡入淡出(255,255,255) 带有 for 循环。