如何在RGB图像上实现DCT隐写?解决单通道编码变色问题
解决DCT隐写单通道编码导致的图像变色问题
你遇到的图像变色问题本质是仅修改单一RGB通道的DCT系数打破了三通道的原始色彩平衡——蓝色通道的像素值被修改后,与未改动的红、绿通道叠加,自然会出现偏色。完全可以通过全通道编码来解决这个问题,甚至能同时提升隐写的鲁棒性。
可行的解决方案
针对你的需求,有两种主流思路:
1. 三通道分别编码完整消息(推荐,贴合你参考的RGB压缩方案)
参考JPEG等RGB图像压缩的思路,对红、绿、蓝三个通道单独执行DCT、量化和隐写操作,每个通道都存储完整的秘密消息。这样三个通道的修改量均衡,视觉上几乎不会出现偏色;同时,即使其中一个通道被轻微损坏,另外两个通道仍能恢复消息,提升了隐写的鲁棒性。
2. 拆分消息比特到三个通道
将秘密消息的比特序列均匀分配到三个通道(比如每3个比特分别放到蓝、绿、红通道),每个通道仅修改1/3的比特数,单个通道的像素变化更小,变色更不明显,同时还能提升隐写的容量。
下面重点给出第一种方案的代码修改,因为它更符合你提到的"RGB通道分别处理"的思路,且实现起来更直观。
代码修改实现
首先,我们需要把原代码中嵌套的enc函数提取为类的独立成员函数,方便复用在三个通道上:
1. 重构编码函数
import cv2 import numpy as np import itertools class DCTSteganography: def __init__(self): # 标准JPEG亮度量化表 self.quant = np.array([[16,11,10,16,24,40,51,61], [12,12,14,19,26,58,60,55], [14,13,16,24,40,57,69,56], [14,17,22,29,51,87,80,62], [18,22,37,56,68,109,103,77], [24,35,55,64,81,104,113,92], [49,64,78,87,103,121,120,101], [72,92,95,98,112,100,103,99]]) self.message = "" self.bitMess = [] def toBits(self): bits = [] for char in self.message: binval = bin(ord(char))[2:].rjust(8,'0') bits.append(binval) self.numBits = bin(len(bits))[2:].rjust(8,'0') self.bitMess = bits return bits def chunks(self, l, n): m = int(n) for i in range(0, len(l), m): yield l[i:i + m] def addPadd(self, img, row, col): img = cv2.resize(img,(col+(8-col%8), row+(8-row%8))) return img # 重构为独立的通道编码函数,可复用在RGB任意通道 def encode_channel(self, channel, row, col): channel = np.float32(channel) # 拆分为8x8块 imgBlocks = [np.round(channel[j:j+8, i:i+8]-128) for (j,i) in itertools.product(range(0,row,8), range(0,col,8))] # DCT变换 dctBlocks = [np.round(cv2.dct(img_Block)) for img_Block in imgBlocks] # 量化 quantizedDCT = [np.round(dct_Block/self.quant) for dct_Block in dctBlocks] messIndex = 0 letterIndex = 0 for quantizedBlock in quantizedDCT: if messIndex >= len(self.bitMess): break # 修改DC系数的LSB DC = quantizedBlock[0][0] DC = np.uint8(DC) DC = np.unpackbits(DC) DC[7] = self.bitMess[messIndex][letterIndex] DC = np.packbits(DC) DC = np.float32(DC) DC = DC - 255 quantizedBlock[0][0] = DC letterIndex += 1 if letterIndex == 8: letterIndex = 0 messIndex += 1 # 反量化+逆DCT sImgBlocks = [quantizedBlock * self.quant + 128 for quantizedBlock in quantizedDCT] sImgDCT = [cv2.idct(B) + 128 for B in sImgBlocks] # 重组通道图像 sImg = [] for chunkRowBlocks in self.chunks(sImgDCT, col/8): for rowBlockNum in range(8): for block in chunkRowBlocks: sImg.extend(block[rowBlockNum]) sImg = np.array(sImg).reshape(row, col) sImg = np.uint8(sImg) return sImg def encode_image(self, img, secret_msg): self.message = str(len(secret_msg)) + '*' + secret_msg self.toBits() row, col = img.shape[:2] # 计算图像可承载的最大比特数(每个8x8块存1bit,三个通道) max_bits = (row//8)*(col//8)*3 if len(self.message)*8 > max_bits: print("Error: Message too large to encode in image") return False # 补全图像到8x8的倍数 if row%8 !=0 or col%8 !=0: img = self.addPadd(img, row, col) row, col = img.shape[:2] # 拆分RGB通道并分别编码 bImg, gImg, rImg = cv2.split(img) bImg_encoded = self.encode_channel(bImg, row, col) gImg_encoded = self.encode_channel(gImg, row, col) rImg_encoded = self.encode_channel(rImg, row, col) # 合并通道生成隐写图像 sImg = cv2.merge((bImg_encoded, gImg_encoded, rImg_encoded)) return sImg
2. 重构解码函数
解码时,你可以选择从任意一个通道提取消息(因为三个通道都存了完整消息),也可以从三个通道提取后验证一致性,提升可靠性:
def decode_channel(self, channel, row, col): channel = np.float32(channel) imgBlocks = [channel[j:j+8, i:i+8]-128 for (j,i) in itertools.product(range(0,row,8), range(0,col,8))] dctBlocks = [cv2.dct(img_Block) for img_Block in imgBlocks] quantizedDCT = [dct_Block/self.quant for dct_Block in dctBlocks] messSize = None messageBits = [] buff = 0 i = 0 for quantizedBlock in quantizedDCT: DC = quantizedBlock[0][0] DC = np.uint8(DC) DC = np.unpackbits(DC) # 提取DC系数的LSB并重组字符 buff += (DC[7] & 1) << (7 - i) i += 1 if i == 8: messageBits.append(chr(buff)) buff = 0 i = 0 # 解析消息长度标记 if messageBits[-1] == '*' and messSize is None: try: messSize = int(''.join(messageBits[:-1])) except: pass # 检查是否提取完完整消息 if messSize is not None and len(messageBits) - len(str(messSize)) -1 == messSize: return ''.join(messageBits)[len(str(messSize))+1:] return '' def decode_image(self, img): row, col = img.shape[:2] bImg, gImg, rImg = cv2.split(img) # 从三个通道分别解码 msg_from_blue = self.decode_channel(bImg, row, col) msg_from_green = self.decode_channel(gImg, row, col) msg_from_red = self.decode_channel(rImg, row, col) # 优先返回三个通道一致的结果,提升可靠性 if msg_from_blue == msg_from_green == msg_from_red and msg_from_blue != '': return msg_from_blue # 若部分通道损坏,返回非空的有效结果 elif msg_from_blue != '': return msg_from_blue elif msg_from_green != '': return msg_from_green else: return msg_from_red
关键说明
- 为什么能避免变色?:三个通道都进行了相同程度的DCT系数修改,RGB三通道的色彩比例保持了相对平衡,视觉上几乎无法察觉变化,彻底解决了单通道修改导致的偏色问题。
- 原代码问题修正:原
encode_image函数开头直接打印错误返回False是逻辑错误,补充了消息容量判断逻辑,确保只有当消息能被完全承载时才执行编码。 - 鲁棒性提升:三通道存储相同消息后,即使图像被轻微压缩、滤波,只要至少一个通道的消息未被破坏,就能正确解码。
内容的提问来源于stack exchange,提问作者Ameer Khan Ashraf




