diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..81fb752
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,26 @@
+language: python
+python:
+ - "3.8"
+
+
+# command to install dependencies
+install:
+ - pip install -r requirements.txt
+ - pip install numpy==1.18.1
+ - pip install coverage codecov
+ - python setup.py install
+
+# command to run tests
+script:
+ - cd examples
+ - coverage run -p example_no_writing.py
+ - coverage run -p example_bit.py
+ - coverage run -p example_str.py
+ - coverage run -p example_img.py
+ - cp .coverage.* ..
+ - cd ..
+
+# Push the results back to codecov
+after_success:
+ - coverage combine
+ - codecov
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..21d3ccf
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 郭飞
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..34dee47
--- /dev/null
+++ b/README.md
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# blind-watermark
+
+Blind watermark based on dct and svd.
+
+
+[](https://pypi.org/project/blind_watermark/)
+[](https://travis-ci.com/guofei9987/blind_watermark)
+[](https://codecov.io/gh/guofei9987/blind_watermark)
+[](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE)
+
+
+[](https://github.com/guofei9987/blind_watermark/)
+[](https://github.com/guofei9987/blind_watermark/fork)
+[](https://pepy.tech/project/blind-watermark)
+[](https://github.com/guofei9987/blind_watermark/discussions)
+
+
+- **Documentation:** [https://BlindWatermark.github.io/blind_watermark/#/en/](https://BlindWatermark.github.io/blind_watermark/#/en/)
+- **文档:** [https://BlindWatermark.github.io/blind_watermark/#/zh/](https://BlindWatermark.github.io/blind_watermark/#/zh/)
+- **中文 readme** [README_cn.md](README_cn.md)
+- **Source code:** [https://github.com/guofei9987/blind_watermark](https://github.com/guofei9987/blind_watermark)
+
+
+
+# install
+```bash
+pip install blind-watermark
+```
+
+For the current developer version:
+```bach
+git clone git@github.com:guofei9987/blind_watermark.git
+cd blind_watermark
+pip install .
+```
+
+# How to use
+
+
+## Use in bash
+
+
+```bash
+# embed watermark into image:
+blind_watermark --embed --pwd 1234 examples/pic/ori_img.jpeg "watermark text" examples/output/embedded.png
+# extract watermark from image:
+blind_watermark --extract --pwd 1234 --wm_shape 111 examples/output/embedded.png
+```
+
+
+
+## Use in Python
+
+Original Image + Watermark = Watermarked Image
+
+ + '@guofei9987 开源万岁!' = 
+
+
+See the [codes](/examples/example_str.py)
+
+Embed watermark:
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_img=1, password_wm=1)
+bwm1.read_img('pic/ori_img.jpg')
+wm = '@guofei9987 开源万岁!'
+bwm1.read_wm(wm, mode='str')
+bwm1.embed('output/embedded.png')
+len_wm = len(bwm1.wm_bit)
+print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))
+```
+
+Extract watermark:
+```python
+bwm1 = WaterMark(password_img=1, password_wm=1)
+wm_extract = bwm1.extract('output/embedded.png', wm_shape=len_wm, mode='str')
+print(wm_extract)
+```
+Output:
+>@guofei9987 开源万岁!
+
+### attacks on Watermarked Image
+
+
+|attack method|image after attack|extracted watermark|
+|--|--|--|
+|Rotate 45 Degrees||'@guofei9987 开源万岁!'|
+|Random crop||'@guofei9987 开源万岁!'|
+|Masks|  |'@guofei9987 开源万岁!'|
+|Vertical cut||'@guofei9987 开源万岁!'|
+|Horizontal cut||'@guofei9987 开源万岁!'|
+|Resize||'@guofei9987 开源万岁!'|
+|Pepper Noise||'@guofei9987 开源万岁!'|
+|Brightness 10% Down||'@guofei9987 开源万岁!'|
+
+
+
+
+
+
+### embed images
+
+embed watermark:
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# read original image
+bwm1.read_img('pic/ori_img.jpg')
+# read watermark
+bwm1.read_wm('pic/watermark.png')
+# embed
+bwm1.embed('output/embedded.png')
+```
+
+
+Extract watermark:
+```python
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# notice that wm_shape is necessary
+bwm1.extract(filename='output/embedded.png', wm_shape=(128, 128), out_wm_name='output/extracted.png', )
+```
+
+
+|attack method|image after attack|extracted watermark|
+|--|--|--|
+|Rotate 45 Degrees|||
+|Random crop|||
+|Mask|  ||
+
+
+### embed array of bits
+
+See it [here](/examples/example_bit.py)
+
+
+As demo, we embed 6 bytes data:
+```python
+wm = [True, False, True, True, True, False]
+```
+
+Embed:
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_img=1, password_wm=1)
+bwm1.read_ori_img('pic/ori_img.jpg')
+bwm1.read_wm([True, False, True, True, True, False], mode='bit')
+bwm1.embed('output/embedded.png')
+```
+
+Extract:
+```python
+bwm1 = WaterMark(password_img=1, password_wm=1, wm_shape=6)
+wm_extract = bwm1.extract('output/打上水印的图.png', mode='bit')
+print(wm_extract)
+```
+Notice that `wm_shape` (shape of watermark) is necessary
+
+The output `wm_extract` is an array of float. set a threshold such as 0.5.
+
+
+# Concurrency
+
+```python
+WaterMark(..., processes=None)
+```
+- `processes`: number of processes, can be integer. Default `None`, meaning use all processes.
+
+## Related Project
+
+text_blind_watermark: [https://github.com/guofei9987/text_blind_watermark](https://github.com/guofei9987/text_blind_watermark)
+Embed message into text.
diff --git a/README_cn.md b/README_cn.md
new file mode 100644
index 0000000..a2c7b52
--- /dev/null
+++ b/README_cn.md
@@ -0,0 +1,173 @@
+# blind-watermark
+
+基于频域的数字盲水印
+
+
+[](https://pypi.org/project/blind_watermark/)
+[](https://travis-ci.com/guofei9987/blind_watermark)
+[](https://codecov.io/gh/guofei9987/blind_watermark)
+[](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE)
+
+
+[](https://github.com/guofei9987/blind_watermark/)
+[](https://github.com/guofei9987/blind_watermark/fork)
+[](https://pepy.tech/project/blind-watermark)
+[](https://github.com/guofei9987/blind_watermark/discussions)
+
+
+- **Documentation:** [https://BlindWatermark.github.io/blind_watermark/#/en/](https://BlindWatermark.github.io/blind_watermark/#/en/)
+- **文档:** [https://BlindWatermark.github.io/blind_watermark/#/zh/](https://BlindWatermark.github.io/blind_watermark/#/zh/)
+- **English readme** [README.md](README.md)
+- **Source code:** [https://github.com/guofei9987/blind_watermark](https://github.com/guofei9987/blind_watermark)
+
+# 安装
+```bash
+pip install blind-watermark
+```
+
+或者安装最新开发版本
+```bach
+git clone git@github.com:guofei9987/blind_watermark.git
+cd blind_watermark
+pip install .
+```
+
+# 如何使用
+
+## 命令行中使用
+
+```bash
+# 嵌入水印:
+blind_watermark --embed --pwd 1234 examples/pic/ori_img.jpeg "watermark text" examples/output/embedded.png
+# 提取水印:
+blind_watermark --extract --pwd 1234 --wm_shape 111 examples/output/embedded.png
+```
+
+
+
+## Python 中使用
+
+原图 + 水印 = 打上水印的图
+
+ + '@guofei9987 开源万岁!' = 
+
+
+
+参考 [代码](/examples/example_str.py)
+
+
+嵌入水印
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_img=1, password_wm=1)
+bwm1.read_img('pic/ori_img.jpg')
+wm = '@guofei9987 开源万岁!'
+bwm1.read_wm(wm, mode='str')
+bwm1.embed('output/embedded.png')
+len_wm = len(bwm1.wm_bit)
+print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))
+```
+
+
+提取水印
+```python
+bwm1 = WaterMark(password_img=1, password_wm=1)
+wm_extract = bwm1.extract('output/embedded.png', wm_shape=len_wm, mode='str')
+print(wm_extract)
+```
+Output:
+>@guofei9987 开源万岁!
+
+
+### 各种攻击后的效果
+
+|攻击方式|攻击后的图片|提取的水印|
+|--|--|--|
+|旋转攻击45度||'@guofei9987 开源万岁!'|
+|随机截图||'@guofei9987 开源万岁!'|
+|多遮挡|  |'@guofei9987 开源万岁!'|
+|纵向裁剪||'@guofei9987 开源万岁!'|
+|横向裁剪||'@guofei9987 开源万岁!'|
+|缩放攻击||'@guofei9987 开源万岁!'|
+|椒盐攻击||'@guofei9987 开源万岁!'|
+|亮度攻击||'@guofei9987 开源万岁!'|
+
+
+
+### 嵌入图片
+
+参考 [代码](/examples/example_str.py)
+
+
+嵌入:
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# read original image
+bwm1.read_img('pic/ori_img.jpg')
+# read watermark
+bwm1.read_wm('pic/watermark.png')
+# embed
+bwm1.embed('output/embedded.png')
+```
+
+提取:
+```python
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# notice that wm_shape is necessary
+bwm1.extract(filename='output/embedded.png', wm_shape=(128, 128), out_wm_name='output/extracted.png', )
+```
+
+|攻击方式|攻击后的图片|提取的水印|
+|--|--|--|
+|旋转攻击45度|||
+|随机截图|||
+|多遮挡|  ||
+
+
+
+### 隐水印还可以是二进制数据
+
+参考 [代码](/examples/example_bit.py)
+
+
+作为 demo, 如果要嵌入是如下长度为6的二进制数据
+```python
+wm = [True, False, True, True, True, False]
+```
+
+嵌入水印
+
+```python
+# 除了嵌入图片,也可以嵌入比特类数据
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_img=1, password_wm=1)
+bwm1.read_ori_img('pic/ori_img.jpg')
+bwm1.read_wm([True, False, True, True, True, False], mode='bit')
+bwm1.embed('output/打上水印的图.png')
+```
+
+解水印:(注意设定水印形状 `wm_shape`)
+```python
+bwm1 = WaterMark(password_img=1, password_wm=1, wm_shape=6)
+wm_extract = bwm1.extract('output/打上水印的图.png', mode='bit')
+print(wm_extract)
+```
+
+解出的水印是一个0~1之间的实数,方便用户自行卡阈值。如果水印信息量远小于图片可容纳量,偏差极小。
+
+# 并行计算
+
+```python
+WaterMark(..., processes=None)
+```
+- `processes`: 整数,指定线程数。默认为 `None`, 表示使用全部线程。
+
+
+## 相关项目
+
+text_blind_watermark: [https://github.com/guofei9987/text_blind_watermark](https://github.com/guofei9987/text_blind_watermark)
+文本盲水印,把信息隐秘地打入文本.
diff --git a/blind_watermark/__init__.py b/blind_watermark/__init__.py
new file mode 100644
index 0000000..ceb842b
--- /dev/null
+++ b/blind_watermark/__init__.py
@@ -0,0 +1,6 @@
+from .blind_watermark import WaterMark
+from .bwm_core import WaterMarkCore
+from .att import *
+from .recover import recover_crop
+
+__version__ = '0.3.1'
diff --git a/blind_watermark/att.py b/blind_watermark/att.py
new file mode 100644
index 0000000..6eaea05
--- /dev/null
+++ b/blind_watermark/att.py
@@ -0,0 +1,199 @@
+# coding=utf-8
+
+# attack on the watermark
+import cv2
+import numpy as np
+
+
+def cut_att_height(input_filename=None, input_img=None, output_file_name=None, ratio=0.8):
+ # 纵向剪切攻击
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+ input_img_shape = input_img.shape
+ height = int(input_img_shape[0] * ratio)
+
+ output_img = input_img[:height, :, :]
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
+
+
+def cut_att_width(input_filename=None, input_img=None, output_file_name=None, ratio=0.8):
+ # 横向裁剪攻击
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+ input_img_shape = input_img.shape
+ width = int(input_img_shape[1] * ratio)
+
+ output_img = input_img[:, :width, :]
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
+
+
+def cut_att(input_filename=None, output_file_name=None, input_img=None, loc=((0.3, 0.1), (0.7, 0.9)), resize=0.6):
+ # 截屏攻击 = 裁剪攻击 + 缩放攻击 + 知道攻击参数(按照参数还原)
+ # 裁剪攻击:其它部分都补0
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+
+ output_img = input_img.copy()
+ shape = output_img.shape
+ x1, y1, x2, y2 = shape[0] * loc[0][0], shape[1] * loc[0][1], shape[0] * loc[1][0], shape[1] * loc[1][1]
+ output_img[:int(x1), :] = 255
+ output_img[int(x2):, :] = 255
+ output_img[:, :int(y1)] = 255
+ output_img[:, int(y2):] = 255
+
+ # 缩放一次,然后还原
+ output_img = cv2.resize(output_img,
+ dsize=(int(shape[1] * resize), int(shape[0] * resize))
+ )
+
+ output_img = cv2.resize(output_img, dsize=(int(shape[1]), int(shape[0])))
+
+ if output_file_name is not None:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
+
+
+def cut_att2(input_filename=None, input_img=None, output_file_name=None, loc_r=((0.3, 0.1), (0.9, 0.9)), scale=1.1):
+ # 截屏攻击 = 剪切攻击 + 缩放攻击 + 不知道攻击参数
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+ h, w, _ = input_img.shape
+
+ output_img = input_img.copy()
+ # 剪切攻击
+ x1, y1, x2, y2 = w * loc_r[0][0], h * loc_r[0][1], w * loc_r[1][0], h * loc_r[1][1]
+ x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
+ output_img = output_img[y1:y2, x1:x2]
+
+ # 缩放攻击
+ h, w, _ = output_img.shape
+ output_img = cv2.resize(output_img, dsize=(int(w * scale), int(h * scale)))
+
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img, (x1, y1, x2, y2)
+
+
+def anti_cut_att_old(input_filename, output_file_name, origin_shape):
+ # 反裁剪攻击:复制一块范围,然后补全
+ # origin_shape 分辨率与约定理解的是颠倒的,约定的是列数*行数
+ input_img = cv2.imread(input_filename)
+ output_img = input_img.copy()
+ output_img_shape = output_img.shape
+ if output_img_shape[0] > origin_shape[0] or output_img_shape[0] > origin_shape[0]:
+ print('裁剪打击后的图片,不可能比原始图片大,检查一下')
+ return
+
+ # 还原纵向打击
+ while output_img_shape[0] < origin_shape[0]:
+ output_img = np.concatenate([output_img, output_img[:origin_shape[0] - output_img_shape[0], :, :]], axis=0)
+ output_img_shape = output_img.shape
+ while output_img_shape[1] < origin_shape[1]:
+ output_img = np.concatenate([output_img, output_img[:, :origin_shape[1] - output_img_shape[1], :]], axis=1)
+ output_img_shape = output_img.shape
+
+ cv2.imwrite(output_file_name, output_img)
+
+
+def anti_cut_att(input_filename=None, input_img=None, output_file_name=None, origin_shape=None):
+ # 反裁剪攻击:补0
+ # origin_shape 分辨率与约定理解的是颠倒的,约定的是列数*行数
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+ output_img = input_img.copy()
+ output_img_shape = output_img.shape
+ if output_img_shape[0] > origin_shape[0] or output_img_shape[0] > origin_shape[0]:
+ print('裁剪打击后的图片,不可能比原始图片大,检查一下')
+ return
+
+ # 还原纵向打击
+ if output_img_shape[0] < origin_shape[0]:
+ output_img = np.concatenate(
+ [output_img, 255 * np.ones((origin_shape[0] - output_img_shape[0], output_img_shape[1], 3))]
+ , axis=0)
+ output_img_shape = output_img.shape
+
+ if output_img_shape[1] < origin_shape[1]:
+ output_img = np.concatenate(
+ [output_img, 255 * np.ones((output_img_shape[0], origin_shape[1] - output_img_shape[1], 3))]
+ , axis=1)
+
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
+
+
+def resize_att(input_filename=None, input_img=None, output_file_name=None, out_shape=(500, 500)):
+ # 缩放攻击:因为攻击和还原都是缩放,所以攻击和还原都调用这个函数
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+ output_img = cv2.resize(input_img, dsize=out_shape)
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
+
+
+def bright_att(input_filename=None, input_img=None, output_file_name=None, ratio=0.8):
+ # 亮度调整攻击,ratio应当多于0
+ # ratio>1是调得更亮,ratio<1是亮度更暗
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+ output_img = input_img * ratio
+ output_img[output_img > 255] = 255
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
+
+
+def shelter_att(input_filename=None, input_img=None, output_file_name=None, ratio=0.1, n=3):
+ # 遮挡攻击:遮挡图像中的一部分
+ # n个遮挡块
+ # 每个遮挡块所占比例为ratio
+ if input_filename:
+ output_img = cv2.imread(input_filename)
+ else:
+ output_img = input_img.copy()
+ input_img_shape = output_img.shape
+
+ for i in range(n):
+ tmp = np.random.rand() * (1 - ratio) # 随机选择一个地方,1-ratio是为了防止溢出
+ start_height, end_height = int(tmp * input_img_shape[0]), int((tmp + ratio) * input_img_shape[0])
+ tmp = np.random.rand() * (1 - ratio)
+ start_width, end_width = int(tmp * input_img_shape[1]), int((tmp + ratio) * input_img_shape[1])
+
+ output_img[start_height:end_height, start_width:end_width, :] = 255
+
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
+
+
+def salt_pepper_att(input_filename=None, input_img=None, output_file_name=None, ratio=0.01):
+ # 椒盐攻击
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+ input_img_shape = input_img.shape
+ output_img = input_img.copy()
+ for i in range(input_img_shape[0]):
+ for j in range(input_img_shape[1]):
+ if np.random.rand() < ratio:
+ output_img[i, j, :] = 255
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
+
+
+def rot_att(input_filename=None, input_img=None, output_file_name=None, angle=45):
+ # 旋转攻击
+ if input_filename:
+ input_img = cv2.imread(input_filename)
+ rows, cols, _ = input_img.shape
+ M = cv2.getRotationMatrix2D(center=(cols / 2, rows / 2), angle=angle, scale=1)
+ output_img = cv2.warpAffine(input_img, M, (cols, rows))
+ if output_file_name:
+ cv2.imwrite(output_file_name, output_img)
+ return output_img
diff --git a/blind_watermark/blind_watermark.py b/blind_watermark/blind_watermark.py
new file mode 100644
index 0000000..ca58be6
--- /dev/null
+++ b/blind_watermark/blind_watermark.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+# coding=utf-8
+# @Time : 2020/8/13
+# @Author : github.com/guofei9987
+import warnings
+
+import numpy as np
+import cv2
+
+from .bwm_core import WaterMarkCore
+
+
+class WaterMark:
+ def __init__(self, password_wm=1, password_img=1, block_shape=(4, 4), mode='common', processes=None):
+
+ self.bwm_core = WaterMarkCore(password_img=password_img, mode=mode, processes=processes)
+
+ self.password_wm = password_wm
+
+ self.wm_bit = None
+ self.wm_size = 0
+
+ def read_img(self, filename=None, img=None):
+ if filename is not None:
+ # 从文件读入图片
+ img = cv2.imread(filename, flags=cv2.IMREAD_UNCHANGED)
+ assert img is not None, "image file '{filename}' not read".format(filename=filename)
+
+ self.bwm_core.read_img_arr(img=img)
+ return img
+
+ def read_wm(self, wm_content, mode='img'):
+ assert mode in ('img', 'str', 'bit'), "mode in ('img','str','bit')"
+ if mode == 'img':
+ wm = cv2.imread(filename=wm_content, flags=cv2.IMREAD_GRAYSCALE)
+ assert wm is not None, 'file "{filename}" not read'.format(filename=wm_content)
+
+ # 读入图片格式的水印,并转为一维 bit 格式,抛弃灰度级别
+ self.wm_bit = wm.flatten() > 128
+
+ elif mode == 'str':
+ byte = bin(int(wm_content.encode('utf-8').hex(), base=16))[2:]
+ self.wm_bit = (np.array(list(byte)) == '1')
+ else:
+ self.wm_bit = np.array(wm_content)
+
+ self.wm_size = self.wm_bit.size
+
+ # 水印加密:
+ np.random.RandomState(self.password_wm).shuffle(self.wm_bit)
+
+ self.bwm_core.read_wm(self.wm_bit)
+
+ def embed(self, filename=None):
+ embed_img = self.bwm_core.embed()
+
+ if filename is not None:
+ cv2.imwrite(filename, embed_img)
+ return embed_img
+
+ def extract_decrypt(self, wm_avg):
+ wm_index = np.arange(self.wm_size)
+ np.random.RandomState(self.password_wm).shuffle(wm_index)
+ wm_avg[wm_index] = wm_avg.copy()
+ return wm_avg
+
+ def extract(self, filename=None, embed_img=None, wm_shape=None, out_wm_name=None, mode='img'):
+ assert wm_shape is not None, 'wm_shape needed'
+
+ if filename is not None:
+ embed_img = cv2.imread(filename, flags=cv2.IMREAD_COLOR)
+ assert embed_img is not None, "{filename} not read".format(filename=filename)
+
+ self.wm_size = np.array(wm_shape).prod()
+
+ if mode in ('str', 'bit'):
+ wm_avg = self.bwm_core.extract_with_kmeans(img=embed_img, wm_shape=wm_shape)
+ else:
+ wm_avg = self.bwm_core.extract(img=embed_img, wm_shape=wm_shape)
+
+ # 解密:
+ wm = self.extract_decrypt(wm_avg=wm_avg)
+
+ # 转化为指定格式:
+ if mode == 'img':
+ wm = 255 * wm.reshape(wm_shape[0], wm_shape[1])
+ cv2.imwrite(out_wm_name, wm)
+ elif mode == 'str':
+ byte = ''.join((np.round(wm)).astype(np.int).astype(np.str))
+ wm = bytes.fromhex(hex(int(byte, base=2))[2:]).decode('utf-8', errors='replace')
+
+ return wm
diff --git a/blind_watermark/bwm_core.py b/blind_watermark/bwm_core.py
new file mode 100644
index 0000000..4f4ee90
--- /dev/null
+++ b/blind_watermark/bwm_core.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+# coding=utf-8
+# @Time : 2021/12/17
+# @Author : github.com/guofei9987
+import numpy as np
+from numpy.linalg import svd
+import copy
+import cv2
+from cv2 import dct, idct
+from pywt import dwt2, idwt2
+from .pool import AutoPool
+
+
+class WaterMarkCore:
+ def __init__(self, password_img=1, mode='common', processes=None):
+ self.block_shape = np.array([4, 4])
+ self.password_img = password_img
+ self.d1, self.d2 = 36, 20 # d1/d2 越大鲁棒性越强,但输出图片的失真越大
+
+ # init data
+ self.img, self.img_YUV = None, None # self.img 是原图,self.img_YUV 对像素做了加白偶数化
+ self.ca, self.hvd, = [np.array([])] * 3, [np.array([])] * 3 # 每个通道 dct 的结果
+ self.ca_block = [np.array([])] * 3 # 每个 channel 存一个四维 array,代表四维分块后的结果
+ self.ca_part = [np.array([])] * 3 # 四维分块后,有时因不整除而少一部分,self.ca_part 是少这一部分的 self.ca
+
+ self.wm_size, self.block_num = 0, 0 # 水印的长度,原图片可插入信息的个数
+ self.pool = AutoPool(mode=mode, processes=processes)
+
+ self.fast_mode = False
+ self.alpha = None # 用于处理透明图
+
+ def init_block_index(self):
+ self.block_num = self.ca_block_shape[0] * self.ca_block_shape[1]
+ assert self.wm_size < self.block_num, IndexError(
+ '最多可嵌入{}kb信息,多于水印的{}kb信息,溢出'.format(self.block_num / 1000, self.wm_size / 1000))
+ # self.part_shape 是取整后的ca二维大小,用于嵌入时忽略右边和下面对不齐的细条部分。
+ self.part_shape = self.ca_block_shape[:2] * self.block_shape
+ self.block_index = [(i, j) for i in range(self.ca_block_shape[0]) for j in range(self.ca_block_shape[1])]
+
+ def read_img_arr(self, img):
+ # 处理透明图
+ self.alpha = None
+ if img.shape[2] == 4:
+ if img[:, :, 3].min() < 255:
+ self.alpha = img[:, :, 3]
+ img = img[:, :, :3]
+
+ # 读入图片->YUV化->加白边使像素变偶数->四维分块
+ self.img = img.astype(np.float32)
+ self.img_shape = self.img.shape[:2]
+
+ # 如果不是偶数,那么补上白边,Y(明亮度)UV(颜色)
+ self.img_YUV = cv2.copyMakeBorder(cv2.cvtColor(self.img, cv2.COLOR_BGR2YUV),
+ 0, self.img.shape[0] % 2, 0, self.img.shape[1] % 2,
+ cv2.BORDER_CONSTANT, value=(0, 0, 0))
+
+ self.ca_shape = [(i + 1) // 2 for i in self.img_shape]
+
+ self.ca_block_shape = (self.ca_shape[0] // self.block_shape[0], self.ca_shape[1] // self.block_shape[1],
+ self.block_shape[0], self.block_shape[1])
+ strides = 4 * np.array([self.ca_shape[1] * self.block_shape[0], self.block_shape[1], self.ca_shape[1], 1])
+
+ for channel in range(3):
+ self.ca[channel], self.hvd[channel] = dwt2(self.img_YUV[:, :, channel], 'haar')
+ # 转为4维度
+ self.ca_block[channel] = np.lib.stride_tricks.as_strided(self.ca[channel].astype(np.float32),
+ self.ca_block_shape, strides)
+
+ def read_wm(self, wm_bit):
+ self.wm_bit = wm_bit
+ self.wm_size = wm_bit.size
+
+ def block_add_wm(self, arg):
+ if self.fast_mode:
+ return self.block_add_wm_fast(arg)
+ else:
+ return self.block_add_wm_slow(arg)
+
+ def block_add_wm_slow(self, arg):
+ block, shuffler, i = arg
+ # dct->(flatten->加密->逆flatten)->svd->打水印->逆svd->(flatten->解密->逆flatten)->逆dct
+ wm_1 = self.wm_bit[i % self.wm_size]
+ block_dct = dct(block)
+
+ # 加密(打乱顺序)
+ block_dct_shuffled = block_dct.flatten()[shuffler].reshape(self.block_shape)
+ u, s, v = svd(block_dct_shuffled)
+ s[0] = (s[0] // self.d1 + 1 / 4 + 1 / 2 * wm_1) * self.d1
+ if self.d2:
+ s[1] = (s[1] // self.d2 + 1 / 4 + 1 / 2 * wm_1) * self.d2
+
+ block_dct_flatten = np.dot(u, np.dot(np.diag(s), v)).flatten()
+ block_dct_flatten[shuffler] = block_dct_flatten.copy()
+ return idct(block_dct_flatten.reshape(self.block_shape))
+
+ def block_add_wm_fast(self, arg):
+ # dct->svd->打水印->逆svd->逆dct
+ block, shuffler, i = arg
+ wm_1 = self.wm_bit[i % self.wm_size]
+
+ u, s, v = svd(dct(block))
+ s[0] = (s[0] // self.d1 + 1 / 4 + 1 / 2 * wm_1) * self.d1
+
+ return idct(np.dot(u, np.dot(np.diag(s), v)))
+
+ def embed(self):
+ self.init_block_index()
+
+ embed_ca = copy.deepcopy(self.ca)
+ embed_YUV = [np.array([])] * 3
+
+ self.idx_shuffle = random_strategy1(self.password_img, self.block_num,
+ self.block_shape[0] * self.block_shape[1])
+ for channel in range(3):
+ tmp = self.pool.map(self.block_add_wm,
+ [(self.ca_block[channel][self.block_index[i]], self.idx_shuffle[i], i)
+ for i in range(self.block_num)])
+
+ for i in range(self.block_num):
+ self.ca_block[channel][self.block_index[i]] = tmp[i]
+
+ # 4维分块变回2维
+ self.ca_part[channel] = np.concatenate(np.concatenate(self.ca_block[channel], 1), 1)
+ # 4维分块时右边和下边不能整除的长条保留,其余是主体部分,换成 embed 之后的频域的数据
+ embed_ca[channel][:self.part_shape[0], :self.part_shape[1]] = self.ca_part[channel]
+ # 逆变换回去
+ embed_YUV[channel] = idwt2((embed_ca[channel], self.hvd[channel]), "haar")
+
+ # 合并3通道
+ embed_img_YUV = np.stack(embed_YUV, axis=2)
+ # 之前如果不是2的整数,增加了白边,这里去除掉
+ embed_img_YUV = embed_img_YUV[:self.img_shape[0], :self.img_shape[1]]
+ embed_img = cv2.cvtColor(embed_img_YUV, cv2.COLOR_YUV2BGR)
+ embed_img = np.clip(embed_img, a_min=0, a_max=255)
+
+ if self.alpha is not None:
+ embed_img = cv2.merge([embed_img.astype(np.uint8), self.alpha])
+ return embed_img
+
+ def block_get_wm(self, args):
+ if self.fast_mode:
+ return self.block_get_wm_fast(args)
+ else:
+ return self.block_get_wm_slow(args)
+
+ def block_get_wm_slow(self, args):
+ block, shuffler = args
+ # dct->flatten->加密->逆flatten->svd->解水印
+ block_dct_shuffled = dct(block).flatten()[shuffler].reshape(self.block_shape)
+
+ u, s, v = svd(block_dct_shuffled)
+ wm = (s[0] % self.d1 > self.d1 / 2) * 1
+ if self.d2:
+ tmp = (s[1] % self.d2 > self.d2 / 2) * 1
+ wm = (wm * 3 + tmp * 1) / 4
+ return wm
+
+ def block_get_wm_fast(self, args):
+ block, shuffler = args
+ # dct->flatten->加密->逆flatten->svd->解水印
+ u, s, v = svd(dct(block))
+ wm = (s[0] % self.d1 > self.d1 / 2) * 1
+
+ return wm
+
+ def extract_raw(self, img):
+ # 每个分块提取 1 bit 信息
+ self.read_img_arr(img=img)
+ self.init_block_index()
+
+ wm_block_bit = np.zeros(shape=(3, self.block_num)) # 3个channel,length 个分块提取的水印,全都记录下来
+
+ self.idx_shuffle = random_strategy1(seed=self.password_img,
+ size=self.block_num,
+ block_shape=self.block_shape[0] * self.block_shape[1], # 16
+ )
+ for channel in range(3):
+ wm_block_bit[channel, :] = self.pool.map(self.block_get_wm,
+ [(self.ca_block[channel][self.block_index[i]], self.idx_shuffle[i])
+ for i in range(self.block_num)])
+ return wm_block_bit
+
+ def extract_avg(self, wm_block_bit):
+ # 对循环嵌入+3个 channel 求平均
+ wm_avg = np.zeros(shape=self.wm_size)
+ for i in range(self.wm_size):
+ wm_avg[i] = wm_block_bit[:, i::self.wm_size].mean()
+ return wm_avg
+
+ def extract(self, img, wm_shape):
+ self.wm_size = np.array(wm_shape).prod()
+
+ # 提取每个分块埋入的 bit:
+ wm_block_bit = self.extract_raw(img=img)
+ # 做平均:
+ wm_avg = self.extract_avg(wm_block_bit)
+ return wm_avg
+
+ def extract_with_kmeans(self, img, wm_shape):
+ wm_avg = self.extract(img=img, wm_shape=wm_shape)
+
+ return one_dim_kmeans(wm_avg)
+
+
+def one_dim_kmeans(inputs):
+ threshold = 0
+ e_tol = 10 ** (-6)
+ center = [inputs.min(), inputs.max()] # 1. 初始化中心点
+ for i in range(300):
+ threshold = (center[0] + center[1]) / 2
+ is_class01 = inputs > threshold # 2. 检查所有点与这k个点之间的距离,每个点归类到最近的中心
+ center = [inputs[~is_class01].mean(), inputs[is_class01].mean()] # 3. 重新找中心点
+ if np.abs((center[0] + center[1]) / 2 - threshold) < e_tol: # 4. 停止条件
+ threshold = (center[0] + center[1]) / 2
+ break
+
+ is_class01 = inputs > threshold
+ return is_class01
+
+
+def random_strategy1(seed, size, block_shape):
+ return np.random.RandomState(seed) \
+ .random(size=(size, block_shape)) \
+ .argsort(axis=1)
+
+
+def random_strategy2(seed, size, block_shape):
+ one_line = np.random.RandomState(seed) \
+ .random(size=(1, block_shape)) \
+ .argsort(axis=1)
+
+ return np.repeat(one_line, repeats=size, axis=0)
diff --git a/blind_watermark/cli_tools.py b/blind_watermark/cli_tools.py
new file mode 100644
index 0000000..011dec4
--- /dev/null
+++ b/blind_watermark/cli_tools.py
@@ -0,0 +1,53 @@
+from optparse import OptionParser
+from .blind_watermark import WaterMark
+
+usage1 = 'blind_watermark --embed --pwd 1234 image.jpg "watermark text" embed.png'
+usage2 = 'blind_watermark --extract --pwd 1234 --wm_shape 111 embed.png'
+optParser = OptionParser(usage=usage1 + '\n' + usage2)
+
+optParser.add_option('--embed', dest='work_mode', action='store_const', const='embed'
+ , help='Embed watermark into images')
+optParser.add_option('--extract', dest='work_mode', action='store_const', const='extract'
+ , help='Extract watermark from images')
+
+optParser.add_option('-p', '--pwd', dest='password', help='password, like 1234')
+optParser.add_option('--wm_shape', dest='wm_shape', help='Watermark shape, like 120')
+
+(opts, args) = optParser.parse_args()
+
+
+def main():
+ bwm1 = WaterMark(password_img=int(opts.password))
+ if opts.work_mode == 'embed':
+ if not len(args) == 3:
+ print('Error! Usage: ')
+ print(usage1)
+ return
+ else:
+ bwm1.read_img(args[0])
+ bwm1.read_wm(args[1], mode='str')
+ bwm1.embed(args[2])
+ print('Embed succeed! to file ', args[2])
+ print('Put down watermark size:', len(bwm1.wm_bit))
+
+ if opts.work_mode == 'extract':
+ if not len(args) == 1:
+ print('Error! Usage: ')
+ print(usage2)
+ return
+
+ else:
+ wm_str = bwm1.extract(filename=args[0], wm_shape=int(opts.wm_shape), mode='str')
+ print('Extract succeed! watermark is:')
+ print(wm_str)
+
+
+'''
+python -m blind_watermark.cli_tools --embed --pwd 1234 examples/pic/ori_img.jpeg "watermark text" examples/output/embedded.png
+python -m blind_watermark.cli_tools --extract --pwd 1234 --wm_shape 111 examples/output/embedded.png
+
+
+cd examples
+blind_watermark --embed --pwd 1234 examples/pic/ori_img.jpeg "watermark text" examples/output/embedded.png
+blind_watermark --extract --pwd 1234 --wm_shape 111 examples/output/embedded.png
+'''
diff --git a/blind_watermark/pool.py b/blind_watermark/pool.py
new file mode 100644
index 0000000..8dfa7cc
--- /dev/null
+++ b/blind_watermark/pool.py
@@ -0,0 +1,38 @@
+import sys
+import multiprocessing
+import warnings
+
+if sys.platform != 'win32':
+ multiprocessing.set_start_method('fork')
+
+
+class CommonPool(object):
+ def map(self, func, args):
+ return list(map(func, args))
+
+
+class AutoPool(object):
+ def __init__(self, mode, processes):
+
+ if mode == 'multiprocessing' and sys.platform == 'win32':
+ warnings.warn('multiprocessing not support in windows, turning to multithreading')
+ mode = 'multithreading'
+
+ self.mode = mode
+ self.processes = processes
+
+ if mode == 'vectorization':
+ pass
+ elif mode == 'cached':
+ pass
+ elif mode == 'multithreading':
+ from multiprocessing.dummy import Pool as ThreadPool
+ self.pool = ThreadPool(processes=processes)
+ elif mode == 'multiprocessing':
+ from multiprocessing import Pool
+ self.pool = Pool(processes=processes)
+ else: # common
+ self.pool = CommonPool()
+
+ def map(self, func, args):
+ return self.pool.map(func, args)
diff --git a/blind_watermark/recover.py b/blind_watermark/recover.py
new file mode 100644
index 0000000..8243363
--- /dev/null
+++ b/blind_watermark/recover.py
@@ -0,0 +1,94 @@
+import cv2
+import numpy as np
+
+import functools
+
+
+# 一个帮助缓存化加速的类,引入事实上的全局变量
+class MyValues:
+ def __init__(self):
+ self.idx = 0
+ self.image, self.template = None, None
+
+ def set_val(self, image, template):
+ self.idx += 1
+ self.image, self.template = image, template
+
+
+my_value = MyValues()
+
+
+@functools.lru_cache(maxsize=None, typed=False)
+def match_template(w, h, idx):
+ image, template = my_value.image, my_value.template
+ resized = cv2.resize(template, dsize=(w, h))
+ scores = cv2.matchTemplate(image, resized, cv2.TM_CCOEFF_NORMED)
+ ind = np.unravel_index(np.argmax(scores, axis=None), scores.shape)
+ return ind, scores[ind]
+
+
+def match_template_by_scale(scale):
+ image, template = my_value.image, my_value.template
+ w, h = int(np.round(template.shape[1] * scale)), int(template.shape[0] * scale)
+ ind, score = match_template(w, h, idx=my_value.idx)
+ return ind, score, scale
+
+
+def search_template(scale=(0.5, 2), search_num=200):
+ image, template = my_value.image, my_value.template
+ # 局部暴力搜索算法,寻找最优的scale
+ tmp = []
+ min_scale, max_scale = scale
+
+ max_scale = min(max_scale, image.shape[0] / template.shape[0], image.shape[1] / template.shape[1])
+
+ max_idx = 0
+
+ for i in range(2):
+ for scale in np.linspace(min_scale, max_scale, search_num):
+ ind, score, scale = match_template_by_scale(scale)
+ tmp.append([ind, score, scale])
+
+ # 寻找最佳
+ max_idx = 0
+ max_score = 0
+ for idx, (ind, score, scale) in enumerate(tmp):
+ if score > max_score:
+ max_idx, max_score = idx, score
+
+ min_scale, max_scale = tmp[max(0, max_idx - 1)][2], tmp[max(0, max_idx + 1)][2]
+
+ search_num = 2 * int((max_scale - min_scale) * max(template.shape[1], template.shape[0])) + 1
+
+ return tmp[max_idx]
+
+
+def estimate_crop_parameters(original_file=None, template_file=None, ori_img=None, tem_img=None
+ , scale=(0.5, 2), search_num=200):
+ # 推测攻击后的图片,在原图片中的位置、大小
+ if template_file:
+ tem_img = cv2.imread(template_file, cv2.IMREAD_GRAYSCALE) # template image
+ if original_file:
+ ori_img = cv2.imread(original_file, cv2.IMREAD_GRAYSCALE) # image
+
+ my_value.set_val(image=ori_img, template=tem_img)
+ ind, score, scale_infer = search_template(scale=scale, search_num=search_num)
+ w, h = int(tem_img.shape[1] * scale_infer), int(tem_img.shape[0] * scale_infer)
+ # x1, y1, x2, y2 = ind[0], ind[1], ind[0] + h, ind[1] + w
+ x1, y1, x2, y2 = ind[1], ind[0], ind[1] + w, ind[0] + h
+ return (x1, y1, x2, y2), ori_img.shape, score, scale_infer
+
+
+def recover_crop(template_file=None, tem_img=None, output_file_name=None, loc=None, image_o_shape=None):
+ if template_file:
+ tem_img = cv2.imread(template_file) # template image
+
+ (x1, y1, x2, y2) = loc
+
+ img_recovered = np.zeros((image_o_shape[0], image_o_shape[1], 3))
+
+ img_recovered[y1:y2, x1:x2, :] = cv2.resize(tem_img, dsize=(x2 - x1, y2 - y1))
+
+ if output_file_name:
+ cv2.imwrite(output_file_name, img_recovered)
+ return img_recovered
diff --git a/blind_watermark/requirements.txt b/blind_watermark/requirements.txt
new file mode 100644
index 0000000..2f55497
--- /dev/null
+++ b/blind_watermark/requirements.txt
@@ -0,0 +1 @@
+blind-watermark
\ No newline at end of file
diff --git a/docs/.nojekyll b/docs/.nojekyll
new file mode 100644
index 0000000..e69de29
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..d3180d2
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,22 @@
+
+# [blind_watermark](https://github.com/guofei9987/blind_watermark)
+
+[](https://pypi.org/project/blind_watermark/)
+[](https://github.com/guofei9987/blind_watermark)
+[](https://travis-ci.com/guofei9987/blind_watermark)
+[](https://codecov.io/gh/guofei9987/blind_watermark)
+[](https://pypi.org/project/blind_watermark/)
+[](https://github.com/guofei9987/blind_watermark/stargazers)
+[](https://github.com/guofei9987/blind_watermark/network/members)
+[](https://gitter.im/guofei9987/blind_watermark?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+
+
+Heuristic Algorithms in Python
+(Genetic Algorithm, Particle Swarm Optimization, Simulated Annealing, Ant Colony Algorithm, Immune Algorithm,Artificial Fish Swarm Algorithm in Python)
+
+
+- **Documentation:** [https://blind_watermark.github.io/blind_watermark/#/en/](https://blind_watermark.github.io/blind_watermark/#/en/)
+- **文档:** [https://blind_watermark.github.io/blind_watermark/#/zh/](https://blind_watermark.github.io/blind_watermark/#/zh/)
+- **Source code:** [https://github.com/guofei9987/blind_watermark](https://github.com/guofei9987/blind_watermark)
+
diff --git a/docs/_coverpage.md b/docs/_coverpage.md
new file mode 100644
index 0000000..d6cb70f
--- /dev/null
+++ b/docs/_coverpage.md
@@ -0,0 +1,11 @@
+
+
+# blind_watermark
+
+> 数字盲水印
+
+* 图片水印
+* 比特水印
+
+[GitHub](https://github.com/guofei9987/blind_watermark/)
+[Get Started](/en/README)
diff --git a/docs/_navbar.md b/docs/_navbar.md
new file mode 100644
index 0000000..e8e1c66
--- /dev/null
+++ b/docs/_navbar.md
@@ -0,0 +1,3 @@
+- Translations
+ - [:uk: English](/en/)
+ - [:cn: 中文](/zh/)
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
new file mode 100644
index 0000000..0467701
--- /dev/null
+++ b/docs/_sidebar.md
@@ -0,0 +1,3 @@
+
+* [English Document](docs/en.md)
+* [中文文档](docs/zh.md)
diff --git a/docs/en/README.md b/docs/en/README.md
new file mode 100644
index 0000000..6f29b8d
--- /dev/null
+++ b/docs/en/README.md
@@ -0,0 +1,175 @@
+
+# blind-watermark
+
+Blind watermark based on dct and svd.
+
+
+[](https://pypi.org/project/blind_watermark/)
+[](https://travis-ci.com/guofei9987/blind_watermark)
+[](https://codecov.io/gh/guofei9987/blind_watermark)
+[](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE)
+
+
+[](https://github.com/guofei9987/blind_watermark/)
+[](https://github.com/guofei9987/blind_watermark/fork)
+[](https://pepy.tech/project/blind-watermark)
+[](https://github.com/guofei9987/blind_watermark/discussions)
+
+
+- **Documentation:** [https://BlindWatermark.github.io/blind_watermark/#/en/](https://BlindWatermark.github.io/blind_watermark/#/en/)
+- **文档:** [https://BlindWatermark.github.io/blind_watermark/#/zh/](https://BlindWatermark.github.io/blind_watermark/#/zh/)
+- **中文 readme** [README_cn.md](README_cn.md)
+- **Source code:** [https://github.com/guofei9987/blind_watermark](https://github.com/guofei9987/blind_watermark)
+
+
+
+# install
+```bash
+pip install blind-watermark
+```
+
+For the current developer version:
+```bach
+git clone git@github.com:guofei9987/blind_watermark.git
+cd blind_watermark
+pip install .
+```
+
+# How to use
+
+
+### Use in bash
+
+
+```bash
+# embed watermark into image:
+blind_watermark --embed --pwd 1234 examples/pic/ori_img.jpeg "watermark text" examples/output/embedded.png
+# extract watermark from image:
+blind_watermark --extract --pwd 1234 --wm_shape 111 examples/output/embedded.png
+```
+
+
+
+## Use in Python
+
+Original Image + Watermark = Watermarked Image
+
+ + '@guofei9987 开源万岁!' = 
+
+
+See the [codes](/examples/example_str.py)
+
+Embed watermark:
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_img=1, password_wm=1)
+bwm1.read_img('pic/ori_img.jpg')
+wm = '@guofei9987 开源万岁!'
+bwm1.read_wm(wm, mode='str')
+bwm1.embed('output/embedded.png')
+len_wm = len(bwm1.wm_bit)
+print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))
+```
+
+Extract watermark:
+```python
+bwm1 = WaterMark(password_img=1, password_wm=1)
+wm_extract = bwm1.extract('output/embedded.png', wm_shape=len_wm, mode='str')
+print(wm_extract)
+```
+Output:
+>@guofei9987 开源万岁!
+
+### attacks on Watermarked Image
+
+
+|attack method|image after attack|extracted watermark|
+|--|--|--|
+|Rotate 45 Degrees||'@guofei9987 开源万岁!'|
+|Random crop||'@guofei9987 开源万岁!'|
+|Masks|  |'@guofei9987 开源万岁!'|
+|50% Horizontal cut||'@guofei9987 开源万岁!'|
+|50% Vertical cut||'@guofei9987 开源万岁!'|
+|Resize 0.5||'@guofei9987 开源万岁!'|
+|Pepper Noise||'@guofei9987 开源万岁!'|
+|Brightness 10% Down||'@guofei9987 开源万岁!'|
+
+
+
+
+
+
+### embed images
+
+embed watermark:
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# read original image
+bwm1.read_img('pic/ori_img.jpg')
+# read watermark
+bwm1.read_wm('pic/watermark.png')
+# embed
+bwm1.embed('output/embedded.png')
+```
+
+
+Extract watermark:
+```python
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# notice that wm_shape is necessary
+bwm1.extract(filename='output/embedded.png', wm_shape=(128, 128), out_wm_name='output/extracted.png', )
+```
+
+
+|attack method|image after attack|extracted watermark|
+|--|--|--|
+|Rotate 45 Degrees|||
+|Random crop|||
+|Mask|  ||
+
+
+### embed array of bits
+
+See it [here](/examples/example_bit.py)
+
+
+As demo, we embed 6 bytes data:
+```python
+wm = [True, False, True, True, True, False]
+```
+
+Embed:
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_img=1, password_wm=1)
+bwm1.read_ori_img('pic/ori_img.jpg')
+bwm1.read_wm([True, False, True, True, True, False], mode='bit')
+bwm1.embed('output/embedded.png')
+```
+
+Extract:
+```python
+bwm1 = WaterMark(password_img=1, password_wm=1, wm_shape=6)
+wm_extract = bwm1.extract('output/打上水印的图.png', mode='bit')
+print(wm_extract)
+```
+Notice that `wm_shape` (shape of watermark) is necessary
+
+The output `wm_extract` is an array of float. set a threshold such as 0.5.
+
+
+# Concurrency
+
+```python
+WaterMark(..., processes=None)
+```
+- `processes`: number of processes, can be integer. Default `None`, meaning use all processes.
+
+## Related Project
+
+text_blind_watermark: [https://github.com/guofei9987/text_blind_watermark](https://github.com/guofei9987/text_blind_watermark)
+Embed message into text.
diff --git a/docs/en/_coverpage.md b/docs/en/_coverpage.md
new file mode 100644
index 0000000..15c33a1
--- /dev/null
+++ b/docs/en/_coverpage.md
@@ -0,0 +1,22 @@
+
+
+# blind_watermark
+
+> Blind Watermark
+
+* [](https://pypi.org/project/blind_watermark/)
+[](https://travis-ci.com/guofei9987/blind_watermark)
+[](https://codecov.io/gh/guofei9987/blind_watermark)
+[](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE)
+
+
+[](https://github.com/guofei9987/blind_watermark/)
+[](https://github.com/guofei9987/blind_watermark/fork)
+[](https://pepy.tech/project/blind-watermark)
+[](https://github.com/guofei9987/blind_watermark/discussions)
+* embed a picture
+* embed a string
+* embed byte-file
+
+[GitHub](https://github.com/guofei9987/blind_watermark/)
+[Get Started](/en/README)
diff --git a/docs/en/_sidebar.md b/docs/en/_sidebar.md
new file mode 100644
index 0000000..98d56f2
--- /dev/null
+++ b/docs/en/_sidebar.md
@@ -0,0 +1,2 @@
+* [Document](en/README.md)
+
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..e881898
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,61 @@
+
+
+
+
+ blind_watermark
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/make_doc.py b/docs/make_doc.py
new file mode 100644
index 0000000..f669e1b
--- /dev/null
+++ b/docs/make_doc.py
@@ -0,0 +1,104 @@
+# 不想用 Sphinx,也不像弄一堆静态html文件,所以自己写个咯
+
+
+'''
+需要从readme中解析出:
+1. "-> Demo code: [examples/demo_pso.py](examples/demo_pso.py)"
+2. 三个```python为开头,三个 ``` 为结尾
+3. 从py文件中读出文本,并替换
+4. 前几行是求star,只在readme中出现
+
+
+需要从py文件中解析出:
+1. # %% 做断点后赋予index值,然后插入readme
+'''
+import os
+import sys
+
+import re
+
+
+def search_code(py_file_name, section_idx):
+ '''
+ 给定py文件名和section序号,返回一个list,内容是py文件中的code(markdown格式)
+ :param py_file_name:
+ :param section_idx:
+ :return:
+ '''
+ with open('../' + py_file_name, encoding='utf-8', mode="r") as f:
+ content = f.readlines()
+ content_new, i, search_idx, idx_first_match = [], 0, 0, None
+ while i < len(content) and search_idx <= section_idx:
+ if content[i].startswith('# %%'):
+ search_idx += 1
+ i += 1 # 带井号百分号的那一行也跳过去,不要放到文档里面
+ if search_idx < section_idx:
+ pass
+ elif search_idx == section_idx:
+ idx_first_match = idx_first_match or i # record first match line
+ content_new.append(content[i])
+ i += 1
+ return [
+ '-> Demo code: [{py_file_name}#s{section_idx}](https://github.com/guofei9987/blind_watermark/blob/master/{py_file_name}#L{idx_first_match})\n'.
+ format(py_file_name=py_file_name, section_idx=section_idx + 1, idx_first_match=idx_first_match),
+ '```python\n'] \
+ + content_new \
+ + ['```\n']
+
+
+# %%
+
+
+def make_doc(origin_file):
+ with open(origin_file, encoding='utf-8', mode="r") as f_readme:
+ readme = f_readme.readlines()
+
+ regex = re.compile('\[examples/[\w#.]+\]')
+ readme_idx = 0
+ readme_new = []
+ while readme_idx < len(readme):
+ readme_line = readme[readme_idx]
+ if readme_line.startswith('-> Demo code: ['):
+ # 找到中括号里面的内容,解析为文件名,section号
+ py_file_name, section_idx = regex.findall(readme[readme_idx])[0][1:-1].split('#s')
+ section_idx = int(section_idx) - 1
+
+ print('插入代码: ', py_file_name, section_idx)
+ content_new = search_code(py_file_name, section_idx)
+ readme_new.extend(content_new)
+
+ # 往下寻找第一个代码结束位置
+ while readme[readme_idx] != '```\n':
+ readme_idx += 1
+ else:
+ # 如果不需要插入代码,就用原本的内容
+ readme_new.append(readme_line)
+
+ readme_idx += 1
+ return readme_new
+
+
+# 主页 README 和 en/README
+readme_new = make_doc(origin_file='../README.md')
+with open('../README.md', encoding='utf-8', mode="w") as f_readme:
+ f_readme.writelines(readme_new)
+
+with open('en/README.md', encoding='utf-8', mode="w") as f_readme_en:
+ f_readme_en.writelines(readme_new[20:])
+
+# 跟目录的 README_cn.md 和 zh/README.md
+readme_zh = make_doc(origin_file='../README_cn.md')
+with open('../README_cn.md', encoding='utf-8', mode="w") as f_readme:
+ f_readme.writelines(readme_zh)
+
+with open('zh/README.md', encoding='utf-8', mode="w") as f_readme_en:
+ f_readme_en.writelines(readme_zh)
+
+# docs = ['zh/README.md','en/README.md'
+# ]
+# for i in docs:
+# docs_new = make_doc(origin_file=i)
+# with open(i, encoding='utf-8', mode="w") as f:
+# f.writelines(docs_new)
+
+# sys.exit()
diff --git a/docs/run_server.bat b/docs/run_server.bat
new file mode 100644
index 0000000..7175c07
--- /dev/null
+++ b/docs/run_server.bat
@@ -0,0 +1 @@
+docsify serve
\ No newline at end of file
diff --git a/docs/vue.css b/docs/vue.css
new file mode 100644
index 0000000..c7a3140
--- /dev/null
+++ b/docs/vue.css
@@ -0,0 +1,939 @@
+@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");
+
+* {
+ -webkit-font-smoothing: antialiased;
+ -webkit-overflow-scrolling: touch;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ -webkit-text-size-adjust: none;
+ -webkit-touch-callout: none;
+ box-sizing: border-box
+}
+
+body:not(.ready) {
+ overflow: hidden
+}
+
+body:not(.ready) .app-nav, body:not(.ready) > nav, body:not(.ready) [data-cloak] {
+ display: none
+}
+
+div#app {
+ font-size: 30px;
+ font-weight: lighter;
+ margin: 40vh auto;
+ text-align: center
+}
+
+div#app:empty:before {
+ content: "Loading..."
+}
+
+.emoji {
+ height: 1.2rem;
+ vertical-align: middle
+}
+
+.progress {
+ background-color: var(--theme-color, #42b983);
+ height: 2px;
+ left: 0;
+ position: fixed;
+ right: 0;
+ top: 0;
+ transition: width .2s, opacity .4s;
+ width: 0;
+ z-index: 5
+}
+
+.search .search-keyword, .search a:hover {
+ color: var(--theme-color, #42b983)
+}
+
+.search .search-keyword {
+ font-style: normal;
+ font-weight: 700
+}
+
+body, html {
+ height: 100%
+}
+
+body {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ color: #34495e;
+ font-family: Source Sans Pro, Helvetica Neue, Arial, sans-serif;
+ font-size: 15px;
+ letter-spacing: 0;
+ margin: 0;
+ overflow-x: hidden
+}
+
+img {
+ max-width: 100%
+}
+
+a[disabled] {
+ cursor: not-allowed;
+ opacity: .6
+}
+
+kbd {
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ display: inline-block;
+ font-size: 12px !important;
+ line-height: 12px;
+ margin-bottom: 3px;
+ padding: 3px 5px;
+ vertical-align: middle
+}
+
+li input[type=checkbox] {
+ margin: 0 .2em .25em 0;
+ vertical-align: middle
+}
+
+.app-nav {
+ margin: 25px 60px 0 0;
+ position: absolute;
+ right: 0;
+ text-align: right;
+ z-index: 2
+}
+
+.app-nav.no-badge {
+ margin-right: 0px
+}
+
+.app-nav.no-badge ul{
+ padding: 0 0 0 0
+}
+
+.app-nav p {
+ margin: 0
+}
+
+.app-nav > a {
+ margin: 0 1rem;
+ padding: 5px 0
+}
+
+.app-nav li, .app-nav ul {
+ display: inline-block;
+ list-style: none;
+ margin: 0
+}
+
+.app-nav a {
+ color: inherit;
+ font-size: 16px;
+ text-decoration: none;
+ transition: color .3s
+}
+
+.app-nav a.active, .app-nav a:hover {
+ color: var(--theme-color, #42b983)
+}
+
+.app-nav a.active {
+ border-bottom: 2px solid var(--theme-color, #42b983)
+}
+
+.app-nav li {
+ display: inline-block;
+ margin: 0 1rem;
+ padding: 5px 0;
+ position: relative
+}
+
+.app-nav li ul {
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-bottom-color: #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ display: none;
+ max-height: calc(100vh - 61px);
+ overflow-y: auto;
+ padding: 10px 0;
+ position: absolute;
+ right: -15px;
+ text-align: left;
+ top: 100%;
+ white-space: nowrap
+}
+
+.app-nav li ul li {
+ display: block;
+ font-size: 14px;
+ line-height: 1rem;
+ margin: 0;
+ margin: 8px 14px;
+ white-space: nowrap
+}
+
+.app-nav li ul a {
+ display: block;
+ font-size: inherit;
+ margin: 0;
+ padding: 0
+}
+
+.app-nav li ul a.active {
+ border-bottom: 0
+}
+
+.app-nav li:hover ul {
+ display: block
+}
+
+.github-corner {
+ border-bottom: 0;
+ position: fixed;
+ right: 0;
+ text-decoration: none;
+ top: 0;
+ z-index: 1
+}
+
+.github-corner:hover .octo-arm {
+ animation: a .56s ease-in-out
+}
+
+.github-corner svg {
+ color: #fff;
+ fill: var(--theme-color, #42b983);
+ height: 80px;
+ width: 80px
+}
+
+main {
+ display: block;
+ position: relative;
+ width: 100vw;
+ height: 100%;
+ z-index: 0
+}
+
+main.hidden {
+ display: none
+}
+
+.anchor {
+ display: inline-block;
+ text-decoration: none;
+ transition: all .3s
+}
+
+.anchor span {
+ color: #34495e
+}
+
+.anchor:hover {
+ text-decoration: underline
+}
+
+.sidebar {
+ border-right: 1px solid rgba(0, 0, 0, .07);
+ overflow-y: auto;
+ padding: 40px 0 0;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ transition: transform .25s ease-out;
+ width: 300px;
+ z-index: 3
+}
+
+.sidebar > h1 {
+ margin: 0 auto 1rem;
+ font-size: 1.5rem;
+ font-weight: 300;
+ text-align: center
+}
+
+.sidebar > h1 a {
+ color: inherit;
+ text-decoration: none
+}
+
+.sidebar > h1 .app-nav {
+ display: block;
+ position: static
+}
+
+.sidebar .sidebar-nav {
+ line-height: 1em;
+ padding-bottom: 40px
+}
+
+.sidebar li.collapse .app-sub-sidebar {
+ display: none
+}
+
+.sidebar ul {
+ margin: 0 0 0 15px;
+ padding: 0
+}
+
+.sidebar li > p {
+ font-weight: 700;
+ margin: 0
+}
+
+.sidebar ul, .sidebar ul li {
+ list-style: none
+}
+
+.sidebar ul li a {
+ border-bottom: none;
+ display: block
+}
+
+.sidebar ul li ul {
+ padding-left: 20px
+}
+
+.sidebar::-webkit-scrollbar {
+ width: 4px
+}
+
+.sidebar::-webkit-scrollbar-thumb {
+ background: transparent;
+ border-radius: 4px
+}
+
+.sidebar:hover::-webkit-scrollbar-thumb {
+ background: hsla(0, 0%, 53%, .4)
+}
+
+.sidebar:hover::-webkit-scrollbar-track {
+ background: hsla(0, 0%, 53%, .1)
+}
+
+.sidebar-toggle {
+ background-color: transparent;
+ background-color: hsla(0, 0%, 100%, .8);
+ border: 0;
+ outline: none;
+ padding: 10px;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ text-align: center;
+ transition: opacity .3s;
+ width: 284px;
+ z-index: 4
+}
+
+.sidebar-toggle .sidebar-toggle-button:hover {
+ opacity: .4
+}
+
+.sidebar-toggle span {
+ background-color: var(--theme-color, #42b983);
+ display: block;
+ margin-bottom: 4px;
+ width: 16px;
+ height: 2px
+}
+
+body.sticky .sidebar, body.sticky .sidebar-toggle {
+ position: fixed
+}
+
+.content {
+ padding-top: 60px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 300px;
+ transition: left .25s ease
+}
+
+.markdown-section {
+ /*margin: 0 auto;*/
+ margin: 1em auto;
+ max-width: 1300px;
+ padding: 30px 0 40px 0;
+ position: relative
+}
+
+.markdown-section > * {
+ box-sizing: border-box;
+ font-size: inherit
+}
+
+.markdown-section > :first-child {
+ margin-top: 0 !important
+}
+
+.markdown-section hr {
+ border: none;
+ border-bottom: 1px solid #eee;
+ margin: 2em 0
+}
+
+.markdown-section iframe {
+ border: 1px solid #eee
+}
+
+.markdown-section table {
+ border-collapse: collapse;
+ border-spacing: 0;
+ display: block;
+ margin-bottom: 1rem;
+ overflow: auto;
+ width: 100%
+}
+
+.markdown-section th {
+ font-weight: 700
+}
+
+.markdown-section td, .markdown-section th {
+ border: 1px solid #ddd;
+ padding: 6px 13px
+}
+
+.markdown-section tr {
+ border-top: 1px solid #ccc
+}
+
+.markdown-section p.tip, .markdown-section tr:nth-child(2n) {
+ background-color: #f8f8f8
+}
+
+.markdown-section p.tip {
+ border-bottom-right-radius: 2px;
+ border-left: 4px solid #f66;
+ border-top-right-radius: 2px;
+ margin: 2em 0;
+ padding: 12px 24px 12px 30px;
+ position: relative
+}
+
+.markdown-section p.tip:before {
+ background-color: #f66;
+ border-radius: 100%;
+ color: #fff;
+ content: "!";
+ font-family: Dosis, Source Sans Pro, Helvetica Neue, Arial, sans-serif;
+ font-size: 14px;
+ font-weight: 700;
+ left: -12px;
+ line-height: 20px;
+ position: absolute;
+ height: 20px;
+ width: 20px;
+ text-align: center;
+ top: 14px
+}
+
+.markdown-section p.tip code {
+ background-color: #efefef
+}
+
+.markdown-section p.tip em {
+ color: #34495e
+}
+
+.markdown-section p.warn {
+ background: rgba(66, 185, 131, .1);
+ border-radius: 2px;
+ padding: 1rem
+}
+
+.markdown-section ul.task-list > li {
+ list-style-type: none
+}
+
+body.close .sidebar {
+ transform: translateX(-300px)
+}
+
+body.close .sidebar-toggle {
+ width: auto
+}
+
+body.close .content {
+ left: 0
+}
+
+@media print {
+ .app-nav, .github-corner, .sidebar, .sidebar-toggle {
+ display: none
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .github-corner, .sidebar, .sidebar-toggle {
+ position: fixed
+ }
+
+ .app-nav {
+ margin-top: 16px
+ }
+
+ .app-nav li ul {
+ top: 30px
+ }
+
+ main {
+ height: auto;
+ overflow-x: hidden
+ }
+
+ .sidebar {
+ left: -300px;
+ transition: transform .25s ease-out
+ }
+
+ .content {
+ left: 0;
+ max-width: 100vw;
+ position: static;
+ padding-top: 20px;
+ transition: transform .25s ease
+ }
+
+ .app-nav, .github-corner {
+ transition: transform .25s ease-out
+ }
+
+ .sidebar-toggle {
+ background-color: transparent;
+ width: auto;
+ padding: 30px 30px 10px 10px
+ }
+
+ body.close .sidebar {
+ transform: translateX(300px)
+ }
+
+ body.close .sidebar-toggle {
+ background-color: hsla(0, 0%, 100%, .8);
+ transition: background-color 1s;
+ width: 284px;
+ padding: 10px
+ }
+
+ body.close .content {
+ transform: translateX(300px)
+ }
+
+ body.close .app-nav, body.close .github-corner {
+ display: none
+ }
+
+ .github-corner:hover .octo-arm {
+ animation: none
+ }
+
+ .github-corner .octo-arm {
+ animation: a .56s ease-in-out
+ }
+}
+
+@keyframes a {
+ 0%, to {
+ transform: rotate(0)
+ }
+ 20%, 60% {
+ transform: rotate(-25deg)
+ }
+ 40%, 80% {
+ transform: rotate(10deg)
+ }
+}
+
+section.cover {
+ -ms-flex-align: center;
+ align-items: center;
+ background-position: 50%;
+ background-repeat: no-repeat;
+ background-size: cover;
+ height: 100vh;
+ display: none
+}
+
+section.cover.show {
+ display: -ms-flexbox;
+ display: flex
+}
+
+section.cover.has-mask .mask {
+ background-color: #fff;
+ opacity: .8;
+ position: absolute;
+ top: 0;
+ height: 100%;
+ width: 100%
+}
+
+section.cover .cover-main {
+ -ms-flex: 1;
+ flex: 1;
+ margin: -20px 16px 0;
+ text-align: center;
+ z-index: 1
+}
+
+section.cover a {
+ color: inherit
+}
+
+section.cover a, section.cover a:hover {
+ text-decoration: none
+}
+
+section.cover p {
+ line-height: 1.5rem;
+ margin: 1em 0
+}
+
+section.cover h1 {
+ color: inherit;
+ font-size: 2.5rem;
+ font-weight: 300;
+ margin: .625rem 0 2.5rem;
+ position: relative;
+ text-align: center
+}
+
+section.cover h1 a {
+ display: block
+}
+
+section.cover h1 small {
+ bottom: -.4375rem;
+ font-size: 1rem;
+ position: absolute
+}
+
+section.cover blockquote {
+ font-size: 1.5rem;
+ text-align: center
+}
+
+section.cover ul {
+ line-height: 1.8;
+ list-style-type: none;
+ margin: 1em auto;
+ max-width: 500px;
+ padding: 0
+}
+
+section.cover .cover-main > p:last-child a {
+ border: 1px solid var(--theme-color, #42b983);
+ border-radius: 2rem;
+ box-sizing: border-box;
+ color: var(--theme-color, #42b983);
+ display: inline-block;
+ font-size: 1.05rem;
+ letter-spacing: .1rem;
+ margin: .5rem 1rem;
+ padding: .75em 2rem;
+ text-decoration: none;
+ transition: all .15s ease
+}
+
+section.cover .cover-main > p:last-child a:last-child {
+ background-color: var(--theme-color, #42b983);
+ color: #fff
+}
+
+section.cover .cover-main > p:last-child a:last-child:hover {
+ color: inherit;
+ opacity: .8
+}
+
+section.cover .cover-main > p:last-child a:hover {
+ color: inherit
+}
+
+section.cover blockquote > p > a {
+ border-bottom: 2px solid var(--theme-color, #42b983);
+ transition: color .3s
+}
+
+section.cover blockquote > p > a:hover {
+ color: var(--theme-color, #42b983)
+}
+
+.sidebar, body {
+ background-color: #fff
+}
+
+.sidebar {
+ color: #364149
+}
+
+.sidebar li {
+ margin: 6px 0
+}
+
+.sidebar ul li a {
+ color: #505d6b;
+ font-size: 14px;
+ font-weight: 400;
+ overflow: hidden;
+ text-decoration: none;
+ text-overflow: ellipsis;
+ white-space: nowrap
+}
+
+.sidebar ul li a:hover {
+ text-decoration: underline
+}
+
+.sidebar ul li ul {
+ padding: 0
+}
+
+.sidebar ul li.active > a {
+ border-right: 2px solid;
+ color: var(--theme-color, #42b983);
+ font-weight: 600
+}
+
+.app-sub-sidebar li:before {
+ content: "";
+ padding-right: 4px;
+ float: left
+}
+
+.markdown-section h1, .markdown-section h2, .markdown-section h3, .markdown-section h4 {
+ color: #2c3e50;
+ font-weight: 600
+}
+
+.markdown-section strong {
+ color: red;
+ font-weight: 600
+}
+
+.markdown-section a {
+ color: var(--theme-color, #42b983);
+ font-weight: 600
+}
+
+
+.markdown-section h1 {
+ font-size: 2.25rem;
+ margin: 0 0 1rem
+}
+
+.markdown-section h2 {
+ background: #c0cda5;
+ font-size: 2.25rem;
+ margin: 45px 0 .8rem
+}
+
+.markdown-section h3,h4,h5 {
+ background: #e5f7f3;
+}
+
+.markdown-section h3 {
+ font-size: 1.5rem;
+ margin: 40px 0 .6rem
+}
+
+.markdown-section h4 {
+ font-size: 1rem
+}
+
+.markdown-section h5 {
+ font-size: 0.875rem
+}
+
+.markdown-section h6 {
+ color: #777;
+ font-size: 0.75rem
+}
+
+.markdown-section figure, .markdown-section p {
+ margin: 1.5em 0 0 0
+ /*上右下左*/
+}
+
+.markdown-section ol, .markdown-section p, .markdown-section ul {
+ line-height: 1.6rem;
+ word-spacing: .05rem
+}
+
+.markdown-section ol, .markdown-section ul {
+ margin-top: 0px;
+ padding-left: 1.5rem
+}
+
+.markdown-section blockquote {
+ border-left: 8px solid var(--theme-color, #42b983);
+ color: #858585;
+ margin: 0 0;
+ padding-left: 20px
+}
+
+.markdown-section blockquote p {
+ font-weight: 600;
+ margin-left: 0;
+ margin-top: 0
+}
+
+.markdown-section iframe {
+ margin: 1em 0
+}
+
+.markdown-section em {
+ color: #7f8c8d
+}
+
+.markdown-section code {
+ border-radius: 2px;
+ color: #e96900;
+ font-size: .8rem;
+ margin: 0 2px;
+ padding: 3px 5px;
+ white-space: pre-wrap
+}
+
+.markdown-section code, .markdown-section pre {
+ background-color: #f8f8f8;
+ font-family: Roboto Mono, Monaco, courier, monospace
+}
+
+.markdown-section pre {
+ -moz-osx-font-smoothing: initial;
+ -webkit-font-smoothing: initial;
+ line-height: 1.5rem;
+ margin: 1.2em 0;
+ overflow: auto;
+ padding: 0 1.4rem;
+ position: relative;
+ word-wrap: normal
+}
+
+.token.cdata, .token.comment, .token.doctype, .token.prolog {
+ color: #8e908c
+}
+
+.token.namespace {
+ opacity: .7
+}
+
+.token.boolean, .token.number {
+ color: #c76b29
+}
+
+.token.punctuation {
+ color: #525252
+}
+
+.token.property {
+ color: #c08b30
+}
+
+.token.tag {
+ color: #2973b7
+}
+
+.token.string {
+ color: var(--theme-color, #42b983)
+}
+
+.token.selector {
+ color: #6679cc
+}
+
+.token.attr-name {
+ color: #2973b7
+}
+
+.language-css .token.string, .style .token.string, .token.entity, .token.url {
+ color: #22a2c9
+}
+
+.token.attr-value, .token.control, .token.directive, .token.unit {
+ color: var(--theme-color, #42b983)
+}
+
+.token.function, .token.keyword {
+ color: #e96900
+}
+
+.token.atrule, .token.regex, .token.statement {
+ color: #22a2c9
+}
+
+.token.placeholder, .token.variable {
+ color: #3d8fd1
+}
+
+.token.deleted {
+ text-decoration: line-through
+}
+
+.token.inserted {
+ border-bottom: 1px dotted #202746;
+ text-decoration: none
+}
+
+.token.italic {
+ font-style: italic
+}
+
+.token.bold, .token.important {
+ font-weight: 700
+}
+
+.token.important {
+ color: #c94922
+}
+
+.token.entity {
+ cursor: help
+}
+
+.markdown-section pre > code {
+ -moz-osx-font-smoothing: initial;
+ -webkit-font-smoothing: initial;
+ background-color: #f8f8f8;
+ border-radius: 2px;
+ color: #525252;
+ display: block;
+ font-family: Roboto Mono, Monaco, courier, monospace;
+ font-size: .8rem;
+ line-height: inherit;
+ margin: 0 2px;
+ max-width: inherit;
+ overflow: inherit;
+ padding: 2.2em 5px;
+ white-space: inherit
+}
+
+.markdown-section code:after, .markdown-section code:before {
+ letter-spacing: .05rem
+}
+
+code .token {
+ -moz-osx-font-smoothing: initial;
+ -webkit-font-smoothing: initial;
+ min-height: 1.5rem
+}
+
+pre:after {
+ color: #ccc;
+ content: attr(data-lang);
+ font-size: .6rem;
+ font-weight: 600;
+ height: 15px;
+ line-height: 15px;
+ padding: 5px 10px 0;
+ position: absolute;
+ right: 0;
+ text-align: right;
+ top: 0
+}
diff --git a/docs/zh/README.md b/docs/zh/README.md
new file mode 100644
index 0000000..cfea9af
--- /dev/null
+++ b/docs/zh/README.md
@@ -0,0 +1,173 @@
+# blind-watermark
+
+基于频域的数字盲水印
+
+
+[](https://pypi.org/project/blind_watermark/)
+[](https://travis-ci.com/guofei9987/blind_watermark)
+[](https://codecov.io/gh/guofei9987/blind_watermark)
+[](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE)
+
+
+[](https://github.com/guofei9987/blind_watermark/)
+[](https://github.com/guofei9987/blind_watermark/fork)
+[](https://pepy.tech/project/blind-watermark)
+[](https://github.com/guofei9987/blind_watermark/discussions)
+
+
+- **Documentation:** [https://BlindWatermark.github.io/blind_watermark/#/en/](https://BlindWatermark.github.io/blind_watermark/#/en/)
+- **文档:** [https://BlindWatermark.github.io/blind_watermark/#/zh/](https://BlindWatermark.github.io/blind_watermark/#/zh/)
+- **English readme** [README.md](README.md)
+- **Source code:** [https://github.com/guofei9987/blind_watermark](https://github.com/guofei9987/blind_watermark)
+
+# 安装
+```bash
+pip install blind-watermark
+```
+
+或者安装最新开发版本
+```bach
+git clone git@github.com:guofei9987/blind_watermark.git
+cd blind_watermark
+pip install .
+```
+
+# 如何使用
+
+### 命令行中使用
+
+```bash
+# 嵌入水印:
+blind_watermark --embed --pwd 1234 examples/pic/ori_img.jpeg "watermark text" examples/output/embedded.png
+# 提取水印:
+blind_watermark --extract --pwd 1234 --wm_shape 111 examples/output/embedded.png
+```
+
+
+
+## Python 中使用
+
+原图 + 水印 = 打上水印的图
+
+ + '@guofei9987 开源万岁!' = 
+
+
+
+参考 [代码](/examples/example_str.py)
+
+
+嵌入水印
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_img=1, password_wm=1)
+bwm1.read_img('pic/ori_img.jpg')
+wm = '@guofei9987 开源万岁!'
+bwm1.read_wm(wm, mode='str')
+bwm1.embed('output/embedded.png')
+len_wm = len(bwm1.wm_bit)
+print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))
+```
+
+
+提取水印
+```python
+bwm1 = WaterMark(password_img=1, password_wm=1)
+wm_extract = bwm1.extract('output/embedded.png', wm_shape=len_wm, mode='str')
+print(wm_extract)
+```
+Output:
+>@guofei9987 开源万岁!
+
+
+### 各种攻击后的效果
+
+|攻击方式|攻击后的图片|提取的水印|
+|--|--|--|
+|旋转攻击45度||'@guofei9987 开源万岁!'|
+|随机截图||'@guofei9987 开源万岁!'|
+|多遮挡|  |'@guofei9987 开源万岁!'|
+|横向裁剪50%||'@guofei9987 开源万岁!'|
+|纵向裁剪50%||'@guofei9987 开源万岁!'|
+|缩放攻击||'@guofei9987 开源万岁!'|
+|椒盐攻击||'@guofei9987 开源万岁!'|
+|亮度攻击||'@guofei9987 开源万岁!'|
+
+
+
+### 嵌入图片
+
+参考 [代码](/examples/example_str.py)
+
+
+嵌入:
+```python
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# read original image
+bwm1.read_img('pic/ori_img.jpg')
+# read watermark
+bwm1.read_wm('pic/watermark.png')
+# embed
+bwm1.embed('output/embedded.png')
+```
+
+提取:
+```python
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# notice that wm_shape is necessary
+bwm1.extract(filename='output/embedded.png', wm_shape=(128, 128), out_wm_name='output/extracted.png', )
+```
+
+|攻击方式|攻击后的图片|提取的水印|
+|--|--|--|
+|旋转攻击45度|||
+|随机截图|||
+|多遮挡|  ||
+
+
+
+### 隐水印还可以是二进制数据
+
+参考 [代码](/examples/example_bit.py)
+
+
+作为 demo, 如果要嵌入是如下长度为6的二进制数据
+```python
+wm = [True, False, True, True, True, False]
+```
+
+嵌入水印
+
+```python
+# 除了嵌入图片,也可以嵌入比特类数据
+from blind_watermark import WaterMark
+
+bwm1 = WaterMark(password_img=1, password_wm=1)
+bwm1.read_ori_img('pic/ori_img.jpg')
+bwm1.read_wm([True, False, True, True, True, False], mode='bit')
+bwm1.embed('output/打上水印的图.png')
+```
+
+解水印:(注意设定水印形状 `wm_shape`)
+```python
+bwm1 = WaterMark(password_img=1, password_wm=1, wm_shape=6)
+wm_extract = bwm1.extract('output/打上水印的图.png', mode='bit')
+print(wm_extract)
+```
+
+解出的水印是一个0~1之间的实数,方便用户自行卡阈值。如果水印信息量远小于图片可容纳量,偏差极小。
+
+# 并行计算
+
+```python
+WaterMark(..., processes=None)
+```
+- `processes`: 整数,指定线程数。默认为 `None`, 表示使用全部线程。
+
+
+## 相关项目
+
+text_blind_watermark: [https://github.com/guofei9987/text_blind_watermark](https://github.com/guofei9987/text_blind_watermark)
+文本盲水印,把信息隐秘地打入文本.
diff --git a/docs/zh/_coverpage.md b/docs/zh/_coverpage.md
new file mode 100644
index 0000000..af20a27
--- /dev/null
+++ b/docs/zh/_coverpage.md
@@ -0,0 +1,22 @@
+
+
+# blind_watermark
+
+> 数字盲水印
+
+* [](https://pypi.org/project/blind_watermark/)
+[](https://travis-ci.com/guofei9987/blind_watermark)
+[](https://codecov.io/gh/guofei9987/blind_watermark)
+[](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE)
+
+
+[](https://github.com/guofei9987/blind_watermark/)
+[](https://github.com/guofei9987/blind_watermark/fork)
+[](https://pepy.tech/project/blind-watermark)
+[](https://github.com/guofei9987/blind_watermark/discussions)
+* 嵌入图片
+* 嵌入文本
+* 嵌入二进制
+
+[GitHub](https://github.com/guofei9987/blind_watermark/)
+[开始](/zh/README)
diff --git a/docs/zh/_sidebar.md b/docs/zh/_sidebar.md
new file mode 100644
index 0000000..766f183
--- /dev/null
+++ b/docs/zh/_sidebar.md
@@ -0,0 +1,2 @@
+* [文档](zh/README.md)
+
diff --git "a/docs/\344\272\256\345\272\246\346\224\273\345\207\273.jpg" "b/docs/\344\272\256\345\272\246\346\224\273\345\207\273.jpg"
new file mode 100644
index 0000000..22b46f8
Binary files /dev/null and "b/docs/\344\272\256\345\272\246\346\224\273\345\207\273.jpg" differ
diff --git "a/docs/\344\272\256\345\272\246\350\260\203\344\275\216\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" "b/docs/\344\272\256\345\272\246\350\260\203\344\275\216\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..d8bdeeb
Binary files /dev/null and "b/docs/\344\272\256\345\272\246\350\260\203\344\275\216\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" differ
diff --git "a/docs/\344\272\256\345\272\246\350\260\203\351\253\230\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" "b/docs/\344\272\256\345\272\246\350\260\203\351\253\230\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..0505dce
Binary files /dev/null and "b/docs/\344\272\256\345\272\246\350\260\203\351\253\230\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" differ
diff --git "a/docs/\345\216\237\345\233\276.jpeg" "b/docs/\345\216\237\345\233\276.jpeg"
new file mode 100644
index 0000000..50b0580
Binary files /dev/null and "b/docs/\345\216\237\345\233\276.jpeg" differ
diff --git "a/docs/\345\244\232\351\201\256\346\214\241\346\224\273\345\207\273.jpg" "b/docs/\345\244\232\351\201\256\346\214\241\346\224\273\345\207\273.jpg"
new file mode 100644
index 0000000..d7eab43
Binary files /dev/null and "b/docs/\345\244\232\351\201\256\346\214\241\346\224\273\345\207\273.jpg" differ
diff --git "a/docs/\345\244\232\351\201\256\346\214\241\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" "b/docs/\345\244\232\351\201\256\346\214\241\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..0b38cd5
Binary files /dev/null and "b/docs/\345\244\232\351\201\256\346\214\241\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" differ
diff --git "a/docs/\346\210\252\345\261\217\346\224\273\345\207\2732_\350\277\230\345\216\237.jpg" "b/docs/\346\210\252\345\261\217\346\224\273\345\207\2732_\350\277\230\345\216\237.jpg"
new file mode 100644
index 0000000..aabd456
Binary files /dev/null and "b/docs/\346\210\252\345\261\217\346\224\273\345\207\2732_\350\277\230\345\216\237.jpg" differ
diff --git "a/docs/\346\211\223\344\270\212\346\260\264\345\215\260\347\232\204\345\233\276.jpg" "b/docs/\346\211\223\344\270\212\346\260\264\345\215\260\347\232\204\345\233\276.jpg"
new file mode 100644
index 0000000..b9f0dcb
Binary files /dev/null and "b/docs/\346\211\223\344\270\212\346\260\264\345\215\260\347\232\204\345\233\276.jpg" differ
diff --git "a/docs/\346\227\213\350\275\254\346\224\273\345\207\273.jpg" "b/docs/\346\227\213\350\275\254\346\224\273\345\207\273.jpg"
new file mode 100644
index 0000000..733ea2f
Binary files /dev/null and "b/docs/\346\227\213\350\275\254\346\224\273\345\207\273.jpg" differ
diff --git "a/docs/\346\227\213\350\275\254\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" "b/docs/\346\227\213\350\275\254\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..adc5f18
Binary files /dev/null and "b/docs/\346\227\213\350\275\254\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" differ
diff --git "a/docs/\346\244\222\347\233\220\346\224\273\345\207\273.jpg" "b/docs/\346\244\222\347\233\220\346\224\273\345\207\273.jpg"
new file mode 100644
index 0000000..5249cf6
Binary files /dev/null and "b/docs/\346\244\222\347\233\220\346\224\273\345\207\273.jpg" differ
diff --git "a/docs/\346\244\222\347\233\220\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" "b/docs/\346\244\222\347\233\220\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..2ca4fb1
Binary files /dev/null and "b/docs/\346\244\222\347\233\220\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" differ
diff --git "a/docs/\346\250\252\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\345\241\253\350\241\245.jpg" "b/docs/\346\250\252\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\345\241\253\350\241\245.jpg"
new file mode 100644
index 0000000..0167dc6
Binary files /dev/null and "b/docs/\346\250\252\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\345\241\253\350\241\245.jpg" differ
diff --git "a/docs/\346\250\252\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" "b/docs/\346\250\252\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..ba9daf5
Binary files /dev/null and "b/docs/\346\250\252\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" differ
diff --git "a/docs/\346\260\264\345\215\260.png" "b/docs/\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..b2fdd40
Binary files /dev/null and "b/docs/\346\260\264\345\215\260.png" differ
diff --git "a/docs/\347\272\265\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\345\241\253\350\241\245.jpg" "b/docs/\347\272\265\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\345\241\253\350\241\245.jpg"
new file mode 100644
index 0000000..7a05f20
Binary files /dev/null and "b/docs/\347\272\265\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\345\241\253\350\241\245.jpg" differ
diff --git "a/docs/\347\272\265\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" "b/docs/\347\272\265\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..c341a20
Binary files /dev/null and "b/docs/\347\272\265\345\220\221\350\243\201\345\211\252\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" differ
diff --git "a/docs/\347\274\251\346\224\276\346\224\273\345\207\273.jpg" "b/docs/\347\274\251\346\224\276\346\224\273\345\207\273.jpg"
new file mode 100644
index 0000000..79b2a71
Binary files /dev/null and "b/docs/\347\274\251\346\224\276\346\224\273\345\207\273.jpg" differ
diff --git "a/docs/\347\274\251\346\224\276\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" "b/docs/\347\274\251\346\224\276\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..0080bbd
Binary files /dev/null and "b/docs/\347\274\251\346\224\276\346\224\273\345\207\273_\346\217\220\345\217\226\346\260\264\345\215\260.png" differ
diff --git "a/docs/\350\247\243\345\207\272\347\232\204\346\260\264\345\215\260.png" "b/docs/\350\247\243\345\207\272\347\232\204\346\260\264\345\215\260.png"
new file mode 100644
index 0000000..173df04
Binary files /dev/null and "b/docs/\350\247\243\345\207\272\347\232\204\346\260\264\345\215\260.png" differ
diff --git a/examples/example_bit.py b/examples/example_bit.py
new file mode 100644
index 0000000..41d05f3
--- /dev/null
+++ b/examples/example_bit.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
+
+from blind_watermark import att
+from blind_watermark import WaterMark
+import cv2
+from blind_watermark import WaterMarkCore
+import numpy as np
+
+# %%
+
+bwm = WaterMark(password_img=1, password_wm=1)
+
+# 读取原图
+bwm.read_img('pic/ori_img.jpeg')
+
+# 读取水印
+wm = [True, False, True, False, True, False, True, False, True, False]
+bwm.read_wm(wm, mode='bit')
+
+# 打上盲水印
+bwm.embed('output/embedded.png')
+
+len_wm = len(wm) # 解水印需要用到长度
+ori_img_shape = cv2.imread('pic/ori_img.jpeg').shape[:2] # 抗攻击需要知道原图的shape
+
+# %% 解水印
+
+# 注意设定水印的长宽wm_shape
+bwm1 = WaterMark(password_img=1, password_wm=1)
+wm_extract = bwm1.extract('output/embedded.png', wm_shape=len_wm, mode='bit')
+print("不攻击的提取结果:", wm_extract)
+
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+
+# %%截屏攻击
+
+loc = ((0.3, 0.1), (0.7, 0.9))
+
+att.cut_att(input_filename='output/embedded.png', output_file_name='output/截屏攻击.png', loc=loc)
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/截屏攻击.png', wm_shape=len_wm, mode='bit')
+print("截屏攻击{loc}后的提取结果:".format(loc=loc), wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+
+# %%
+# 一次横向裁剪打击
+r = 0.2
+att.cut_att_width(input_filename='output/embedded.png', output_file_name='output/横向裁剪攻击.png', ratio=r)
+att.anti_cut_att(input_filename='output/横向裁剪攻击.png', output_file_name='output/横向裁剪攻击_填补.png',
+ origin_shape=ori_img_shape)
+
+# 提取水印
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/横向裁剪攻击_填补.png', wm_shape=len_wm, mode='bit')
+print(f"横向裁剪攻击r={r}后的提取结果:", wm_extract)
+
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+
+# %%一次纵向裁剪攻击
+ratio = 0.2
+att.cut_att_height(input_filename='output/embedded.png', output_file_name='output/纵向裁剪攻击.png', ratio=ratio)
+att.anti_cut_att(input_filename='output/纵向裁剪攻击.png', output_file_name='output/纵向裁剪攻击_填补.png',
+ origin_shape=ori_img_shape)
+
+# 提取
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/纵向裁剪攻击_填补.png', wm_shape=len_wm, mode='bit')
+print(f"纵向裁剪攻击ratio={ratio}后的提取结果:", wm_extract)
+
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+# %%椒盐攻击
+ratio = 0.05
+att.salt_pepper_att(input_filename='output/embedded.png', output_file_name='output/椒盐攻击.png', ratio=ratio)
+# ratio是椒盐概率
+
+# 提取
+wm_extract = bwm1.extract('output/椒盐攻击.png', wm_shape=len_wm, mode='bit')
+print(f"椒盐攻击ratio={ratio}后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+
+# %%旋转攻击
+att.rot_att(input_filename='output/embedded.png', output_file_name='output/旋转攻击.png', angle=45)
+att.rot_att(input_filename='output/旋转攻击.png', output_file_name='output/旋转攻击_还原.png', angle=-45)
+
+# 提取水印
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/旋转攻击_还原.png', wm_shape=len_wm, mode='bit')
+print("旋转攻击后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+
+# %%遮挡攻击
+n = 60
+att.shelter_att(input_filename='output/embedded.png', output_file_name='output/多遮挡攻击.png', ratio=0.1, n=n)
+
+# 提取
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/多遮挡攻击.png', wm_shape=len_wm, mode='bit')
+print(f"遮挡攻击{n}后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+
+# %%缩放攻击
+att.resize_att(input_filename='output/embedded.png', output_file_name='output/缩放攻击.png', out_shape=(800, 600))
+att.resize_att(input_filename='output/缩放攻击.png', output_file_name='output/缩放攻击_还原.png', out_shape=ori_img_shape[::-1])
+# out_shape 是分辨率,需要颠倒一下
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/缩放攻击_还原.png', wm_shape=len_wm, mode='bit')
+print("缩放攻击后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
diff --git a/examples/example_img.py b/examples/example_img.py
new file mode 100644
index 0000000..f3b1444
--- /dev/null
+++ b/examples/example_img.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import cv2
+
+from blind_watermark import WaterMark
+
+bwm = WaterMark(password_wm=1, password_img=1)
+# 读取原图
+bwm.read_img(filename='pic/ori_img.jpeg')
+# 读取水印
+bwm.read_wm('pic/watermark.png')
+# 打上盲水印
+bwm.embed('output/embedded.png')
+wm_shape = cv2.imread('pic/watermark.png', flags=cv2.IMREAD_GRAYSCALE).shape
+
+# %% 解水印
+
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+# 注意需要设定水印的长宽wm_shape
+bwm1.extract('output/embedded.png', wm_shape=wm_shape, out_wm_name='output/wm_extracted.png', mode='img')
diff --git a/examples/example_no_writing.py b/examples/example_no_writing.py
new file mode 100644
index 0000000..35badc8
--- /dev/null
+++ b/examples/example_no_writing.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+This demonstrates how to embed and extract without writing files to local file system.
+The format of images is numpy.array.
+This may be useful if you want to use blind-watermark in another project.
+"""
+
+from blind_watermark import WaterMark
+from blind_watermark import att
+from blind_watermark.recover import estimate_crop_parameters, recover_crop
+import cv2
+import numpy as np
+
+ori_img = cv2.imread('pic/ori_img.jpeg', flags=cv2.IMREAD_UNCHANGED)
+wm = '@guofei9987 开源万岁!'
+ori_img_shape = ori_img.shape[:2] # 抗攻击有时需要知道原图的shape
+
+# %% embed string into image whose format is numpy.array
+bwm = WaterMark(password_img=1, password_wm=1)
+bwm.read_img(img=ori_img)
+
+bwm.read_wm(wm, mode='str')
+embed_img = bwm.embed()
+
+len_wm = len(bwm.wm_bit) # 解水印需要用到长度
+print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))
+
+# %% extract from image whose format is numpy.array
+bwm1 = WaterMark(password_img=1, password_wm=1)
+wm_extract = bwm1.extract(embed_img=embed_img, wm_shape=len_wm, mode='str')
+print("不攻击的提取结果:", wm_extract)
+
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %%截屏攻击 = 裁剪攻击 + 缩放攻击 + 知道攻击参数(按照参数还原)
+
+loc = ((0.1, 0.1), (0.5, 0.5))
+resize = 0.7
+img_attacked = att.cut_att(input_img=embed_img, output_file_name=None, loc=loc, resize=resize)
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract(embed_img=img_attacked, wm_shape=len_wm, mode='str')
+print("截屏攻击={loc},缩放攻击={resize},并且知道攻击参数。提取结果:".format(loc=loc, resize=resize), wm_extract)
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %% 截屏攻击 = 剪切攻击 + 缩放攻击 + 不知道攻击参数
+loc_r = ((0.1, 0.1), (0.7, 0.6))
+scale = 0.7
+img_attacked, (x1, y1, x2, y2) = att.cut_att2(input_img=embed_img, loc_r=loc_r, scale=scale)
+print(f'Crop attack\'s real parameters: x1={x1},y1={y1},x2={x2},y2={y2}')
+
+# estimate crop attack parameters:
+(x1, y1, x2, y2), image_o_shape, score, scale_infer = estimate_crop_parameters(ori_img=embed_img,
+ tem_img=img_attacked,
+ scale=(0.5, 2), search_num=200)
+
+print(f'Crop attack\'s estimate parameters: x1={x1},y1={y1},x2={x2},y2={y2}. score={score}')
+
+# recover from attack:
+img_recovered = recover_crop(tem_img=img_attacked, loc=(x1, y1, x2, y2), image_o_shape=image_o_shape)
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract(embed_img=embed_img, wm_shape=len_wm, mode='str')
+print("截屏攻击,不知道攻击参数。提取结果:", wm_extract)
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %% Vertical cut
+r = 0.3
+img_attacked = att.cut_att_width(input_img=embed_img, ratio=r)
+img_recovered = att.anti_cut_att(input_img=img_attacked, origin_shape=ori_img_shape)
+
+# 提取水印
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract(embed_img=img_recovered, wm_shape=len_wm, mode='str')
+print(f"横向裁剪攻击r={r}后的提取结果:", wm_extract)
+
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %% horizontal cut
+r = 0.4
+img_attacked = att.cut_att_height(input_img=embed_img, ratio=r)
+img_recovered = att.anti_cut_att(input_img=img_attacked, origin_shape=ori_img_shape)
+
+# extract:
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract(embed_img=img_recovered, wm_shape=len_wm, mode='str')
+print(f"纵向裁剪攻击r={r}后的提取结果:", wm_extract)
+
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %%椒盐攻击
+ratio = 0.05
+img_attacked = att.salt_pepper_att(input_img=embed_img, ratio=ratio)
+# ratio是椒盐概率
+
+# 提取
+wm_extract = bwm1.extract(embed_img=img_attacked, wm_shape=len_wm, mode='str')
+print(f"椒盐攻击ratio={ratio}后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+
+# %%旋转攻击
+angle = 60
+img_attacked = att.rot_att(input_img=embed_img, angle=angle)
+img_recovered = att.rot_att(input_img=img_attacked, angle=-angle)
+
+# 提取水印
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract(embed_img=img_recovered, wm_shape=len_wm, mode='str')
+print(f"旋转攻击angle={angle}后的提取结果:", wm_extract)
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %%遮挡攻击
+n = 60
+img_attacked = att.shelter_att(input_img=embed_img, ratio=0.1, n=n)
+
+# 提取
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract(embed_img=img_attacked, wm_shape=len_wm, mode='str')
+print(f"遮挡攻击{n}次后的提取结果:", wm_extract)
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %%缩放攻击
+img_attacked = att.resize_att(input_img=embed_img, out_shape=(400, 300))
+img_recovered = att.resize_att(input_img=img_attacked, out_shape=ori_img_shape[::-1])
+# out_shape 是分辨率,需要颠倒一下
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract(embed_img=img_recovered, wm_shape=len_wm, mode='str')
+print("缩放攻击后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+# %%
+
+img_attacked = att.bright_att(input_img=embed_img, ratio=0.9)
+img_recovered = att.bright_att(input_img=img_attacked, ratio=1.1)
+wm_extract = bwm1.extract(embed_img=img_recovered, wm_shape=len_wm, mode='str')
+
+print("亮度攻击后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
diff --git a/examples/example_str.py b/examples/example_str.py
new file mode 100644
index 0000000..ef8a3ff
--- /dev/null
+++ b/examples/example_str.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# embed string
+import numpy as np
+from blind_watermark import WaterMark
+from blind_watermark import att
+from blind_watermark.recover import estimate_crop_parameters, recover_crop
+
+import cv2
+
+bwm = WaterMark(password_img=1, password_wm=1)
+bwm.read_img('pic/ori_img.jpeg')
+wm = '@guofei9987 开源万岁!'
+bwm.read_wm(wm, mode='str')
+bwm.embed('output/embedded.png')
+
+len_wm = len(bwm.wm_bit) # 解水印需要用到长度
+print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))
+
+ori_img_shape = cv2.imread('pic/ori_img.jpeg').shape[:2] # 抗攻击有时需要知道原图的shape
+
+# %% 解水印
+bwm1 = WaterMark(password_img=1, password_wm=1)
+wm_extract = bwm1.extract('output/embedded.png', wm_shape=len_wm, mode='str')
+print("不攻击的提取结果:", wm_extract)
+
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %%截屏攻击 = 裁剪攻击 + 缩放攻击 + 知道攻击参数(按照参数还原)
+
+loc = ((0.1, 0.1), (0.5, 0.5))
+resize = 0.7
+att.cut_att(input_filename='output/embedded.png', output_file_name='output/截屏攻击.png', loc=loc, resize=resize)
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/截屏攻击.png', wm_shape=len_wm, mode='str')
+print("截屏攻击={loc},缩放攻击={resize},并且知道攻击参数。提取结果:".format(loc=loc, resize=resize), wm_extract)
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %% 截屏攻击 = 剪切攻击 + 缩放攻击 + 不知道攻击参数
+loc_r = ((0.1, 0.1), (0.7, 0.6))
+scale = 0.7
+_, (x1, y1, x2, y2) = att.cut_att2(input_filename='output/embedded.png', output_file_name='output/截屏攻击2.png',
+ loc_r=loc_r, scale=scale)
+print(f'Crop attack\'s real parameters: x1={x1},y1={y1},x2={x2},y2={y2}')
+
+# estimate crop attack parameters:
+(x1, y1, x2, y2), image_o_shape, score, scale_infer = estimate_crop_parameters(original_file='output/embedded.png',
+ template_file='output/截屏攻击2.png',
+ scale=(0.5, 2), search_num=200)
+
+print(f'Crop attack\'s estimate parameters: x1={x1},y1={y1},x2={x2},y2={y2}. score={score}')
+
+# recover from attack:
+recover_crop(template_file='output/截屏攻击2.png', output_file_name='output/截屏攻击2_还原.png',
+ loc=(x1, y1, x2, y2), image_o_shape=image_o_shape)
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/截屏攻击2_还原.png', wm_shape=len_wm, mode='str')
+print("截屏攻击,不知道攻击参数。提取结果:", wm_extract)
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %% Vertical cut
+r = 0.3
+att.cut_att_width(input_filename='output/embedded.png', output_file_name='output/横向裁剪攻击.png', ratio=r)
+att.anti_cut_att(input_filename='output/横向裁剪攻击.png', output_file_name='output/横向裁剪攻击_填补.png',
+ origin_shape=ori_img_shape)
+
+# 提取水印
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/横向裁剪攻击_填补.png', wm_shape=len_wm, mode='str')
+print(f"横向裁剪攻击r={r}后的提取结果:", wm_extract)
+
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %% horizontal cut
+r = 0.4
+att.cut_att_height(input_filename='output/embedded.png', output_file_name='output/纵向裁剪攻击.png', ratio=r)
+att.anti_cut_att(input_filename='output/纵向裁剪攻击.png', output_file_name='output/纵向裁剪攻击_填补.png',
+ origin_shape=ori_img_shape)
+
+# extract:
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/纵向裁剪攻击_填补.png', wm_shape=len_wm, mode='str')
+print(f"纵向裁剪攻击r={r}后的提取结果:", wm_extract)
+
+assert wm == wm_extract, '提取水印和原水印不一致'
+# %%椒盐攻击
+ratio = 0.05
+att.salt_pepper_att(input_filename='output/embedded.png', output_file_name='output/椒盐攻击.png', ratio=ratio)
+# ratio是椒盐概率
+
+# 提取
+wm_extract = bwm1.extract('output/椒盐攻击.png', wm_shape=len_wm, mode='str')
+print(f"椒盐攻击ratio={ratio}后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+
+# %%旋转攻击
+angle = 60
+att.rot_att(input_filename='output/embedded.png', output_file_name='output/旋转攻击.png', angle=angle)
+att.rot_att(input_filename='output/旋转攻击.png', output_file_name='output/旋转攻击_还原.png', angle=-angle)
+
+# 提取水印
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/旋转攻击_还原.png', wm_shape=len_wm, mode='str')
+print(f"旋转攻击angle={angle}后的提取结果:", wm_extract)
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %%遮挡攻击
+n = 60
+att.shelter_att(input_filename='output/embedded.png', output_file_name='output/多遮挡攻击.png', ratio=0.1, n=n)
+
+# 提取
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/多遮挡攻击.png', wm_shape=len_wm, mode='str')
+print(f"遮挡攻击{n}次后的提取结果:", wm_extract)
+assert wm == wm_extract, '提取水印和原水印不一致'
+
+# %%缩放攻击
+att.resize_att(input_filename='output/embedded.png', output_file_name='output/缩放攻击.png', out_shape=(400, 300))
+att.resize_att(input_filename='output/缩放攻击.png', output_file_name='output/缩放攻击_还原.png',
+ out_shape=ori_img_shape[::-1])
+# out_shape 是分辨率,需要颠倒一下
+
+bwm1 = WaterMark(password_wm=1, password_img=1)
+wm_extract = bwm1.extract('output/缩放攻击_还原.png', wm_shape=len_wm, mode='str')
+print("缩放攻击后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
+# %%
+
+att.bright_att(input_filename='output/embedded.png', output_file_name='output/亮度攻击.png', ratio=0.9)
+att.bright_att(input_filename='output/亮度攻击.png', output_file_name='output/亮度攻击_还原.png', ratio=1.1)
+wm_extract = bwm1.extract('output/亮度攻击_还原.png', wm_shape=len_wm, mode='str')
+
+print("亮度攻击后的提取结果:", wm_extract)
+assert np.all(wm == wm_extract), '提取水印和原水印不一致'
diff --git a/examples/output/.keep b/examples/output/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/examples/pic/ori_img.jpeg b/examples/pic/ori_img.jpeg
new file mode 100644
index 0000000..ef48a05
Binary files /dev/null and b/examples/pic/ori_img.jpeg differ
diff --git a/examples/pic/watermark.png b/examples/pic/watermark.png
new file mode 100644
index 0000000..d645a1a
Binary files /dev/null and b/examples/pic/watermark.png differ
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..2bc4287
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+numpy>=1.17.0
+opencv-python
+setuptools
+PyWavelets
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..96579a4
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,39 @@
+from setuptools import setup, find_packages
+from os import path as os_path
+import blind_watermark
+
+this_directory = os_path.abspath(os_path.dirname(__file__))
+
+
+# 读取文件内容
+def read_file(filename):
+ with open(os_path.join(this_directory, filename), encoding='utf-8') as f:
+ long_description = f.read()
+ return long_description
+
+
+# 获取依赖
+def read_requirements(filename):
+ return [line.strip() for line in read_file(filename).splitlines()
+ if not line.startswith('#')]
+
+
+setup(name='blind_watermark',
+ python_requires='>=3.5',
+ version=blind_watermark.__version__,
+ description='Blind Watermark in Python',
+ long_description=read_file('docs/en/README.md'),
+ long_description_content_type="text/markdown",
+ url='https://github.com/guofei9987/blind_watermark',
+ author='Guo Fei',
+ author_email='guofei9987@foxmail.com',
+ license='MIT',
+ packages=find_packages(),
+ platforms=['linux', 'windows', 'macos'],
+ install_requires=['numpy', 'opencv-python', 'PyWavelets'],
+ zip_safe=False,
+ entry_points={
+ 'console_scripts': [
+ 'blind_watermark = blind_watermark.cli_tools:main'
+ ]
+ })