隐藏信息于图片的一种方式

文章介绍了如何通过在图片中利用HSV色彩空间和ASCII字符编码,将信息隐藏在像素间的颜色组合中,既能防止图片被裁切后丢失信息,又能在线上分享时选择性添加水印进行保护。
摘要由CSDN通过智能技术生成

隐藏信息于图片的一种方式

实现逻辑

我们会先在在两个像素之间建立联系。寻找第一个像素的相似色,取色带左右两边相似的16个色,另一色同理,于是我们得到了16*16=256种色彩组合,这个数字就是char的容量,所以我们可以通过两个色块来表示一个单字符。
因为使用的是HSV色彩空间,所以可以操控三个值的起伏,但多个值是难以操控的,于是我们可以将Hue值定下来,从而操控S与V值,这样它们就处于一个直角坐标系中了,所以共有256 * 256 = 65536个坐标,我们将其按照4 * 4 的格子将其分为4096个大格子,两个坐标系之间任意两个大格子之间都可以建立关系,如字符’A’可以由0100 0001表示,而这个坐标对应(S1,V1)=(1,0)与(S2,V2)=(0,1)(S *,V * ∈ [0, 3])

注意,如果处于边缘位置(S *, V * <= 1 or S * , V * > 253)而无法操控这个,我们就跳过这个色,直至无法插入信息。

效果

在使用了相似色替代了原色后,得到的图片可能与原图有部分区别,但在不与原图对比的情况下,人眼是很难看出区别的。
图一
在这里插入图片描述
图2

如上两图中,图1是原图,图2是修饰后的图片。

而这是解密后的结果。

之后,为了使被处理过的图片在被裁切之后仍然能过被识别出来信息,我们可以循环上述处理图结果1
片的方式直至最后一个像素点。然后在解析的图片后,得到的大字符串中,再使用一个算法还原出原信息。

裁切后的图片结果2

可以发现就算被裁剪也是可以实现解析的。

那么接下来我们来慢慢解析这个程序。

修饰图片

我们要先设计一个映射表,使得两个16进制数可以确定一个ASCII字符,我们可以考虑将ASCII字符的十进制值转换为16进制表示。由于ASCII字符集中的字符可以用一个字节(即8位)表示,因此每个字符可以由两个16进制数字唯一确定。

def create_ascii_hex_map():
    # 创建一个空字典来存储映射
    ascii_hex_map = {}

    # 遍历ASCII值的范围(0-127)
    for i in range(128):
        # 将ASCII值转换为16进制,结果是一个字符串,如'0x41'
        hex_str = hex(i)

        # 移除'0x'前缀,并确保结果是两个字符长,不足前面补'0'
        hex_str = hex_str[2:].zfill(2)

        # 将两个16进制数字作为键,ASCII字符作为值加入映射表
        ascii_hex_map[(hex_str[0], hex_str[1])] = chr(i)

    return ascii_hex_map

然后我们要通过输入一个ASCII字符,得到该字符对应的16进制键值对,首先使用上述创建的映射表,然后实现逆向查找。由于原始映射表是以16进制数对作为键,ASCII字符作为值,我们需要反转这个映射表以便通过ASCII字符查找16进制键值对。

def create_ascii_hex_map():
    # 创建一个空字典来存储映射
    ascii_hex_map = {}

    # 遍历ASCII值的范围(0-127)
    for i in range(128):
        # 将ASCII值转换为16进制,结果是一个字符串,如'0x41'
        hex_str = hex(i)

        # 移除'0x'前缀,并确保结果是两个字符长,不足前面补'0'
        hex_str = hex_str[2:].zfill(2)

        # 将两个16进制数字作为键,ASCII字符作为值加入映射表
        ascii_hex_map[(hex_str[0], hex_str[1])] = chr(i)

    return ascii_hex_map

def reverse_map(ascii_hex_map):
    # 反转映射表:ASCII字符作为键,16进制数对作为值
    return {value: key for key, value in ascii_hex_map.items()}

def get_hex_key_for_ascii(ascii_char, reversed_map):
    # 通过ASCII字符查找16进制键值对
    return reversed_map.get(ascii_char, "字符不在表中!")

# 创建映射表并反转
ascii_hex_map = create_ascii_hex_map()
reversed_map = reverse_map(ascii_hex_map)

# 查找字符'A'的16进制键值对
ascii_char = 'A'
hex_key = get_hex_key_for_ascii(ascii_char, reversed_map)
print(f"ASCII中的 '{ascii_char}' 的16进制数是: {hex_key}")

最后是主程序,用于加载图片,处理坐标关系,保存图片。

import os
from PIL import Image


