利用取模在图片中隐藏信息
思路为所有像素对指定的数取模,然后利用进制转化存储信息。例如原来的局部像素为 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
,指定的模为 16,存储的信息为 iydon
:
- 令局部像素对 16 取模为 0,即
[0, 0, 0, 0, 0, 0, 0, 0, 16, 16]
- 将
iydon
编码为 bytes,即 [105, 121, 100, 111, 110]
- 将编码后的数据按位进制转化,即
[6, 9, 7, 9, 6, 4, 6, 15, 6, 14]
- 局部像素与进制转化后的数据相加,即
[6, 9, 7, 9, 6, 4, 6, 15, 22, 30]
程序如下,但是程序目前仅支持 PNG,三通道图片上出现的问题可能是由读取与写入的 RGB 顺序导致的。
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 | import math
import pathlib as p
import typing as t
import cv2
import numpy as np
Path = t.Union[str, p.Path]
class Bytes:
'''Vec<u8>'''
def __init__(self, size: int = 2) -> None:
# assert 1 <= size <= 256
self._size = size
self._part = math.ceil(8/math.log2(size))
def __lt__(self, other: t.List[int]) -> t.List[int]:
'''<: from'''
# assert len(other) % self._part == 0
# assert all(o//self._size==0 for o in other)
length = len(other) // self._part
ans = [0] * length
for ith in range(length):
factor = 1
for digit in reversed(other[ith*self._part: (ith+1)*self._part]):
ans[ith] += factor * digit
factor *= self._size
return ans
def __gt__(self, other: t.List[int]) -> t.List[int]:
'''>: to'''
# assert all(o//256==0 for o in other)
ans = [0] * (len(other)*self._part)
for ith, byte in enumerate(other):
start = (ith+1) * self._part
for jth in range(self._part):
ans[start-jth-1] = byte % self._size
byte //= self._size
return ans
class Image:
def __init__(self, data: np.ndarray) -> None:
self._data = data
@property
def capacity(self) -> int:
return self._data.size
@classmethod
def from_file(cls, path: Path) -> 'Image':
if p.Path(path).suffix != '.png':
raise NotImplementedError('The bug with non-png images is not yet fixed')
return cls(cv2.imread(str(path)))
def read(self, size: int) -> bytes:
# TODO: 使用第一位存储 size 信息
_bytes = Bytes(size)
ith = np.nonzero(self._data.flat % np.uint8(size))[0][0]
info = self._data.flat[ith+1:] % size
return bytes(_bytes < info)
def burn(self, content: bytes, size: int) -> 'Image':
_bytes = Bytes(size)
info = np.array(_bytes>content, dtype=np.uint8)
capacity, length = self._data.size, info.size
assert capacity > length
self._data.flat[:capacity] -= self._data.flat[:capacity] % size
self._data.flat[capacity-length-1] += 1
self._data.flat[-length:] += info
return self
def write(self, path: Path) -> None:
cv2.imwrite(path, self._data)
def capacity(self, size: int) -> float:
# Kilo-Bytes (KB)
return self._data.size / Bytes(size)._part / 1024.
if __name__ == '__main__':
with open('airFoil2D.yaml', 'rb') as f: # https://github.com/iydon/of.yaml/blob/main/tutorials/incompressible/simpleFoam/airFoil2D.yaml
Image \
.from_file('example.png') \
.burn(f.read(), 2) \
.write('output.png')
# print(Image.from_file('output.png').read(2))
|