使用 Python 在图像中隐藏信息

原理很简单,图片的每一个像素具有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()

测试各种字母、数字、符号、空格符,成功。

照片改变肉眼根本看不见。

谢谢观看,欢迎指正。

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值