def encode_image_with_text(image_path, text, start_delimiter="!START!", end_delimiter="!END!"):
    # 在文本前后添加分隔符
    text_with_delimiters = f"{start_delimiter}{text}{end_delimiter}"

    # 加载图片
    img = Image.open(image_path)
    pixels = img.load()

    # 将文本转换为ASCII值列表,循环使用文本以填满整个图像
    ascii_values_cycle = [ord(c) for c in text_with_delimiters] * (
                img.width * img.height // len(text_with_delimiters) + 1)
        # 遍历ASCII值列表,每个字符对应两个像素
    for i, ascii_val in enumerate(ascii_values_cycle):
        # 计算两个像素的索引
        pixel_index_1 = 2 * i
        pixel_index_2 = 2 * i + 1

        # 确保不超出图片大小
        if pixel_index_2 >= img.width * img.height:
            break

        # 计算两个像素的坐标
        x1, y1 = divmod(pixel_index_1, img.width)
        x2, y2 = divmod(pixel_index_2, img.width)

        # 确保x2, y2不超出图像边界
        # if x2 >= img.width or y2 >= img.height:
        #     continue
        # 获取原始颜色
        original_color_1 = pixels[y1, x1]
        original_color_2 = pixels[y2, x2]

        # 根据ASCII值修改颜色(这里需要一个函数来根据原始颜色和ASCII值计算新颜色)
        new_color_1, new_color_2 = calculate_new_colors(original_color_1, original_color_2, ascii_val)

        # 设置新颜色
        pixels[y1, x1] = new_color_1
        pixels[y2, x2] = new_color_2

    # 保存修改后的图片
    img.save("worked_image.png")

解密图片

为了解密通过上述方法加密的图像并恢复原始文本,我们需要执行加密过程的逆操作。

我们需要从修改后的图像中提取每个像素的颜色值,然后根据你的颜色到ASCII映射逻辑反向映射回ASCII字符。

from PIL import Image

def decode_text_from_image(image_path):
    # 加载加密的图片
    img = Image.open(image_path)
    pixels = img.load()

    decoded_chars = []

    for i in range(0, img.width * img.height, 2):
        # 计算两个像素的坐标
        x1, y1 = divmod(i, img.width)
        x2, y2 = divmod(i + 1, img.width)

        # 获取两个像素的颜色
        color_1 = pixels[y1, x1]
        color_2 = pixels[y2, x2]

        if color_1.__len__ == 4:
            H1, S1, V1, _ = color_1
            H2, S2, V2, _ = color_2
        else:
            H1, S1, V1 = color_1
            H2, S2, V2 = color_2

        if S1 > 253 or S1 <= 1 or S2 > 253 or S2 <= 1 or V1 > 253 or V1 <= 1 or V2 > 253 or V2 <= 1:
            continue
        # 从颜色恢复ASCII值(需要实现这个函数)
        ascii_val = recover_ascii_from_colors(color_1, color_2)

        # 如果ascii_val是终止字符,则停止解码
        if ascii_val == 0:  # 假设使用ASCII值0作为消息结束的标志
            break

        decoded_chars.append(chr(ascii_val))

    # 将字符列表转换为字符串
    decoded_text = ''.join(decoded_chars)
    return decoded_text

def recover_ascii_from_colors(color_1, color_2):
    # 根据颜色恢复ASCII值的逻辑(需要根据加密逻辑的具体实现来填写)
    # 这里只是一个示例框架,具体实现取决于你的加密逻辑
    H1, S1, V1 = color_1
    H2, S2, V2 = color_2

    # 假设使用了相同的S和V分割逻辑
    AS = (S1 % 4) * 4 + (V1 % 4)
    BS = (S2 % 4) * 4 + (V2 % 4)

    # 从AS和BS恢复原始的ASCII值
    ascii_val = AS * 16 + BS  # 假设AS和BS分别代表ASCII值的高位和低位

    return ascii_val

decoded_text = decode_text_from_image("encoded_image.png")
print(decoded_text)

但使用上述代码后,我们会发现导出的文本是大段且及其重复的。

所以我们可以使用一个技巧来处理多余的字符。

我们可以使用特定的分隔符或结构来确定原始文本,所以在加密过程中会在文本的开始、结束都有添加一个特定的标记(例如特殊字符或字符串),这个标记通常在正常的文本中不会出现。所以我们就可以在解密过程中通过搜索这个特定的标记来识别原始文本的开始和结束位置。

def extract_message_with_delimiters(decrypted_text, start_delimiter="!START!", end_delimiter="!END!"):
    # 搜索开始和结束分隔符的位置
    start_index = decrypted_text.find(start_delimiter)
    end_index = decrypted_text.find(end_delimiter, start_index + len(start_delimiter))
    
    # 如果找到了分隔符,提取并返回原始文本
    if start_index != -1 and end_index != -1:
        # 提取原始文本,不包括分隔符本身
        original_text = decrypted_text[start_index + len(start_delimiter):end_index]
        return original_text
    else:
        # 如果没有找到分隔符,返回整个解密文本或错误消息
        return "没有找到分隔符或原文本错误!"

decrypted_text = "!START!HelloWorld!END!SomeOtherText!START!HelloWorld!END!"
original_text = extract_message_with_delimiters(decrypted_text)
print(original_text)
# 完成解密

除此以外,我们还可以在修饰图片后,在其后加入’_watermarked’的标记,代表其已被修饰过了。然后在解密的时候看这个图片名称的后缀是否是’_watermarked’,然后以此确定改图片是否被修饰过。

def decode_text_from_image(image_path):
    # 检查图片名称是否包含'_watermarked'
    if '_watermarked' not in image_path:
        print("该图片没有被加过水印!")
    	return None
	
    # 解密过程
    
    return decoded_text
def encode_image_with_text(image_path, text, start_delimiter="!START!", end_delimiter="!END!"):
    # 加载图片
    img = Image.open(image_path)
    pixels = img.load()
    
    # 加密过程
    
    # 分割原始图片路径以获取文件名和扩展名
    file_name, file_extension = os.path.splitext(os.path.basename(image_path))
    
    # 创建新的文件名,加上'_watermarked'后缀
    new_file_name = f"{file_name}_watermarked{file_extension}"
    
    # 保存修改后的图片
    # 将修饰过的图片保存在原目录下
    img.save(os.path.join(os.path.dirname(image_path), new_file_name))

至此完成解密过程。

可能的作用

1.在网上分享图片时,可以选择是否添加水印,添加水印后的照片可以被这个平台发现并解密。

2.是一种新的加密方式,如果不知道解密的方式则很难发现图片隐藏的信息。

3.可以防止图片被裁切后失去原图片的信息。

还有更多的使用方式欢迎在评论区启发。

留一个彩蛋彩蛋

本文已同步至知乎原文欢迎点赞收藏

  • 36
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值