最近玩了一个很有意思的操作,直接把数据存放在图片中,这种算法有很多种,今天就说一种加密算法。
原理
首先需要知道像素,有一张三通道的彩色图片,也就是一个 三个维度的矩阵,每个元素的数字的范围是 0~255,很简单,是一个 8 位二进制的数字,总而言言之,每个像素是一个8位的数字。
三个数值可以直接构成一个像素颜色,也就是RGB(Opencv是使用的BGR,无影响)。那就请看下面的图:
你能看出左右红色有什么区别吗?反正我是看不出来,实际上在红色分量上左边是255、右边是254,仅仅差了1,也就是说相差1基本看不出来变化。
那么把八位的数据拆开,最后一位是0是1都不会影响颜色太大变化,因为仅仅相差1。
所以算法就出来了,首先,把一张图片的所有像素的最后一位变成0,这样肉眼是观查不出问题的。然后我们把需要加密的信息重新拆成二进制形式,补充到最后一位上,还是不会看出来(反正我是看不出来)。
程序
我也写出来了这部分程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
| import cv2 import numpy as np
class ImageData: def __init__(self, path: str):
self.imageRead(path)
def __makeHead(self, length: int) -> list: """ 制作头部数据 :param length: 数据长度 :return: """ data_max_size = 4 head_size = 32
if length + head_size > (1 << (8 * data_max_size)): raise ValueError if length + head_size > self.size[0] * self.size[1] * self.size[2]: raise ValueError
bin_str = list(bin(length)[2:])
bin_num0 = [0, 1, 0, 0, 1, 1, 0, 1]
bin_num1 = [ord(num_str) - 48 for num_str in bin_str]
bin_num2 = [0 for tem in range(8 * data_max_size - len(bin_num1))]
bin_num3 = [0 for tem in range(8 * 27)]
head = bin_num0 + bin_num2 + bin_num1 + bin_num3
return head
def __binToNumber(self, datas: np.ndarray) -> list: """ 二进制矩阵转换成十进制列表 :param datas: 二进制矩阵 :return: """
data = [] for tem in datas: num = 0 for tem2 in tem: num = num << 1 num += tem2 data.append(num)
return data
def saveData(self, numbers: list) -> None: """ 将数据藏在图片中 :param numbers: 数据列表 :return: """ length = len(numbers)
head = self.__makeHead(length)
new_numbers = []
for number in numbers: bin_list = [(number >> i) & 1 for i in range(8)][::-1] new_numbers += bin_list
new_numbers = head + new_numbers
numbers_1 = np.array(new_numbers, dtype='uint8') numbers_2 = np.zeros(self.size[0] * self.size[1] * self.size[2] - numbers_1.shape[0], dtype='uint8')
data = np.concatenate((numbers_1, numbers_2)).reshape(self.size)
self.cleanData() self.image += data
def loadData(self) -> tuple: """ 读取藏在图片中的数据 :return: """ data_matrix: np.ndarray = self.image & 1
data_matrix = data_matrix.reshape(-1)
max_length = data_matrix.shape[0]
max_length = max_length - (max_length % 8) data_matrix = data_matrix[:max_length]
data_matrix = data_matrix.reshape((-1, 8))
head_matrix = data_matrix[0:32]
head = self.__binToNumber(head_matrix)
length = head[4] + (head[3] << 8) + (head[2] << 16) + (head[1] << 24)
data_matrix = data_matrix[32:32 + length]
data = self.__binToNumber(data_matrix)
return head, data
def cleanData(self) -> None: """ 清楚图片中的数据 :return: """ self.image = self.image & 0xfe
def imageRead(self, path: str) -> None: """ 读取图片 :param path: 读取图片的路径 :return: """
image = cv2.imread(path)
self.size = image.shape
self.image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
def imageSave(self, path: str) -> None: """ 保存图片 :param path: 保存图片的路径 :return: """ image = cv2.cvtColor(self.image, cv2.COLOR_RGB2BGR) cv2.imwrite(path, image)
def imageShow(self) -> None: """ 显示图片 :return: """
cv2.imshow("image", self.image)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == "__main__": image = ImageData("pycharm.png")
image.saveData([178, 255, 4, 2, 1, 0])
head, data = image.loadData()
image.imageSave("a.png")
print(data)
|
程序有点长,我简单说一下作用。
首先我封装成了一个类ImageData
,构造函数只有一个参数,就是图片的路径,可以直接加载本地图片。
加载进图片之后,图片会处于ImageData
实例化的对象中,也可以再使用imageRead
方法重新加载图片,也可以使用imageSave
方法保存该对象中的图片。我也写了一个显示的方法imageShow
,直接使用可以直接查看。
接下来是关于数据的方法:
cleanData
方法用于清除图片中的数据,也就是直接将像素中的8位二进制数字的最后一位直接变成0。
saveData
方法是将一个列表中的数字藏进图片中,但数字只能是8位二进制数字,有特殊需求可以直接去修改。
loadData
方法会返回图片中的数字,会返回两个值,第一个是相关信息,第二个才是隐藏的数据。