123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- #!/usr/bin/env python3
- #
- # Copyright(C) 2021 wuyaoping
- #
- # DCT algorithm has great a robust but lower capacity.
- import numpy as np
- import os.path as osp
- import cv2
- FLAG = '%'
- # Select a part location from the middle frequency
- LOC_MAX = (4, 1)
- LOC_MIN = (3, 2)
- # The difference between MAX and MIN,
- # bigger to improve robust but make picture low quality.
- ALPHA = 1
- # Quantizer table
- TABLE = np.array([
- [16, 11, 10, 16, 24, 40, 51, 61],
- [12, 12, 14, 19, 26, 58, 60, 55],
- [14, 13, 16, 24, 40, 57, 69, 56],
- [14, 17, 22, 29, 51, 87, 80, 62],
- [18, 22, 37, 56, 68, 109, 103, 77],
- [24, 35, 55, 64, 81, 104, 113, 92],
- [49, 64, 78, 87, 103, 121, 120, 101],
- [72, 92, 95, 98, 112, 100, 103, 99]
- ])
- def insert(path, txt):
- img = cv2.imread(path, cv2.IMREAD_ANYCOLOR)
- txt = "{}{}{}".format(len(txt), FLAG, txt)
- row, col = img.shape[:2]
- max_bytes = (row // 8) * (col // 8) // 8
- assert max_bytes >= len(
- txt), "Message overflow the capacity:{}".format(max_bytes)
- img = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
- # Just use the Y plane to store message, you can use all plane
- y, u, v = cv2.split(img)
- y = y.astype(np.float32)
- blocks = []
- # Quantize blocks
- for r_idx in range(0, 8 * (row // 8), 8):
- for c_idx in range(0, 8 * (col // 8), 8):
- quantized = cv2.dct(y[r_idx: r_idx+8, c_idx: c_idx+8]) / TABLE
- blocks.append(quantized)
- for idx in range(len(txt)):
- encode(blocks[idx*8: (idx+1)*8], txt[idx])
- idx = 0
- # Restore Y plane
- for r_idx in range(0, 8 * (row // 8), 8):
- for c_idx in range(0, 8 * (col // 8), 8):
- y[r_idx: r_idx+8, c_idx: c_idx+8] = cv2.idct(blocks[idx] * TABLE)
- idx += 1
- y = y.astype(np.uint8)
- img = cv2.cvtColor(cv2.merge((y, u, v)), cv2.COLOR_YUV2BGR)
- filename, _ = osp.splitext(path)
- # DCT algorithm can save message even if jpg
- filename += '_dct_embeded' + '.jpg'
- cv2.imwrite(filename, img)
- return filename
- # Encode a char into the blocks
- def encode(blocks, data):
- data = ord(data)
- for idx in range(len(blocks)):
- bit_val = (data >> idx) & 1
- max_val = max(blocks[idx][LOC_MAX], blocks[idx][LOC_MIN])
- min_val = min(blocks[idx][LOC_MAX], blocks[idx][LOC_MIN])
- if max_val - min_val <= ALPHA:
- max_val = min_val + ALPHA + 1e-3
- if bit_val == 1:
- blocks[idx][LOC_MAX] = max_val
- blocks[idx][LOC_MIN] = min_val
- else:
- blocks[idx][LOC_MAX] = min_val
- blocks[idx][LOC_MIN] = max_val
- # Decode a char from the blocks
- def decode(blocks):
- val = 0
- for idx in range(len(blocks)):
- if blocks[idx][LOC_MAX] > blocks[idx][LOC_MIN]:
- val |= 1 << idx
- return chr(val)
- def extract(path):
- img = cv2.imread(path, cv2.IMREAD_ANYCOLOR)
- row, col = img.shape[:2]
- max_bytes = (row // 8) * (col // 8) // 8
- img = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
- y, u, v = cv2.split(img)
- y = y.astype(np.float32)
- blocks = []
- for r_idx in range(0, 8 * (row // 8), 8):
- for c_idx in range(0, 8 * (col // 8), 8):
- quantized = cv2.dct(y[r_idx: r_idx+8, c_idx: c_idx+8]) / TABLE
- blocks.append(quantized)
- res = ''
- idx = 0
- # Extract the length of the message
- while idx < max_bytes:
- ch = decode(blocks[idx*8: (idx+1)*8])
- idx += 1
- if ch == FLAG:
- break
- res += ch
- end = int(res) + idx
- assert end <= max_bytes, "Input image isn't correct."
- res = ''
- while idx < end:
- res += decode(blocks[idx*8: (idx+1)*8])
- idx += 1
- return res
- if __name__ == '__main__':
- data = 'A collection of simple python mini projects to enhance your Python skills.'
- res_path = insert('./example.png', data)
- res = extract(res_path)
- print(res)
|