簡単な電子透かしを作った。
しかしデータを画像の左上から詰めていたので、単一色の画像をベースにするとデータの有無で境界がわかる状態だった。
これはあまり良くないな…と考え、データとして必要な部分を分散させる。
import random
import binascii
from PIL import Image
import numpy
def quartetize(s):
if type(s) is str: s = s.encode()
a = []
f = binascii.b2a_hex(s).decode('ascii')
for c in f:
c = int(c, 16)
a.extend((c>>2, c&0b0011))
return a
def dequartetize(a, byte=False):
h = ''
ps = [a[i:i+2] for i in range(0, len(a), 2)]
for c in ps: h += hex(c[0]<<2 | c[1])[2:]
b = binascii.a2b_hex(h.encode('ascii'))
return b if byte else b.decode()
def watermark(img, s):
arr = numpy.array(img)
o = quartetize(s)
if len(o) > len(arr) * len(arr[0]) * len(arr[0][0]): raise ValueError
f = [True,] * len(o) + [False,] * ( len(arr) * len(arr[0]) * len(arr[0][0]) - len(o) )
random.shuffle(f)
_arr = arr.copy()
_arr[_arr == 255] -= 1
_arr[:, :, :] = _arr[:, :, :] // 5 * 5
j = 0
k = 0
for r0 in _arr:
for r1 in r0:
for i, c in enumerate(r1):
if f[k]:
r1[i] = c + 1 + o[j]
j += 1
k += 1
return Image.fromarray(_arr)
def dewatermark(img, byte=False):
_arr = numpy.array(img).copy()
_arr[:, :, :] %= 5
a = []
for r0 in _arr:
for r1 in r0: a.extend(r1)
d = [x - 1 for x in filter(lambda x: x > 0, a)]
return dequartetize(d, byte)
s = '''ABCabc123あいう[]'''
print('Original string:\n', s)
print('Length: {} / {} bytes'.format(len(s), len(s.encode())))
print('"Quartetized":\n', quartetize(s))
print('"Dequartetized":\n', dequartetize(quartetize(s)))
img = Image.open('base.png')
try: img = watermark(img, s)
except:
print('The image is too small to contain the data.')
quit()
img.save('data_inserted.png')
img = Image.open('data_inserted.png')
s_decoded = dewatermark(img)
print('"Quartetized":\n', s_decoded)
print('Is the same?:', s == s_decoded)
2022/06/07 20:18