原理很简单,图片的每一个像素具有RGB三个数据,每个R、G、B通道范围在0~255,对应二进制是00000000~11111111,可以通过修改R、G、B通道的为我们自己的数据,从而达到隐藏数据的效果。
比如,我们要隐藏 'a',其对应的二进制是01000001,我们就可以将这 8 bit 放在连续的8个R、G、B通道数据的最后一位作为隐藏,这样做我们最多只会改变 1 个单位RGB亮度,这样的话相对于初始RGB值可以忽略不计,从而使得人眼无法识别出来。
还有一个方案,为了将隐藏的信息对应起一个像素的RGB,可以改变R的后3位,G的后3位,B的后2位,这样一个像素点对应着一个8bit的数据,这样对于ASCII码上的字符就足够了。
我选择的是前者方案。
第一步,我们要将输入的字符串转换为二进制
def to_Binary(message : any):
"""将输入信息转换为二进制数据
:param message: 输入信息
:return 二进制数据字符串
"""
msg_type = type(message)
if msg_type == str:
return (''.join(format(ord(char), f'0{code_bit}b') for char in message))
# 如果是 08b 那么就是转换为8位码,这样只能对应ASCII码上的外国字符。为了能隐藏中文,所以选择utf-8的16位码
else:
print ("您输入的信息格式暂时不支持")
这里,为满足中文输入需求,8位已经不够了,至少选择16位。
第二步,将数据写入图片
def write_to_photo(message : any,photo_path : str):
"""将二进制数据写入图片中
:param message:输入的信息
:param photo_path:初始照片地址
:return 一个cv2中的image对象
"""
message +='#####' #‘#####’作为结尾判断符
binary_code = to_Binary(message)
photo = cv2.imread(photo_path) #imread()得到的是BGR数据
#cv2.imshow('before',photo)
max_bit = int(photo.shape[0]*photo.shape[1]*3/code_bit)
if len(binary_code) > max_bit:
print('隐藏数据大于图片最大容量')
else:
code_length = len(binary_code)
width = photo.shape[1]
height = photo.shape[0]
len_count = 0
for i in range(height): #从上往下从左往右写数据
for j in range(width):
# 得到该点像素数
pixel_old_value = photo[i][j]
# 将 BRG 三个通道的二进制值最后一位置零
pixel_B_value = pixel_old_value[0] & 254
pixel_G_value = pixel_old_value[1] & 254
pixel_R_value = pixel_old_value[2] & 254
if len_count < code_length:
pixel_B_value += int(binary_code[len_count])
len_count +=1
if len_count < code_length:
pixel_G_value += int(binary_code[len_count])
len_count += 1
if len_count < code_length:
pixel_R_value += int(binary_code[len_count])
len_count += 1
elif len_count >= code_length:
break
#重新写像素
photo[i][j] = [pixel_B_value,pixel_G_value,pixel_R_value]
return photo
这里要注意图片能隐藏的最大容量。
第三步,从图中解码
#第三步,将图片中的数据解码出来
def decode_from_photo(photo):
"""将数据从图片中解码出来
:param photo 一个cv2中的image对象
:return 返回解码字符串信息
"""
#先不管解码,将像素BGR二进制最后一位取出
all_code=''
for i in range(photo.shape[0]):
for j in range(photo.shape[1]):
pixel_value = photo[i][j]
for k in range(3):
all_code += str((pixel_value[k] & 1))
#将all_code每 code_bit 一个列表元素分割
all_data = [all_code[i:i+code_bit] for i in range(0,len(all_code),code_bit)]
#将all_data转换为隐藏数据
decode_data=''
for data in all_data:
decode_data += chr(int(data,2))
if decode_data[-5:] == '#####': #判断结尾符
return decode_data[:-5]
break
这就是全部的思路,下面附全部代码。
"""
实现功能:将输入信息存储在图片中
将信息转换为二进制数据,再将二进制数据存储在图片像素值的最后一位中
"""
"""
:note 最大可存储:(width*height*3) bit数据,img0 最大是 172*230*3/16 = 7417 个字符
可优化点:
1、先判断 to_Binary 函数转换的msg的类型,比如 int 类型的每一位转换为二进制只需要3bit,比将int转变为16位utf-8码要节省空间
2.标识符是"####"时会出现不知原因的bug,换成“#####”就没有问题。为解决这个问题,可以考虑使用更加稳健的方法来标识信息的结尾。一种常见的方法是在隐藏信息之前,
首先将信息的长度编码到图像中,然后在解码时根据长度信息来确定何时停止解码。这样可以避免在信息中出现特定的字符序列导致解码错误的情况。
"""
import cv2
#第一步,将信息转换为二进制数据
def to_Binary(message : any):
"""将输入信息转换为二进制数据
:param message: 输入信息
:return 二进制数据字符串
"""
msg_type = type(message)
if msg_type == str:
return (''.join(format(ord(char), f'0{code_bit}b') for char in message))
# 如果是 08b 那么就是转换为8位码,这样只能对应ASCII码上的外国字符。为了能隐藏中文,所以选择utf-8的16位码
else:
print ("您输入的信息格式暂时不支持")
#第二步将二进制数据写入图片
def write_to_photo(message : any,photo_path : str):
"""将二进制数据写入图片中
:param message:输入的信息
:param photo_path:初始照片地址
:return 一个cv2中的image对象
"""
message +='#####' #‘#####’作为结尾判断符
binary_code = to_Binary(message)
photo = cv2.imread(photo_path) #imread()得到的是BGR数据
#cv2.imshow('before',photo)
max_bit = int(photo.shape[0]*photo.shape[1]*3/code_bit)
if len(binary_code) > max_bit:
print('隐藏数据大于图片最大容量')
else:
code_length = len(binary_code)
width = photo.shape[1]
height = photo.shape[0]
len_count = 0
for i in range(height): #从上往下从左往右写数据
for j in range(width):
# 得到该点像素数
pixel_old_value = photo[i][j]
# 将 BRG 三个通道的二进制值最后一位置零
pixel_B_value = pixel_old_value[0] & 254
pixel_G_value = pixel_old_value[1] & 254
pixel_R_value = pixel_old_value[2] & 254
if len_count < code_length:
pixel_B_value += int(binary_code[len_count])
len_count +=1
if len_count < code_length:
pixel_G_value += int(binary_code[len_count])
len_count += 1
if len_count < code_length:
pixel_R_value += int(binary_code[len_count])
len_count += 1
elif len_count >= code_length:
break
#重新写像素
photo[i][j] = [pixel_B_value,pixel_G_value,pixel_R_value]
return photo
#第三步,将图片中的数据解码出来
def decode_from_photo(photo):
"""将数据从图片中解码出来
:param photo 一个cv2中的image对象
:return 返回解码字符串信息
"""
#先不管解码,将像素BGR二进制最后一位取出
all_code=''
for i in range(photo.shape[0]):
for j in range(photo.shape[1]):
pixel_value = photo[i][j]
for k in range(3):
all_code += str((pixel_value[k] & 1))
#将all_code每 code_bit 一个列表元素分割
all_data = [all_code[i:i+code_bit] for i in range(0,len(all_code),code_bit)]
#将all_data转换为隐藏数据
decode_data=''
for data in all_data:
decode_data += chr(int(data,2))
if decode_data[-5:] == '#####': #判断结尾符
return decode_data[:-5]
break
def main():
"""
运行的主函数
"""
message = input('请输入:')
photo = write_to_photo(message, 'img0.jpg')
print(decode_from_photo(photo))
#cv2.imshow('later', photo)
cv2.waitKey(0) #等待用户按下任意键后继续执行下一步操作
cv2.destroyAllWindows()
if __name__ == '__main__':
code_bit = 16 #作为转换为二进制数据的位数,正常字母、数字、字符使用 8 位,需要使用中文时选择 16 位
main()
测试各种字母、数字、符号、空格符,成功。
照片改变肉眼根本看不见。
谢谢观看,欢迎指正。