You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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

关键说明

  1. 为什么能避免变色?:三个通道都进行了相同程度的DCT系数修改,RGB三通道的色彩比例保持了相对平衡,视觉上几乎无法察觉变化,彻底解决了单通道修改导致的偏色问题。
  2. 原代码问题修正:原encode_image函数开头直接打印错误返回False是逻辑错误,补充了消息容量判断逻辑,确保只有当消息能被完全承载时才执行编码。
  3. 鲁棒性提升:三通道存储相同消息后,即使图像被轻微压缩、滤波,只要至少一个通道的消息未被破坏,就能正确解码。

内容的提问来源于stack exchange,提问作者Ameer Khan Ashraf

火山引擎 最新活动