prism-steganography
prism models
prism-steganography hides a recoverable 64-bit message inside a cover image imperceptibly β not a fixed watermark, but a full trained encoder/decoder pair. Built as two jointly-trained networks: a U-Net encoder that embeds the message as bounded, near-invisible pixel perturbations, and a CNN decoder that recovers it. A differentiable noise layer sits between them at train time (blur, sensor noise, JPEG-like compression, pixel dropout), so the decoder learns to recover the message even after the image is distorted β not just from a pristine copy.
Production-usable for short recoverable messages (up to 8 ASCII characters) where the message needs to survive real-world image handling, not just a clean file transfer.
ποΈ Model Details
| Architecture | U-Net encoder (message tiled + concatenated with image) + CNN decoder |
| Message capacity | 64 bits (8 bytes / 8 ASCII characters, UTF-8, null-padded) |
| Input | RGB image, 128x128 |
| Training data | PD12M, pxhere, cc0-textures, ambientcg (Apache/CC0-licensed) |
| Training | Mixed precision, differentiable noise layer (blur/noise/JPEG-approx/dropout), early stopping on validation bit-accuracy |
π Usage
Unlike a single-model checkpoint, this repo bundles two networks β an encoder and a decoder β trained together:
from huggingface_hub import hf_hub_download
import torch, importlib.util, json
model_file = hf_hub_download(repo_id="olaverse/prism-steganography", filename="model.py")
ckpt_file = hf_hub_download(repo_id="olaverse/prism-steganography", filename="pytorch_model.pt")
config_file = hf_hub_download(repo_id="olaverse/prism-steganography", filename="config.json")
spec = importlib.util.spec_from_file_location("model", model_file)
model_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(model_module)
config = json.load(open(config_file))
checkpoint = torch.load(ckpt_file, map_location="cpu")
encoder = model_module.StegEncoder(**config)
encoder.load_state_dict(checkpoint["encoder"])
encoder.eval()
decoder = model_module.StegDecoder(**config)
decoder.load_state_dict(checkpoint["decoder"])
decoder.eval()
Hides a 64-bit message (up to 8 ASCII characters, UTF-8 byte-encoded and null-padded) inside a cover image, recoverable even after blur, noise, JPEG re-compression, or pixel dropout.
# encoder(cover_image_tensor, message_bits_tensor) -> stego_image_tensor
# decoder(stego_image_tensor) -> recovered_bit_logits (threshold at 0)
Full example: hide and recover a message
from PIL import Image
import torchvision.transforms.functional as TF
MSG_BITS = config["msg_bits"] # 64
def text_to_bits(text: str, num_bits: int = MSG_BITS) -> torch.Tensor:
num_bytes = num_bits // 8
raw = text.encode("utf-8")[:num_bytes]
raw = raw + b"\x00" * (num_bytes - len(raw))
bits = []
for byte in raw:
bits.extend([(byte >> i) & 1 for i in range(7, -1, -1)])
return torch.tensor(bits, dtype=torch.float32)
def bits_to_text(bits: torch.Tensor) -> str:
bits = bits.round().long().tolist()
byte_vals = []
for i in range(0, len(bits), 8):
val = 0
for b in bits[i:i + 8]:
val = (val << 1) | b
byte_vals.append(val)
raw = bytes(byte_vals).rstrip(b"\x00")
return raw.decode("utf-8", errors="replace")
# load a cover image and resize to the model's trained resolution
img = Image.open("cover.jpg").convert("RGB").resize((128, 128))
cover = TF.to_tensor(img).unsqueeze(0)
# encode a message (truncated/padded to 8 bytes -- this model's fixed capacity)
message = "hi there"[: MSG_BITS // 8]
msg_bits = text_to_bits(message).unsqueeze(0)
with torch.no_grad():
stego = encoder(cover, msg_bits) # near-identical image with the message hidden
recovered_logits = decoder(stego) # decode straight from the stego image
recovered_bits = (recovered_logits > 0).float()
recovered_text = bits_to_text(recovered_bits[0])
print("Original message: ", message)
print("Recovered message:", recovered_text)
TF.to_pil_image(stego[0]).save("stego.jpg") # save the image with the hidden message
Output:
Original message: hi there
Recovered message: hi there
Message capacity is exactly msg_bits // 8 ASCII characters (8, for this model) β longer input is silently truncated by text_to_bits. To test robustness against real-world distortion (compression, re-uploading, etc.), pass stego through your own noise/degradation of choice before decoding β recovery accuracy will drop somewhat under distortion (see Benchmarks above), so consider adding error-correction coding on top of the raw bits for applications that need near-perfect reliability.
π Benchmarks
Multi-trial testing (20 random messages/distortion draws on a single cover image, not a scored benchmark against a standard dataset): clean recovery (no distortion) averaged 99.9% bit-accuracy with a 98.4% worst case β essentially flawless. After distortion (blur/noise/JPEG-approx/dropout, randomly sampled per trial), average bit-accuracy was 93.7%, with a worst-case single trial as low as 62.5% under a severe distortion draw β reliability depends meaningfully on distortion severity, not uniform across all conditions.
Known Limitations
- Message capacity is small β 64 bits (8 characters) by design for this release; longer messages require retraining with a larger bit budget.
- Worst-case robustness under severe distortion is meaningfully lower than the average β the 93.7% mean bit-accuracy under distortion can drop well below that (observed as low as 62.5% in testing) on particularly harsh distortion draws. No error-correction coding is applied on top of the raw bits in this release, so a real deployment wanting near-100% message reliability should add redundancy (e.g. a repetition or Hamming code) on top of the raw bit channel.
- Trained and tested at 128x128 resolution; not evaluated at other input sizes.
- Not evaluated against standard steganography benchmarks or attack suites.
Training data & licensing
Trained on PD12M (Spawning/PD12M, CDLA-Permissive-2.0), pxhere (nyuuzyou/pxhere, CC0), cc0-textures (nyuuzyou/cc0-textures, CC0), and ambientcg (nyuuzyou/ambientcg, CC0). Released under Apache-2.0.
Citation
@misc{prism-steganography,
title = {prism-steganography},
author = {Olaverse},
year = {2026},
url = {https://huggingface.co/olaverse/prism-steganography}
}
- Downloads last month
- 85

