Make me look good. Click on the cell below and press Ctrl+Enter.
from IPython.core.display import HTML
HTML(open('css/custom.css', 'r').read())
Example. Make a 101 by 101 pixel image of the maritime flag for Z, pictured below. Display your image.
# Yellow = R: 255, G: 255, B: 0
# Blue = R: 0, G: 0, B: 255
# Red = R: 255, G: 0, B: 0
import numpy as np
import matplotlib.pyplot as plt
# Initialize 3D array of zeros
M = np.zeros((101, 101, 3), dtype='uint8')
# Loop over every pixel
# i = row, j = column
for i in range(101):
for j in range(101):
if i <= (100 - j) and j >= i:
# Yellow
M[i, j, 0] = 255
M[i, j, 1] = 255
elif i > (100 - j) and j >= i:
# Blue
M[i, j, 2] = 255
elif i > j and i > (100 - j):
# Red
M[i, j, 0] = 255
# Display image
plt.imshow(M)
<matplotlib.image.AxesImage at 0x114191950>
Etymology of the word steganography:
Steganography is the practice of concealing messages or information within other nonsecret text or data.
Examples of steganography:
"When thou art come to Miletus, bid Aristagoras shave thy head, and look thereon."
Bit shifting is the act of moving the digits of a binary number to the left or right.
For example, starting with $23 = [00010111]_2$:
Note:
You can bit shift integers with NumPy using np.left_shift()
(documentation) and np.right_shift()
(documentation).
Example. Using np.left_shift()
and np.right_shift()
, left-shift and right-shift the integer 23 by 1 bit. Does what you get match with the above?
# Left-shift 23 by 1 bit
left_shift = np.left_shift(23, 1)
print(f"Left-shift 23 by 1 bit: {left_shift}")
# Right-shift 23 by 1 bit
right_shift = np.right_shift(23, 1)
print(f"Right-shift 23 by 1 bit: {right_shift}")
Left-shift 23 by 1 bit: 46 Right-shift 23 by 1 bit: 11
Write your notes here. Double-click to edit.
If we left-shift 23, we get
$$ (0 \times 2^7) + (0 \times 2^6) + (1 \times 2^5) + (0 \times 2^4) + (1 \times 2^3) + (1 \times 2^2) + (1 \times 2^1) + (0 \times 2^0) = 32 + 8 + 4 + 2 = 46, $$
which is what np.left_shift()
returns.
If we rightft-shift 23, we get
$$ (0 \times 2^7) + (0 \times 2^6) + (0 \times 2^5) + (0 \times 2^4) + (1 \times 2^3) + (0 \times 2^2) + (1 \times 2^1) + (1 \times 2^0) = 8 + 2 + 1 = 11, $$
which is what np.right_shift()
returns.
# 6 = 00000110 in binary
r = 6
# Shift r 2 bits to the right, and then 2 bits to the left
print(np.left_shift(np.right_shift(r, 2), 2))
# 4 = 00000100 in binary
4
We can also bit shift entire arrays, such as those representing an image.
The code below shifts the array corresponding to the image file calico_cat_in_greece.jpg
(in the same folder as this notebook) 6 bits to the right and then back 6 bits to the left.
This process will zero out the rightmost 6 bits (bits 0, 1, 2, 3, 4, and 5).
import matplotlib.image as img
# Read image file and store as array
cat = img.imread('calico_cat_in_greece.jpg')
# Show original image
plt.imshow(cat)
<matplotlib.image.AxesImage at 0x10880be50>
# Bit shift array: 6 to the right, then 6 to the left
cat_new = np.left_shift(np.right_shift(cat, 6), 6)
# Show resulting image
plt.imshow(cat_new)
<matplotlib.image.AxesImage at 0x114c1ac50>
We use three 8-bit integers to represent the color of each pixel
How important is each bit?
Are some more important than others?
How will switching certain bits to "off" affect the image?
The code in the cell below turns "off" bits in order from least significant to most significant in the 3D array associated with the image in GooseIsland.jpg
(in the same folder as this notebook).
Compare the images below. At what point do you start to be able to see a difference in the image?
# Read the image file
goose_island = img.imread('GooseIsland.jpg')
# Create figure with size 15 (width) x 10 (height)
fig = plt.figure(figsize=(15, 10))
for i in range(8):
# Add subplots in order from top left to bottom right
# Remember that Matplotlib indexes subplots starting at 1
ax = fig.add_subplot(2, 4, i + 1)
# Shift the image left and right by i bits to zero out the bits in order
# This will zero out the i rightmost bits
img_shift = np.left_shift(np.right_shift(goose_island, i), i)
# Plot the shifted image
plt.imshow(img_shift)
# Set title for subplot
if i == 0:
ax.set_title(f"Original Image")
elif i == 1:
ax.set_title(f"Bit {i - 1} Off")
else:
ax.set_title(f"Bit {i - 1} and Below Off")
# Display figure
fig.tight_layout(pad=.8)
fig.show(warn=False) # Avoid backend warnings with keyword argument warn=False
Read through the background section of Project 6 in order to get a short overview of the history of steganography, with particular emphasis on the experience of the U.S. Navy.
Then try the remaining problems, which will guide you through the process of hiding an image!
Problem 1. Load the image in football_team.jpg
(in the same folder as this notebook) into an array named cover
. Display the image. We will call this the cover image: the image we will use to hide a secret image.
cover = img.imread('football_team.jpg')
plt.imshow(cover)
<matplotlib.image.AxesImage at 0x1145a7d90>
Bit shift the array cover
4 bits to the right and then 4 bits to the left to zero out the 4 lower bits. Call the new array cover_shifted
. Display the bit shifted image.
cover_shifted = np.left_shift(np.right_shift(cover, 4), 4)
plt.imshow(cover_shifted)
<matplotlib.image.AxesImage at 0x114cb2990>
Now load the image in enterprise.jpg
into an array named secret
. We will call this the secret image: the image that we will hide and then later recover. Display the secret image.
secret = img.imread('enterprise.jpg')
plt.imshow(secret)
<matplotlib.image.AxesImage at 0x114600410>
Bit shift the array secret
4 bits to the right. Call the resulting array secret_shifted
. Display the image.
secret_shifted = np.right_shift(secret, 4)
plt.imshow(secret_shifted)
<matplotlib.image.AxesImage at 0x1153defd0>
Add the arrays cover_shifted
and secret_shifted
together and store the result in an array named steg
. Display the image. This is the image that we would, say, publish on a website for everyone to see.
steg = cover_shifted + secret_shifted
plt.imshow(steg)
<matplotlib.image.AxesImage at 0x115f08890>
Problem 2. Now pretend you're the one who needs to recover the secret. You've been given the image represented by the array steg
you created in Problem 1. Bit shift the array steg
4 bits to the left. Store the result in an array named recovered
. This is the image that we would recover, holding most of the information in secret
. Display the image corresponding to recovered
.
recovered = np.left_shift(steg, 4)
plt.imshow(recovered)
<matplotlib.image.AxesImage at 0x1161eb7d0>
Problem 3.
In practice, you would write the steg
array from Problem 1 to an image file. However, you need to be careful about which image format you use.
In the code cells below, we write the steg
array from Problem 1 to a JPEG file called steg.jpg
. Then we try to read steg.jpg
back into Python, and recover the secret image as we did in Problem 2.
# Write steg to a file
plt.imsave('steg.jpg', steg, format='jpg')
# Load the steganography image
# Display the image
steg_from_jpg = img.imread('steg.jpg')
plt.imshow(steg_from_jpg)
<matplotlib.image.AxesImage at 0x116e8c550>
# Recover the secret image
# Display the recovered secret image
recovered_from_jpg = np.left_shift(steg_from_jpg, 4)
plt.imshow(recovered_from_jpg)
<matplotlib.image.AxesImage at 0x11724f190>
This isn't very useful... 😕
Why does this happen? When you save an image as a JPEG file, the image is first compressed: not every color is saved in its original form. This leads to the loss of the less significant bits describing the contents of the pixels.
Ordinarily, this wouldn't be a problem since our eyes can't see the difference in colors. However, if you are trying to store important information in the less significant bits of the saved image, compression will destroy that data.
One way to correct this problem is to save our images in a format that doesn't try to compress the file. The PNG format is one such format. The code below saves and reads in the picture in PNG format.
One issue is that when img.imread()
reads a PNG file, it stores the color values as numbers between 0 and 1 instead of between 0 and 255. To fix this, we multiply the values by 255, round to the nearest integer, and then convert the result to the uint8 format.
# Write steg to a file using the PNG format, a lossless format!
plt.imsave('steg.png', steg, format='png')
# Load the steganography image
# Display the image
steg_from_png = img.imread('steg.png')
plt.imshow(steg_from_png)
# PNG values are given as floats between 0 and 1 rather than 8-bit
# integers. We multiply by 255, round and convert the results to uint8.
steg_from_png_8bit = (steg_from_png * 255).round().astype(np.uint8)
# Recover the secret image
# Display the recovered secret image
recovered_from_png = np.left_shift(steg_from_png_8bit, 4)
plt.imshow(recovered_from_png)
<matplotlib.image.AxesImage at 0x118d3be90>
Much better! 👍
Attempt these only once you've completed the problems above.
Problem 4. Now we'll try a more complicated situation.
Sometimes the images you want to use for steganography aren't the same size. You can use another application (like Paint or Adobe Photoshop) to resize the images first so they are the same size.
In the same folder as this notebook, there are two image files, Cinque-terre.jpg
and Atlantic-Ocean-Road-in-Norway.jpg
.
To use the Paint application to resize these images:
Open both files in the Paint application (search for Paint in the Windows Start menu).
In the Home ribbon, click on the Resize button.
A dialog box shoud appear. Click the Pixels radio button to see the size of the image. You can adjust the image size by entering the new size in the spaces provided.
If you want to adjust the image size, and change the aspect ratio (the ratio of width to height), then unclick the Maintain aspect ratio checkbox.
Modify one or both of the files so that the two files have the same dimensions. Save the files and then hide one file inside the other. You should be able to reuse most of your code from Problems 1-3 with minor modifications. Try to recover the hidden file from the steganography file.
cover = img.imread('Cinque-terre-resized.jpg')
plt.imshow(cover)
<matplotlib.image.AxesImage at 0x118253990>
cover_shifted = np.left_shift(np.right_shift(cover, 4), 4)
plt.imshow(cover_shifted)
<matplotlib.image.AxesImage at 0x115f1a110>
secret = img.imread('Atlantic-Ocean-Road-in-Norway-resized.jpg')
plt.imshow(secret)
<matplotlib.image.AxesImage at 0x11829ebd0>
secret_shifted = np.right_shift(secret, 4)
plt.imshow(secret_shifted)
<matplotlib.image.AxesImage at 0x115adfb90>
steg = cover_shifted + secret_shifted
plt.imshow(steg)
<matplotlib.image.AxesImage at 0x118b1b150>
recovered = np.left_shift(steg, 4)
plt.imshow(recovered)
<matplotlib.image.AxesImage at 0x117a3ff90>
Problem 5.
In the same folder as this notebook, there are image files called chittorgarh_fort_india.jpg
and Tuscany-Italy.jpg
.
The picture of the fort is smaller than the picture of the Italian countryside. Make a new image of the fort that is the same size as the picture of Tuscany, by surrounding the picture of the fort with a border. The border can by any color you choose. (See Problem 8 from Lesson 14 for hints.)
Next, hide the new image of the fort inside the picture of Tuscany. Recover your secret image from the steganography file. Are these good images to use for steganography? Why or why not?
# Read in cover image
# Display cover image
cover = img.imread('Tuscany-Italy.jpg')
plt.imshow(cover)
# Print size of cover image
cover_h, cover_w, cover_c = cover.shape
print(f'Size of Tuscany-Italy.jpg: {cover_h} x {cover_w}')
Size of Tuscany-Italy.jpg: 422 x 620
# Read in fort image
# Display fort image
fort = img.imread('chittorgarh_fort_india.jpg')
plt.imshow(fort)
# Size of fort image
fort_h, fort_w, fort_c = fort.shape
print(f'Size of chittorgarh_fort_india.jpg: {fort_h} x {fort_c}')
Size of chittorgarh_fort_india.jpg: 382 x 3
# Create secret image by adding a white border to the fort image
# Size of Tuscany image found above
# Placement of fort image chosen so that it is centered
# Display secret image
secret = 255 * np.ones((cover_h, cover_w, cover_c), dtype='uint8')
secret[20:402, 60:560, 0:3] = fort
plt.imshow(secret)
<matplotlib.image.AxesImage at 0x11656e1d0>
# Create steganography image: hide secret image in cover image
# Display steganography image
cover_shifted = np.left_shift(np.right_shift(cover, 4), 4)
secret_shifted = np.right_shift(secret, 4)
steg = cover_shifted + secret_shifted
plt.imshow(steg)
<matplotlib.image.AxesImage at 0x118636d90>
Note that the hidden image bleeds through the steganography image. These are not great images to use for steganography.
# Recover secret image from steganography image
recovered = np.left_shift(steg, 4)
plt.imshow(recovered)
<matplotlib.image.AxesImage at 0x119775650>