JPEG 隐写术
如果要将图像保存为 jpeg,则必须遵循 jpeg 编码过程。不幸的是,我读过的大多数论文都说不公正。完整流程如下(维基摘要 https://en.wikipedia.org/wiki/JPEG#Encoding of a 182 页规格书 https://www.w3.org/Graphics/JPEG/itu-t81.pdf):
- RGB 到 YCbCr 转换(可选),
- 色度通道的子采样(可选),
- 8x8 块分裂,
- 像素值重心调整,
- DCT,
- 基于压缩比/质量的量化,
- 以之字形排列系数,并且
- 熵编码;最常见的是霍夫曼编码和游程编码(RLE)。
其实有涉及更多细节 https://stackoverflow.com/questions/8560571/where-can-i-get-free-specifications-of-jpeg-jfif-exif-etc,例如标题、部分标记、如何存储 DC 和 AC 系数的细节等。然后,该标准仅松散地定义了一些方面,并且它们的实现在编解码器之间可能有所不同,例如子采样算法、量化表和熵编码。也就是说,大多数软件都遵循通用 JFIF 标准,并且可以被各种软件读取。如果您希望 jpeg 文件执行相同的操作,请准备好为编码器编写数百(到大约一千)行代码。您最好借用已经在互联网上发布的编码器,而不是编写自己的编码器。您可以首先查看libjpeg http://libjpeg.sourceforge.net/它是用 C 编写的,构成了许多其他 jpeg 编解码器的基础,它的C# 实现 http://bitmiracle.com/libjpeg/甚至是一个Java http://www.media.mit.edu/pia/Research/deepview/src/JpegEncoder.java受其启发的版本。
在一些伪代码中,编码/解码过程可以描述如下。
function saveToJpeg(pixels, fileout) {
// pixels is a 2D or 3D array containing your raw pixel values
// blocks is a list of 2D arrays of size 8x8 each, containing pixel values
blocks = splitBlocks(pixels);
// a list similar to blocks, but for the DCT coefficients
coeffs = dct(blocks);
saveCoefficients(coeffs, fileout);
}
function loadJpeg(filein) {
coeffs = readCoefficients(filein);
blocks = idct(coeffs);
pixels = combineBlocks(blocks);
return pixels;
}
对于隐写术,您可以按如下方式修改它。
function embedSecretToJpeg(pixels, secret, fileout) {
blocks = splitBlocks(pixels);
coeffs = dct(blocks);
modified_coeffs = embedSecret(coeffs, secret);
saveCoefficients(modified_coeffs, fileout);
}
function extractSecretFromJpeg(filein) {
coeffs = readCoefficients(filein);
secret = extractSecret(coeffs);
return secret;
}
如果您的封面图像已经是 jpeg 格式,则无需使用解码器将其加载到像素,然后将其传递给编码器来嵌入您的消息。你可以这样做。
function embedSecretToJpeg(pixels, secret, filein, fileout) {
coeffs = readCoefficients(filein);
modified_coeffs = embedSecret(coeffs, secret);
saveCoefficients(modified_coeffs, fileout);
}
就您的问题而言,编码器/解码器应该处理 1、2、3 和 5,除非您自己编写一个。
问题1:通常,您希望用必要的行/列数填充图像,以便宽度和高度都可以被 8 整除。在内部,编码器将跟踪填充的行/列,以便解码器将丢弃它们重建后。这些虚拟行/列的像素值的选择取决于您,但建议您不要使用常量值,因为这会导致振铃伪影 https://en.wikipedia.org/wiki/Ringing_artifacts这与方波的傅里叶变换是 sinc 函数这一事实有关。
问题2:虽然您只修改几个块,但编码过程要求您将它们全部转换,以便将它们存储到文件中。
问题3:您必须量化浮点 DCT 系数,因为这就是无损存储到文件中的内容。您可以在量化步骤后将它们修改为您喜欢的内容。
问题4:没有人阻止您修改任何系数,但您必须记住每个系数都会影响块中的所有 64 个像素。 DC系数和低频交流电 http://image.slidesharecdn.com/digitalimageprotectionusingadaptivewatermarkingtechniques-140503113049-phpapp02/95/digital-image-protection-using-adaptive-watermarking-techniques-10-638.jpg?cb=1399119672它们会带来最大的扭曲,因此您可能需要远离它们。更具体地说,由于 DC 系数的存储方式,修改 DC 系数会将失真传播到所有后续块。
由于大多数高频系数为 0,因此可以使用 RLE 对其进行有效压缩。修改 0 系数可能会将其翻转为 1(如果您正在进行基本的 LSB 替换),这会破坏这种有效的压缩。
最后,一些算法将其秘密存储在任何非零系数中,并会跳过任何 0。但是,如果您尝试修改 1,它可能会翻转为 0,并且在提取过程中您会盲目地跳过读取它。因此,此类算法不会接近任何值为 1 或 0 的系数。
问题5:在解码时,只需将系数乘以相应的量化表值即可。例如,DC 系数为 309.443,量化后可得出round(309.443 / 16) = 19
。舍入位是这里的有损部分,它不允许您重建 309.433。所以反过来就是简单的19 * 16 = 304
.
DCT 在隐写术中的其他用途
频率变换(例如 DCT 和 DWT)可用于隐写术,将秘密嵌入到频域中,但不一定将隐写图像存储为 jpeg。这个过程是像素->DCT->系数->修改系数->IDCT->像素,这就是你发送给接收器的内容。因此,格式的选择在这里很重要。如果您决定将像素保存为 jpeg,那么 DCT 系数中的秘密可能会受到干扰 https://stackoverflow.com/questions/28190487/cant-extract-embedded-stego-message-after-compression-is-applied-to-image通过 jpeg 编码的另一层量化。