因为最近接触到了大佬们做的图片文件夹、图片化打包以及各种无影坦克,觉得非常有趣,所以用python做了一个简单的小玩具。因为本身没有多少编程经验,所以希望看到这篇文章的大佬们不吝赐教。
1、功能:将文件通过更改像素值的方式写入准备好的图片(表图),达到隐藏文件的目的。
2、原理:通过opencv将表图分解为B、G、R三个通道,仅更改每个像素三个通道的最后一位(大于255则减10)实现单个像素存储数字0~999,以此实现数据的存储。
存储结构: 指令ID + 指令内容 + 指令ID + 指令内容+......
为方便读取存储内容,指令ID均使用大于255的数字。
3、代码:
(1)首先是开头的库:
# -*- coding: UTF-8 -*-
import binascii
import cv2
import numpy as np
import math as m
import os
(2)写几个函数为了后续编写方便:
拆分和组合长整数的函数:
def split_int(s):
s = str(s)
n = len(s)
if n % 2 == 1:
s = '0' + s
n = len(s)
sl = np.zeros(int(n / 2))
i = 0
while i < (n / 2):
sl[i] = s[2 * i:2 * (i + 1)]
i = i + 1
return sl
def get_split_int(sl):
n = len(sl)
i = 0
s = ''
while i < n:
if len(str(int(sl[i]))) % 2 == 1:
s1 = '0' + str(int(sl[i]))
else:
s1 = str(int(sl[i]))
s = s + s1
i = i + 1
s = int(s)
return s
编码和解码的函数:
def encode_bd(b):
b = binascii.hexlify(b)
b = str(b)
b = b[2:4]
d = int(b, 16)
return d
def encode_db(d):
d = str(hex(int(str(d), 10)))
d = d[2:4]
if len(d) == 1:
d = '0' + d
d = bytes(d, encoding='utf8')
b = binascii.unhexlify(d)
return b
通过更改一个像素的b,g,r值写入数据:
def w_bgr(b, g, r, data):
data_r = data % 10
data_g = (data % 100 - data_r) / 10
data_b = (data - data_r - data_g) / 100
b = b - b % 10 + data_b
if b > 255:
b -= 10
g = g - g % 10 + data_g
if g > 255:
g -= 10
r = r - r % 10 + data_r
if r > 255:
r -= 10
return b, g, r
通过表示表图像素位置的整数i写入数据:
def set_bgr(b, g, r, bgr_i, s_y, data):
x = int(m.floor(bgr_i / s_y))
y = int(bgr_i % s_y)
b[x, y], g[x, y], r[x, y] = w_bgr(b[x, y], g[x, y], r[x, y], data)
通过一个像素的b,g,r值读取十进制数据:
def get_d(b, g, r):
data_r = r % 10
data_g = g % 10
data_b = b % 10
data = data_r + 10 * data_g + 100 * data_b
return data
通过表示表图像素位置的整数i读取十进制数据:
def get_d_i(b, g, r, bgr_i, s_y):
x = int(m.floor(bgr_i / s_y))
y = int(bgr_i % s_y)
data = get_d(b[x, y], g[x, y], r[x, y])
return data
通过表示表图像素位置的整数i读取十进制数据并编码为byte:
def get_byte(b, g, r, bgr_i, s_y):
x = int(m.floor(bgr_i / s_y))
y = int(bgr_i % s_y)
strb = get_d(b[x, y], g[x, y], r[x, y])
strb = encode_db(int(strb))
return strb
判断文件是否存在并改名
def name_exists(name):
j = 0
name_new = name
while os.path.exists(name_new):
name_new = '(' + str(j) + ')' + name
j = j + 1
return name_new
(3)写一个IMG类方便对表图进行操作:
class IMG:
def __init__(self, path):
self.img = cv2.imread(path)
self.size = os.path.getsize(path)
(self.b, self.g, self.r) = cv2.split(self.img)
def set_d(self, i, data):
s_y = self.img.shape[1]
set_bgr(self.b, self.g, self.r, i, s_y, data)
i = i + 1
return i
def set_b(self, i, data):
data = encode_bd(data)
s_y = self.img.shape[1]
set_bgr(self.b, self.g, self.r, i, s_y, data)
i = i + 1
return i
def set_ds(self, i, ds):
s_y = self.img.shape[1]
le = len(ds)
j = 0
while j < le:
set_bgr(self.b, self.g, self.r, i, s_y, ds[j])
i = i + 1
j = j + 1
return i
def set_s(self, i, s):
s = s.encode('utf-8')
le = len(s)
j = 0
while j < le:
k = s[j:(j + 1)]
k = binascii.hexlify(k)
k = int(k, 16)
i = self.set_d(i, k)
j = j + 1
return i
def update(self):
self.img = cv2.merge([self.b, self.g, self.r])
def save(self, name):
print('生成文件:' + name + '.png', end=' ')
name = name + '.png'
new_name = name_exists(name)
if new_name != name:
print('文件已存在,更名为' + new_name)
else:
print('')
cv2.imwrite(new_name, self.img)
def get_d(self, i):
s_y = self.img.shape[1]
data = get_d_i(self.b, self.g, self.r, i, s_y)
i = i + 1
return data, i
def get_ds(self, i, le):
s_y = self.img.shape[1]
ds = np.zeros(le)
j = 0
while j < le:
ds[j] = get_d_i(self.b, self.g, self.r, i, s_y)
i = i + 1
j = j + 1
return ds, i
def get_b(self, i):
s_y = self.img.shape[1]
b = get_byte(self.b, self.g, self.r, i, s_y)
i = i + 1
return b, i
(4)写一个FILE类方便对文件进行操作:
class FILE:
def __init__(self):
self.name = 'tank'
self.size = 0
(5)指令:
为了修改方便定义几个变量当宏定义使
cmd_start = 256
cmd_filesize = 257
cmd_name = 258
cmd_file = 259
cmd_end = 260
写入指令的函数:
def w_cmd(cmd, i, img, data):
if cmd == cmd_start:
print('写入开始指令...', end=' ')
i = img.set_d(i, cmd_start)
i = img.set_d(i, data)
print('完成,校验数:' + str(data))
return i
elif cmd == cmd_filesize:
print('写入文件大小...', end=' ')
d = data
i = img.set_d(i, cmd_filesize)
data = split_int(data)
i = img.set_ds(i, data)
print('完成,文件大小:' + str(d))
return i
elif cmd == cmd_name:
print('写入文件名称...', end=' ')
i = img.set_d(i, cmd_name)
name = os.path.split(data)
name = name[1]
i = img.set_s(i, name)
print('完成,文件名称:' + str(name))
return i
elif cmd == cmd_file:
print('写入文件...', end=' ')
i = img.set_d(i, cmd_file)
i0 = i
with open(data, "rb") as f:
while True:
strb = f.read(1)
if strb == b"":
print('完成')
return i
i = img.set_b(i, strb)
print('\r', '写入文件...已写入'+str(i-i0), end='', flush=True)
elif cmd == cmd_end:
print('写入结束指令...', end=' ')
i = img.set_d(i, cmd_end)
i = img.set_d(i, data)
print('完成')
return i
else:
print('指令错误,未识别的指令ID' + str(cmd))
return i
因为指令均使用大于255的数字,所以写一个能从当前位向后读取至大于255的数字并返回列表,读取某些指令时用得到:
def auto_cmd(i, img):
cmd_list = []
data, i = img.get_d(i)
while data < 255:
cmd_list.append(data)
data, i = img.get_d(i)
i = i - 1
return cmd_list, i
读取指令的函数:
def r_cmd(i, img, file):
cmd, i = img.get_d(i)
if cmd == cmd_start:
print('读取开始指令...', end=' ')
data, i = img.get_d(i)
if data != 0:
i = -1
print('校验数:' + str(data) + ',错误')
else:
print('完成,校验数:' + str(data))
return i
elif cmd == cmd_filesize:
print('读取文件大小...', end=' ')
size, i = auto_cmd(i, img)
s = get_split_int(size)
file.size = s
print('完成,文件大小:' + str(s))
return i
elif cmd == cmd_name:
print('读取文件名...', end=' ')
data, i = auto_cmd(i, img)
j = 0
s1 = b''
while j < len(data):
s = encode_db(int(data[j]))
s1 = s1 + s
j = j + 1
s1 = s1.decode('utf-8')
file.name = s1
print('完成,文件名:' + str(s1), end=' ')
new_name = name_exists(str(s1))
if new_name != str(s1):
print('文件已存在,更名为'+new_name)
file.name = new_name
else:
print('')
return i
elif cmd == cmd_file:
print('读取文件,正在写入...', end=' ')
with open(file.name, "wb") as f:
j = 0
while j < file.size:
print('\r', '读取文件,正在写入...已写入' + str(j), end='', flush=True)
data, i = img.get_b(i)
f.write(data)
j = j + 1
print('完成')
return i
elif cmd == cmd_end:
print('读取结束指令...', end=' ')
i = -1
print('完成,结束读取')
return i
else:
i = -1
print('指令错误,未识别的指令ID:' + str(cmd))
return i
(6)再往之后写起来就方便了,以写入和读取一个文件为例:
写入一个文件并隐藏在表图中生成一个新图片的函数:
def new_png(path, img, name='tank'):
img = IMG(img)
i = 0
i = w_cmd(cmd_start, i, img, 0)
i = w_cmd(cmd_filesize, i, img, os.path.getsize(path))
i = w_cmd(cmd_name, i, img, path)
i = w_cmd(cmd_file, i, img, path)
i = w_cmd(cmd_end, i, img, 0)
img.update()
img.save(name)
return i
读取文件中的指令并执行:
def read(img):
img = IMG(img)
file = FILE()
i = 0
while i > -1:
i = r_cmd(i, img, file)
使用方法及代码:
写入时调用的函数:
new_png("0.5.zip", 'ph.jpg')
注释:new_png(文件路径,表图路径)
读取时调用的函数:
read('tank.png')
注释: read(生成图)
4、演示:
文件与表图:
写入:
读取:
读取后:
图片比较,上方为原图: