有损压缩中的隐写术(JAVA)

7

我有一个用于在Java中对JPEG图像进行编码的方法。我将文本转换为二进制形式,并将其插入到每个像素的RGB的LSB(根据用户选择的1、2、3、4)中,从(0,0)到(宽度,高度)的每个像素。

outer:
    for(int i = 0; i < height; i++){
        for(int j = 0; j < width; j++){
            Color c = new Color(image.getRGB(j, i));                  

            int red = binaryToInteger(insertMessage(integerToBinary((int)(c.getRed())),numLSB));
            int green = binaryToInteger(insertMessage(integerToBinary((int)(c.getGreen())),numLSB));
            int blue = binaryToInteger(insertMessage(integerToBinary((int)(c.getBlue())),numLSB));

            Color newColor = new Color(red,green,blue);
            image.setRGB(j,i,newColor.getRGB());

        }
    }
    gui.appendStatus("Binarized message is: " + binarizedMessage);
    File output = new File(gui.getOutput()+".jpg");

    ImageIO.write(image, "png", output);

目前,我正在将其编写为png格式,并且它运行良好,但我希望将其制作为jpeg格式。我已经成功地获取了这些数据的png格式。但是,如预期的那样,jpeg格式失败了。

我能够解码图像中隐藏的位,并查看给定消息,只要选择了正确的LSB。

我目前正在阅读有关JPEG隐写术的内容,但还不清楚应该如何开始。我看到了算法,但没有帮助我。

我看到了一段没有任何主类的代码。

我需要在我的应用程序中调用它吗?修改它?我该如何解码?

这里是一个链接到代码,我看过了。

1个回答

10

为了实现更小的文件大小,JPEG使用有损压缩方法。不幸的是,这种方法直接影响(一些)像素的值,从而破坏了嵌入信息的方式。为避免此问题,您需要将文件保存在无损格式中,例如BMP或PNG。

JPEG隐写术有些复杂,但概念很简单。您需要编写一个JPEG编码器,或使用已经存在的编码器。您提供的代码确实是一个编码器,稍作修改后即可用于您的项目。

如果您想理解代码,可以阅读维基百科上关于JPEG编码的文章。我将简要总结其中的一些关键步骤:

  • 将图像拆分为8x8块。
  • 对每个块使用离散余弦变换(DCT)获得浮点DCT系数,并将其量化为整数。
  • 使用霍夫曼编码和游程编码将量化系数存储到文件中。

第二步中的量化是有损位,但之后的所有步骤都是无损的。因此,基本上是从第二步获取量化系数,使用隐写术算法修改它们并继续第三步。

现在来看看链接代码的实际修改。 Compress 方法是您需要调用的方法,以将RGB图像存储到文件中。它负责编写头数据和压缩系数。您只需要在 WriteCompressedData 方法中添加一些代码即可。现在做的事情是循环遍历每个8x8的图像块,应用DCT并量化系数,这些系数存储在 dctArray3 中。然后将此数据压缩并写入文件。这就是您需要介入的地方,即在调用 Huf.HuffmanBlockEncoder 之前修改 dctArray3

例如,假设您有一个字节数组作为您的秘密消息,称为message,并且您想要在特定系数的最低位中每个8x8块嵌入一位。

public void WriteCompressedData(BufferedOutputStream outStream, byte[] message) {
    byte currentByte;
    int nBytes = message.length;
    int iByte = 0;
    int iBit = 7;
    if (nBytes > 0) {
        currentByte = message[0];
    } else {
        currentByte = (byte) 0;
    }
    // Original method code up until the following line
    dctArray3 = dct.quantizeBlock(dctArray2, JpegObj.QtableNumber[comp]);
    // ******************** our stuff *******************
    if (iByte < nBytes) {
        int bit = (currentByte >> iBit) & 1;
        iBit--;
        if (iBit == -1) {
            iBit = 7;
            iByte++;
            if (iByte < nBytes) {
                currentByte = message[iByte];
            }
        }
        dctArray3[23] = (dctArray3[23] & 0xfffffffe) | bit;
    }
    // **************************************************
    Huf.HuffmanBlockEncoder(outStream, dctArray3, lastDCvalue[comp], JpegObj.DCtableNumber[comp], JpegObj.ACtableNumber[comp]);
    ...
}

解码是其反向过程,您需要使用适当的算法从DCT系数中读取并提取出您的秘密信息。为此,您需要一个jpeg解码器,因此我只是从F5隐写术项目中借用了相关文件。具体来说,您需要使用ortega文件夹中的文件,然后可以像这样使用。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import ortega.HuffmanDecode;

public class Extract {
    private static byte[] deZigZag = {
            0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31,
            40, 44, 53, 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61,
            35, 36, 48, 49, 57, 58, 62, 63 };

    private static int[] extract(InputStream fis, int flength) throws IOException {
        byte[] carrier = new byte[flength];
        fis.read(carrier);
        HuffmanDecode hd = new HuffmanDecode(carrier);
        int[] coeff = hd.decode();
        return coeff;
    }

    public static void main(String[] args) {
        // run with argument the stego jpeg filename
        try {
            File f = new File(args[0]);
            FileInputStream fis = new FileInputStream(f);
            int[] coeff = extract(fis, (int) f.length());

            int idx = deZigZag[23];
            // The coeff array has all of the DCT coefficients in one big
            // array, so that the first 64 elements are the coefficients 
            // from the first block, the next 64 from the second and so on.
            //
            // idx is the position of the embedding DCT coefficient.
            // You can start with that and extract its lsb, then increment
            // by 64 to extract the next bit from the next "block" and so on.
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这个周末我会稍后检查你的答案。谢谢你的回答 :) - user1730941
1
我纠正了编码序列逻辑中的一个错误(应该是 if (iBit == -1) 而不是 0),并添加了一个解码器示例。 - Reti43
我能否使用我的方法对图像进行编码,调用原始的JpegEncoder并让它写入图像。然后我再反转它的过程,最后应用我的解码过程。这可行吗? - user1730941
1
你说的用你的方法编码图像是什么意思?你是指将信息嵌入像素中,然后保存为 JPEG 格式吗? - Reti43
那是我之前想的,但我认为这不可能实现。我从未想过这会如此复杂。 - user1730941
1
正如之前所指出的,您无法将数据嵌入像素中,然后将其编码为jpeg。 Jpeg文件通过存储DCT系数来实现压缩。正如上面所演示的那样,这是一种无损过程。您必须使用文件格式提供的内容。要无损地存储像素,需要使用不同的格式。 - Reti43

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接