Usage¶
Installation and setup¶
To use conseal, first install it using pip:
$ pip3 install conseal
Import the package with
>>> import conseal as cl
For steganography on pixels, load the cover image using pillow and numpy.
>>> import numpy as np
>>> from PIL import Image
>>> x0 = np.array(Image.open("cover.png"))
After modification, save the stego image
>>> Image.fromarray(x1).save("stego.png")
For JPEG steganography, load the DCT coefficients using our sister project jpeglib.
>>> import jpeglib
>>> jpeg0 = jpeglib.read_dct("cover.jpeg")
>>> im0 = jpeglib.read_spatial( # decompressed
... "cover.jpeg",
... jpeglib.JCS_GRAYSCALE)
After copying jpeg0 to jpeg1 and modifying jpeg
After modifying jpeg0.Y, write the image as follows.
>>> jpeg1.write_dct("stego.jpeg")
Note
conseal expects the DCT coefficients in 4D shape [num_vertical_blocks, num_horizontal_blocks, 8, 8].
If you use to 2D DCT representation (as used by jpegio, for instance),
you have to convert it to 4D and back as follows.
>>> y_4d = cl.tools.jpegio_to_jpeglib(y_2d)
>>> y_2d = cl.tools.jpeglib_to_jpegio(y_4d)
The conseal API provides methods on different levels of abstraction.
Lower level API calls provide more control, but its use requires more code.
In any case, please make yourself familiar with the codebase before using it!
At high-level API¶
Using the high-level API, you can obtain the stego image from a cover image with a single function call.
>>> jpeg1.Y = cl.uerd.simulate_single_channel(
... y0=jpeg0.Y, # quantized cover DCT
... qt=jpeg0.qt[0], # quantization table
... alpha=0.4, # embedding rate
... seed=12345) # seed for PRNG
At mid-level API¶
Mid-level API exposes the separation principle. It allows user to separately calculate the distortion, and perform the simulation or coding.
>>> rho_p1, rho_m1 = cl.uerd.compute_cost_adjusted(
... y0=jpeg0.Y, # DCT
... qt=jpeg0.qt[0]) # QT
>>> jpeg1.Y += cl.simulate.ternary(
... rhos=(rho_p1, rho_m1), # costs of +1 and -1 changes
... alpha=0.4, # embedding rate
... n=jpeg0.Y.size, # cover size
... seed=12345) # seed for PRNG
Notice that unlike the high-level API, the mid-level and low-level API return only the steganography noise, which is to be added to the cover.
At low-level API¶
The low-level API allows accessing the raw costs (without wet cost modification), as well as the probabilities and simulation.
>>> rho = cl.uerd._costmap.compute_cost(
... y0=jpeg0.Y, # DCT
... qt=jpeg0.qt[0]) # QT
>>> # ... (sanitize rho, create rho_p1 and rho_m1)
>>> (p_p1, p_m1), lbda = cl.simulate._ternary.probability(
... rhos=(rho_p1, rho_m1), # embedding costs
... alpha=0.4, # embedding rate
... n=jpeg0.Y.size) # cover size
>>> jpeg1.Y += cl.simulate._ternary.simulate(
... ps=(p_p1, p_m1),
... seed=12345) # seed for PRNG
The low-level API gives access to the lbda parameter, which is used
to estimate the average payload embedded into the image
as well as the probabilities and simulation.
>>> alpha_hat = cl.simulate._ternary.average_payload(
... lbda=lbda, # lambda (optimized)
... rhos=(rho_p1, rho_m1)) # cost of +1 and -1 changes
Some embedding methods such as nsF5 and LSB have a low-level interface to get probabilities directly
>>> (p_p1, p_m1), _ = cl.nsF5._costmap.probability(
... y0=im_dct.Y, # DCT
... alpha=0.4) # alpha
>>> im_dct.Y += cl.simulate._ternary.simulate(
... ps=(p_p1, p_m1), # probability of change
... seed=12345) # seed for PRNG
>>> (p_p1, p_m1), _ = cl.lsb._costmap.probability(
... x0, # pixels
... alpha=0.4) # embedding rate
>>> stego_spatial = cover_spatial + cl.simulate._ternary.simulate(
... ps=(p_p1, p_m1), # probability of change
... seed=12345) # seed for PRNG