You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于PIL的灰度转彩色及彩色图像数据隐写技术问询

Let's tackle your two questions step by step, and refine your steganography code for color images since you've already got a grayscale version working:

1. Converting a grayscale image (derived from color) back to color with PIL

First, a critical note: You cannot fully recover the original color information from a grayscale image. Converting a color image to grayscale discards the distinct RGB channel values (they’re merged into a single luminance value), so there’s no way to reverse this process to get back the original colors.

That said, you can create pseudocolor images or replicate the grayscale value across RGB channels to get a "colorized" version (though it won’t match the original):

  • Replicate grayscale to RGB channels (produces a visually grayscale image stored as RGB):
    from PIL import Image
    
    # Load your grayscale image
    gray_img = Image.open("grayscale.png")
    # Merge the grayscale channel into RGB
    colorized_img = Image.merge("RGB", (gray_img, gray_img, gray_img))
    colorized_img.save("fake_color.png")
    
  • Apply a pseudocolor map (adds arbitrary colors for visual effect):
    from PIL import Image
    import numpy as np
    import matplotlib.pyplot as plt
    
    gray_img = Image.open("grayscale.png")
    gray_arr = np.array(gray_img)
    # Use a colormap (e.g., viridis, plasma) to add color
    pseudocolor_arr = plt.cm.viridis(gray_arr)
    # Convert back to a PIL image
    pseudocolor_img = Image.fromarray((pseudocolor_arr * 255).astype(np.uint8))
    pseudocolor_img.save("pseudocolor.png")
    

For steganography purposes, this approach is not ideal—it destroys the original image’s color integrity. You’re better off working directly with the original color image instead of converting to grayscale first.

2. Adapting steganography from grayscale to color images

Your two proposed approaches have clear tradeoffs; let’s break them down and fix your existing code:

Approach 1: Convert color to grayscale, steganograph, then convert back to color

  • Pros: Reuses your existing grayscale steganography code with minimal changes.
  • Cons: The resulting image will lose all original color information (it’ll be a grayscale image replicated across RGB channels), making the steganography obvious if the source image was supposed to be colorful. Only use this if you don’t care about preserving the original image’s appearance.

This is the far better option: modifying the least significant bit (LSB) of one color channel (e.g., red) leaves the image visually unchanged (humans can’t detect 1-bit changes in pixel values) while preserving the original color information.

Issues in your current code

Your existing code has a few critical flaws that make it unsuitable for discreet steganography:

  • You’re modifying pixel values directly (e.g., changing max_a to max_a+1) instead of using LSB, which creates visible artifacts.
  • You’re looping over a fixed 512x512 size regardless of the actual image dimensions, which can cause index errors.
  • Your payload calculation based on histogram max_value is flawed—each pixel can store 1 bit of data, so total capacity is width * height bits per channel.

Optimized channel-based steganography code

Here’s a revised version that uses LSB in the red channel (you can switch to green or blue with minor tweaks):

from PIL import Image
import glob
import os

def text_to_bits(text):
    """Convert plain text to a string of 8-bit binary characters, plus end marker"""
    bit_string = ''.join(format(ord(c), '08b') for c in text)
    return bit_string + '11111111'  # 8-bit end marker to signal end of data

def bits_to_text(bits):
    """Convert binary string back to plain text"""
    chars = []
    for i in range(0, len(bits), 8):
        byte = bits[i:i+8]
        chars.append(chr(int(byte, 2)))
    return ''.join(chars)

def hide_bits_in_channel(image_path, bits, channel=0):
    """Hide binary data in the specified color channel (0=Red, 1=Green, 2=Blue) using LSB"""
    img = Image.open(image_path).convert("RGB")
    pixels = img.load()
    width, height = img.size

    if len(bits) > width * height:
        raise ValueError("Data too large for this image")

    bit_index = 0
    for x in range(width):
        for y in range(height):
            if bit_index >= len(bits):
                break
            r, g, b = pixels[x, y]
            # Modify the LSB of the selected channel
            if channel == 0:
                new_r = (r & 0xFE) | int(bits[bit_index])
                pixels[x, y] = (new_r, g, b)
            elif channel == 1:
                new_g = (g & 0xFE) | int(bits[bit_index])
                pixels[x, y] = (r, new_g, b)
            elif channel == 2:
                new_b = (b & 0xFE) | int(bits[bit_index])
                pixels[x, y] = (r, g, new_b)
            bit_index += 1
    return img

def extract_bits_from_channel(image_path, channel=0):
    """Extract hidden binary data from the specified color channel"""
    img = Image.open(image_path).convert("RGB")
    pixels = img.load()
    width, height = img.size
    bits = []

    for x in range(width):
        for y in range(height):
            r, g, b = pixels[x, y]
            # Grab the LSB of the selected channel
            if channel == 0:
                bits.append(str(r & 1))
            elif channel == 1:
                bits.append(str(g & 1))
            elif channel == 2:
                bits.append(str(b & 1))
    return ''.join(bits)

# Example usage
message = "he23@" * 200
os.makedirs("output", exist_ok=True)

# Prepare data and split across images if needed
remaining_bits = text_to_bits(message)
image_list = glob.glob('*.png')
file_index = 0

for filename in image_list:
    if not remaining_bits:
        break
    img = Image.open(filename).convert("RGB")
    capacity = img.size[0] * img.size[1]
    
    # Split data into chunks that fit each image
    if len(remaining_bits) <= capacity:
        chunk = remaining_bits
        remaining_bits = ""
    else:
        chunk = remaining_bits[:capacity]
        remaining_bits = remaining_bits[capacity:]
    
    # Hide chunk and save output
    output_img = hide_bits_in_channel(filename, chunk)
    output_filename = f"output/filename{file_index}.png"
    output_img.save(output_filename)
    file_index += 1

# Extract and verify data
extracted_bits = ""
for filename in glob.glob('output/filename*.png'):
    extracted_bits += extract_bits_from_channel(filename)

# Find end marker and convert back to text
end_index = extracted_bits.find('11111111')
if end_index == -1:
    print("No hidden data found")
else:
    extracted_message = bits_to_text(extracted_bits[:end_index])
    print("Extracted message preview:", extracted_message[:100], "...")

Key improvements:

  • Uses undetectable LSB steganography that preserves the original image’s appearance.
  • Handles variable image sizes instead of hardcoding 512x512.
  • Includes an end marker to correctly identify the end of hidden data.
  • Supports splitting large messages across multiple images.

内容的提问来源于stack exchange,提问作者cantyousee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:55:12