概要
偶然看到一篇文章伪·黑科技】基于像素微调实现的文字隐写术,受此启发,能把文字转成二进制,那么图片像素转成二进制岂不更容易,于是我就写了一个隐藏图片的代码,也就有了这篇随笔。
但是刚写出来不完美,有几点需要改进的地方,现在还没有想通,做此记录,以后有机会再慢慢改进:
- python 包 matplotlib 保存数组构成的图片时,会自动加个透明层,即不是我想要的 RGB 格式,而是 RGBA 格式。why,why,不得不转向其它包
- 包存图片格式时,一般的数组有可能格式不对,如果遇到错误,就保存为 unit8 格式
效果图
提示:这是我截的图,不是原图,像素可能不满足要求,做实验可用自己的图片。
图 1:要被隐藏的图片
图 2:隐藏之后的图片效果图
代码
基本的原理就是利用二进制微调宿主像素。代码中的解释已经很清楚了,在此不再多述。
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 29 11:47:43 2018
@author: zhoukui
"""
import numpy as np
from PIL import Image
import matplotlib.image as mpimg
import sys
def image2Bin(imageFile):
image = mpimg.imread(imageFile)
imageArr = image.ravel()
binList = list((map(bin,imageArr)))
for i, item in enumerate(binList): # 把数字全部转换为 8 位的 0 1 数据
binList[i] = item[2:] # 把二进制标志 '0b' 去掉
binList[i] = '0'*(8 - len(binList[i])) + binList[i]
pixes_x, pixes_y, _ = image.shape # 把原始图片像素保存一下
binX = bin(pixes_x)[2:]
binY = bin(pixes_y)[2:]
binList.insert(0,'0'*(16-len(binX)) + binX)
binList.insert(1,'0'*(16-len(binY)) + binY)
# 前两个 16 位表示的是像素长宽,像素长宽相乘再乘以 3 得到后面 8 位的数目
return binList
def showImage(imageArr, imageTitle):
im = Image.fromarray(imageArr)
im.save(imageTitle)
im.show()
def oneHide(pix, pixValue):
if pix == '0': # 使图片像素变为偶数
if pixValue == 255:
pixValue = 244
elif pixValue % 2 == 1:
pixValue += 1
elif pixValue % 2 == 0:
pixValue += 1
return pixValue # 返回去改变这像素值
def hiding(binList, hidingImageFile):
image = mpimg.imread(hidingImageFile)
#print(image.shape)
imageArr = image.ravel()
# 每一个像素隐藏一个 0 或者 1
# 判断是否藏得下
if (len(binList)*8+16 > len(imageArr)):
print("藏不下,换一张宿主更大的图片或把要藏的图片调小")
else:
flag = 0 # 标记藏到哪了
# 先藏前两个 16 位的
for i in range(2):
for j in range(16):
imageArr[flag] = oneHide(binList[i][j], imageArr[flag])
flag += 1
# 然后藏剩下的
for _, item in enumerate(binList[2:]):
if flag % 8640 == 0:
sys.stdout.write('
complete percent ----->:%.0f%%' % (flag/82976.))
for j in range(8):
imageArr[flag] = oneHide(item[j], imageArr[flag])
flag += 1
sys.stdout.flush()
#print(imageArr[:8])
imageArr = imageArr.reshape(image.shape) #.astype(np.uint8)
return imageArr
def imerging(imageFile):
image = mpimg.imread(imageFile) # 这种读取图片直接是 Ndarray
imageArr = image.ravel()
# 先把隐藏图片的大小解析出来
tempList = [str(i % 2) for i in imageArr[:32]]
pixesX = int(''.join(tempList[:16]), 2)
pixesY = int(''.join(tempList[16:]), 2)
#print(pixesX, pixesY)
# 把隐藏图片的像素解析出来
temp2List = [str(i % 2) for i in imageArr[32 : 32 + pixesX*pixesY*3*8]]
tempArr = np.zeros(pixesX*pixesY*3,)
for i in range(pixesX*pixesY*3):
tempArr[i] = int(''.join(temp2List[8*i:8*(i+1)]), 2)
#sys.stdout.flush()
tempArr = tempArr.reshape(pixesX, pixesY, 3).astype(np.uint8) # 不加会模糊
showImage(tempArr, "image_after_decreption.bmp")
def imageEncre(hidedImageFile, hidingImageFile):
# 可视化隐藏前图片
#imageArrBefore = mpimg.imread(hidingImageFile)
#showImage(imageArrBefore, "image before hiding")
# 把要加密的图片转换成 0 1 数字
print("加密前准备工作...")
binList = image2Bin(hidedImageFile)
# 接下来把 0 与 1 藏进另一张图片
print("开始加密...")
imageArrAfter = hiding(binList, hidingImageFile)
#print(imageArrAfter[:8,:,:])
showImage(imageArrAfter, "image_after_encreption.bmp")
def imageDecre(imageFile):
print("解码中...")
imerging(imageFile)
if __name__ == "__main__":
#imageEncre("hided.jpg", "hiding.jpg")
imageDecre("image_after_encreption.bmp")