From 486b18a039499e46dd8059322c45e58403debc11 Mon Sep 17 00:00:00 2001 From: guofei9987 Date: Thu, 13 Aug 2020 07:58:07 +0800 Subject: [PATCH] version 0.4.0 --- .travis.yml | 26 + LICENSE | 21 + README.md | 194 ++++ README_cn.md | 173 ++++ blind_watermark/__init__.py | 6 + blind_watermark/att.py | 199 ++++ blind_watermark/blind_watermark.py | 92 ++ blind_watermark/bwm_core.py | 232 +++++ blind_watermark/cli_tools.py | 53 + blind_watermark/pool.py | 38 + blind_watermark/recover.py | 94 ++ blind_watermark/requirements.txt | 1 + docs/.nojekyll | 0 docs/README.md | 22 + docs/_coverpage.md | 11 + docs/_navbar.md | 3 + docs/_sidebar.md | 3 + docs/en/README.md | 175 ++++ docs/en/_coverpage.md | 22 + docs/en/_sidebar.md | 2 + docs/index.html | 61 ++ docs/make_doc.py | 104 ++ docs/run_server.bat | 1 + docs/vue.css | 939 ++++++++++++++++++ docs/zh/README.md | 173 ++++ docs/zh/_coverpage.md | 22 + docs/zh/_sidebar.md | 2 + ...6\345\272\246\346\224\273\345\207\273.jpg" | Bin 0 -> 36143 bytes ...0\345\217\226\346\260\264\345\215\260.png" | Bin 0 -> 13373 bytes ...0\345\217\226\346\260\264\345\215\260.png" | Bin 0 -> 13395 bytes "docs/\345\216\237\345\233\276.jpeg" | Bin 0 -> 18174 bytes ...6\346\214\241\346\224\273\345\207\273.jpg" | Bin 0 -> 30051 bytes ...0\345\217\226\346\260\264\345\215\260.png" | Bin 0 -> 6500 bytes ...345\207\2732_\350\277\230\345\216\237.jpg" | Bin 0 -> 14967 bytes ...4\345\215\260\347\232\204\345\233\276.jpg" | Bin 0 -> 37913 bytes ...3\350\275\254\346\224\273\345\207\273.jpg" | Bin 0 -> 37962 bytes ...0\345\217\226\346\260\264\345\215\260.png" | Bin 0 -> 11779 bytes ...2\347\233\220\346\224\273\345\207\273.jpg" | Bin 0 -> 43799 bytes ...0\345\217\226\346\260\264\345\215\260.png" | Bin 0 -> 13273 bytes ...\345\207\273_\345\241\253\350\241\245.jpg" | Bin 0 -> 13620 bytes ...0\345\217\226\346\260\264\345\215\260.png" | Bin 0 -> 13483 bytes "docs/\346\260\264\345\215\260.png" | Bin 0 -> 11260 bytes ...\345\207\273_\345\241\253\350\241\245.jpg" | Bin 0 -> 15769 bytes ...0\345\217\226\346\260\264\345\215\260.png" | Bin 0 -> 12947 bytes ...1\346\224\276\346\224\273\345\207\273.jpg" | Bin 0 -> 13564 bytes ...0\345\217\226\346\260\264\345\215\260.png" | Bin 0 -> 12168 bytes ...2\347\232\204\346\260\264\345\215\260.png" | Bin 0 -> 3348 bytes examples/example_bit.py | 111 +++ examples/example_img.py | 21 + examples/example_no_writing.py | 139 +++ examples/example_str.py | 136 +++ examples/output/.keep | 0 examples/pic/ori_img.jpeg | Bin 0 -> 111598 bytes examples/pic/watermark.png | Bin 0 -> 5673 bytes requirements.txt | 4 + setup.py | 39 + 56 files changed, 3119 insertions(+) create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README_cn.md create mode 100644 blind_watermark/__init__.py create mode 100644 blind_watermark/att.py create mode 100644 blind_watermark/blind_watermark.py create mode 100644 blind_watermark/bwm_core.py create mode 100644 blind_watermark/cli_tools.py create mode 100644 blind_watermark/pool.py create mode 100644 blind_watermark/recover.py create mode 100644 blind_watermark/requirements.txt create mode 100644 docs/.nojekyll create mode 100644 docs/README.md create mode 100644 docs/_coverpage.md create mode 100644 docs/_navbar.md create mode 100644 docs/_sidebar.md create mode 100644 docs/en/README.md create mode 100644 docs/en/_coverpage.md create mode 100644 docs/en/_sidebar.md create mode 100644 docs/index.html create mode 100644 docs/make_doc.py create mode 100644 docs/run_server.bat create mode 100644 docs/vue.css create mode 100644 docs/zh/README.md create mode 100644 docs/zh/_coverpage.md create mode 100644 docs/zh/_sidebar.md create mode 100644 "docs/\344\272\256\345\272\246\346\224\273\345\207\273.jpg" create mode 100644 "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" create mode 100644 "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" create mode 100644 "docs/\345\216\237\345\233\276.jpeg" create mode 100644 "docs/\345\244\232\351\201\256\346\214\241\346\224\273\345\207\273.jpg" create mode 100644 "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" create mode 100644 "docs/\346\210\252\345\261\217\346\224\273\345\207\2732_\350\277\230\345\216\237.jpg" create mode 100644 "docs/\346\211\223\344\270\212\346\260\264\345\215\260\347\232\204\345\233\276.jpg" create mode 100644 "docs/\346\227\213\350\275\254\346\224\273\345\207\273.jpg" create mode 100644 "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" create mode 100644 "docs/\346\244\222\347\233\220\346\224\273\345\207\273.jpg" create mode 100644 "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" create mode 100644 "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" create mode 100644 "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" create mode 100644 "docs/\346\260\264\345\215\260.png" create mode 100644 "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" create mode 100644 "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" create mode 100644 "docs/\347\274\251\346\224\276\346\224\273\345\207\273.jpg" create mode 100644 "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" create mode 100644 "docs/\350\247\243\345\207\272\347\232\204\346\260\264\345\215\260.png" create mode 100644 examples/example_bit.py create mode 100644 examples/example_img.py create mode 100644 examples/example_no_writing.py create mode 100644 examples/example_str.py create mode 100644 examples/output/.keep create mode 100644 examples/pic/ori_img.jpeg create mode 100644 examples/pic/watermark.png create mode 100644 requirements.txt create mode 100644 setup.py 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. + + +[![PyPI](https://img.shields.io/pypi/v/blind_watermark)](https://pypi.org/project/blind_watermark/) +[![Build Status](https://travis-ci.com/guofei9987/blind_watermark.svg?branch=master)](https://travis-ci.com/guofei9987/blind_watermark) +[![codecov](https://codecov.io/gh/guofei9987/blind_watermark/branch/master/graph/badge.svg)](https://codecov.io/gh/guofei9987/blind_watermark) +[![License](https://img.shields.io/pypi/l/blind_watermark.svg)](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE) +![Python](https://img.shields.io/badge/python->=3.5-green.svg) +![Platform](https://img.shields.io/badge/platform-windows%20|%20linux%20|%20macos-green.svg) +[![stars](https://img.shields.io/github/stars/guofei9987/blind_watermark.svg?style=social)](https://github.com/guofei9987/blind_watermark/) +[![fork](https://img.shields.io/github/forks/guofei9987/blind_watermark?style=social)](https://github.com/guofei9987/blind_watermark/fork) +[![Downloads](https://pepy.tech/badge/blind-watermark)](https://pepy.tech/project/blind-watermark) +[![Discussions](https://img.shields.io/badge/discussions-green.svg)](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 + +![origin_image](docs/原图.jpeg) + '@guofei9987 开源万岁!' = ![打上水印的图](docs/打上水印的图.jpg) + + +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|![旋转攻击](docs/旋转攻击.jpg)|'@guofei9987 开源万岁!'| +|Random crop|![截屏攻击](docs/截屏攻击2_还原.jpg)|'@guofei9987 开源万岁!'| +|Masks| ![多遮挡攻击](docs/多遮挡攻击.jpg) |'@guofei9987 开源万岁!'| +|Vertical cut|![横向裁剪攻击](docs/横向裁剪攻击_填补.jpg)|'@guofei9987 开源万岁!'| +|Horizontal cut|![纵向裁剪攻击](docs/纵向裁剪攻击_填补.jpg)|'@guofei9987 开源万岁!'| +|Resize|![缩放攻击](docs/缩放攻击.jpg)|'@guofei9987 开源万岁!'| +|Pepper Noise|![椒盐攻击](docs/椒盐攻击.jpg)|'@guofei9987 开源万岁!'| +|Brightness 10% Down|![亮度攻击](docs/亮度攻击.jpg)|'@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|![旋转攻击](docs/旋转攻击.jpg)|![](docs/旋转攻击_提取水印.png)| +|Random crop|![截屏攻击](docs/截屏攻击2_还原.jpg)|![多遮挡_提取水印](docs/多遮挡攻击_提取水印.png)| +|Mask| ![多遮挡攻击](docs/多遮挡攻击.jpg) |![多遮挡_提取水印](docs/多遮挡攻击_提取水印.png)| + + +### 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 + +基于频域的数字盲水印 + + +[![PyPI](https://img.shields.io/pypi/v/blind_watermark)](https://pypi.org/project/blind_watermark/) +[![Build Status](https://travis-ci.com/guofei9987/blind_watermark.svg?branch=master)](https://travis-ci.com/guofei9987/blind_watermark) +[![codecov](https://codecov.io/gh/guofei9987/blind_watermark/branch/master/graph/badge.svg)](https://codecov.io/gh/guofei9987/blind_watermark) +[![License](https://img.shields.io/pypi/l/blind_watermark.svg)](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE) +![Python](https://img.shields.io/badge/python->=3.5-green.svg) +![Platform](https://img.shields.io/badge/platform-windows%20|%20linux%20|%20macos-green.svg) +[![stars](https://img.shields.io/github/stars/guofei9987/blind_watermark.svg?style=social)](https://github.com/guofei9987/blind_watermark/) +[![fork](https://img.shields.io/github/forks/guofei9987/blind_watermark?style=social)](https://github.com/guofei9987/blind_watermark/fork) +[![Downloads](https://pepy.tech/badge/blind-watermark)](https://pepy.tech/project/blind-watermark) +[![Discussions](https://img.shields.io/badge/discussions-green.svg)](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 中使用 + +原图 + 水印 = 打上水印的图 + +![origin_image](docs/原图.jpeg) + '@guofei9987 开源万岁!' = ![打上水印的图](docs/打上水印的图.jpg) + + + +参考 [代码](/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度|![旋转攻击](docs/旋转攻击.jpg)|'@guofei9987 开源万岁!'| +|随机截图|![截屏攻击](docs/截屏攻击2_还原.jpg)|'@guofei9987 开源万岁!'| +|多遮挡| ![多遮挡攻击](docs/多遮挡攻击.jpg) |'@guofei9987 开源万岁!'| +|纵向裁剪|![横向裁剪攻击](docs/横向裁剪攻击_填补.jpg)|'@guofei9987 开源万岁!'| +|横向裁剪|![纵向裁剪攻击](docs/纵向裁剪攻击_填补.jpg)|'@guofei9987 开源万岁!'| +|缩放攻击|![缩放攻击](docs/缩放攻击.jpg)|'@guofei9987 开源万岁!'| +|椒盐攻击|![椒盐攻击](docs/椒盐攻击.jpg)|'@guofei9987 开源万岁!'| +|亮度攻击|![亮度攻击](docs/亮度攻击.jpg)|'@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度|![旋转攻击](docs/旋转攻击.jpg)|![](docs/旋转攻击_提取水印.png)| +|随机截图|![截屏攻击](docs/截屏攻击2_还原.jpg)|![](docs/旋转攻击_提取水印.png)| +|多遮挡| ![多遮挡攻击](docs/多遮挡攻击.jpg) |![多遮挡_提取水印](docs/多遮挡攻击_提取水印.png)| + + + +### 隐水印还可以是二进制数据 + +参考 [代码](/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) + +[![PyPI](https://img.shields.io/pypi/v/blind_watermark)](https://pypi.org/project/blind_watermark/) +[![release](https://img.shields.io/github/v/release/guofei9987/blind_watermark)](https://github.com/guofei9987/blind_watermark) +[![Build Status](https://travis-ci.com/guofei9987/blind_watermark.svg?branch=master)](https://travis-ci.com/guofei9987/blind_watermark) +[![codecov](https://codecov.io/gh/guofei9987/blind_watermark/branch/master/graph/badge.svg)](https://codecov.io/gh/guofei9987/blind_watermark) +[![PyPI_downloads](https://img.shields.io/pypi/dm/blind_watermark)](https://pypi.org/project/blind_watermark/) +[![Stars](https://img.shields.io/github/stars/guofei9987/blind_watermark?style=social)](https://github.com/guofei9987/blind_watermark/stargazers) +[![Forks](https://img.shields.io/github/forks/guofei9987/blind_watermark.svg?style=social)](https://github.com/guofei9987/blind_watermark/network/members) +[![Join the chat at https://gitter.im/guofei9987/blind_watermark](https://badges.gitter.im/guofei9987/blind_watermark.svg)](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. + + +[![PyPI](https://img.shields.io/pypi/v/blind_watermark)](https://pypi.org/project/blind_watermark/) +[![Build Status](https://travis-ci.com/guofei9987/blind_watermark.svg?branch=master)](https://travis-ci.com/guofei9987/blind_watermark) +[![codecov](https://codecov.io/gh/guofei9987/blind_watermark/branch/master/graph/badge.svg)](https://codecov.io/gh/guofei9987/blind_watermark) +[![License](https://img.shields.io/pypi/l/blind_watermark.svg)](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE) +![Python](https://img.shields.io/badge/python->=3.5-green.svg) +![Platform](https://img.shields.io/badge/platform-windows%20|%20linux%20|%20macos-green.svg) +[![stars](https://img.shields.io/github/stars/guofei9987/blind_watermark.svg?style=social)](https://github.com/guofei9987/blind_watermark/) +[![fork](https://img.shields.io/github/forks/guofei9987/blind_watermark?style=social)](https://github.com/guofei9987/blind_watermark/fork) +[![Downloads](https://pepy.tech/badge/blind-watermark)](https://pepy.tech/project/blind-watermark) +[![Discussions](https://img.shields.io/badge/discussions-green.svg)](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 + +![origin_image](https://blindwatermark.github.io/blind_watermark/原图.jpeg) + '@guofei9987 开源万岁!' = ![打上水印的图](https://blindwatermark.github.io/blind_watermark/打上水印的图.jpg) + + +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|![旋转攻击](https://blindwatermark.github.io/blind_watermark/旋转攻击.jpg)|'@guofei9987 开源万岁!'| +|Random crop|![截屏攻击](https://blindwatermark.github.io/blind_watermark/截屏攻击2_还原.jpg)|'@guofei9987 开源万岁!'| +|Masks| ![多遮挡攻击](https://blindwatermark.github.io/blind_watermark/多遮挡攻击.jpg) |'@guofei9987 开源万岁!'| +|50% Horizontal cut|![横向裁剪攻击](https://blindwatermark.github.io/blind_watermark/横向裁剪攻击_填补.jpg)|'@guofei9987 开源万岁!'| +|50% Vertical cut|![纵向裁剪攻击](https://blindwatermark.github.io/blind_watermark/纵向裁剪攻击_填补.jpg)|'@guofei9987 开源万岁!'| +|Resize 0.5|![缩放攻击](https://blindwatermark.github.io/blind_watermark/缩放攻击.jpg)|'@guofei9987 开源万岁!'| +|Pepper Noise|![椒盐攻击](https://blindwatermark.github.io/blind_watermark/椒盐攻击.jpg)|'@guofei9987 开源万岁!'| +|Brightness 10% Down|![亮度攻击](https://blindwatermark.github.io/blind_watermark/亮度攻击.jpg)|'@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|![旋转攻击](https://blindwatermark.github.io/blind_watermark/旋转攻击.jpg)|![](https://blindwatermark.github.io/blind_watermark/旋转攻击_提取水印.png)| +|Random crop|![截屏攻击](https://blindwatermark.github.io/blind_watermark/截屏攻击2_还原.jpg)|![多遮挡_提取水印](https://blindwatermark.github.io/blind_watermark/多遮挡攻击_提取水印.png)| +|Mask| ![多遮挡攻击](https://blindwatermark.github.io/blind_watermark/多遮挡攻击.jpg) |![多遮挡_提取水印](https://blindwatermark.github.io/blind_watermark/多遮挡攻击_提取水印.png)| + + +### 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 + +* [![PyPI](https://img.shields.io/pypi/v/blind_watermark)](https://pypi.org/project/blind_watermark/) +[![Build Status](https://travis-ci.com/guofei9987/blind_watermark.svg?branch=master)](https://travis-ci.com/guofei9987/blind_watermark) +[![codecov](https://codecov.io/gh/guofei9987/blind_watermark/branch/master/graph/badge.svg)](https://codecov.io/gh/guofei9987/blind_watermark) +[![License](https://img.shields.io/pypi/l/blind_watermark.svg)](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE) +![Python](https://img.shields.io/badge/python->=3.5-green.svg) +![Platform](https://img.shields.io/badge/platform-windows%20|%20linux%20|%20macos-green.svg) +[![stars](https://img.shields.io/github/stars/guofei9987/blind_watermark.svg?style=social)](https://github.com/guofei9987/blind_watermark/) +[![fork](https://img.shields.io/github/forks/guofei9987/blind_watermark?style=social)](https://github.com/guofei9987/blind_watermark/fork) +[![Downloads](https://pepy.tech/badge/blind-watermark)](https://pepy.tech/project/blind-watermark) +[![Discussions](https://img.shields.io/badge/discussions-green.svg)](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 + +基于频域的数字盲水印 + + +[![PyPI](https://img.shields.io/pypi/v/blind_watermark)](https://pypi.org/project/blind_watermark/) +[![Build Status](https://travis-ci.com/guofei9987/blind_watermark.svg?branch=master)](https://travis-ci.com/guofei9987/blind_watermark) +[![codecov](https://codecov.io/gh/guofei9987/blind_watermark/branch/master/graph/badge.svg)](https://codecov.io/gh/guofei9987/blind_watermark) +[![License](https://img.shields.io/pypi/l/blind_watermark.svg)](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE) +![Python](https://img.shields.io/badge/python->=3.5-green.svg) +![Platform](https://img.shields.io/badge/platform-windows%20|%20linux%20|%20macos-green.svg) +[![stars](https://img.shields.io/github/stars/guofei9987/blind_watermark.svg?style=social)](https://github.com/guofei9987/blind_watermark/) +[![fork](https://img.shields.io/github/forks/guofei9987/blind_watermark?style=social)](https://github.com/guofei9987/blind_watermark/fork) +[![Downloads](https://pepy.tech/badge/blind-watermark)](https://pepy.tech/project/blind-watermark) +[![Discussions](https://img.shields.io/badge/discussions-green.svg)](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 中使用 + +原图 + 水印 = 打上水印的图 + +![origin_image](https://blindwatermark.github.io/blind_watermark/原图.jpeg) + '@guofei9987 开源万岁!' = ![打上水印的图](https://blindwatermark.github.io/blind_watermark/打上水印的图.jpg) + + + +参考 [代码](/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度|![旋转攻击](https://blindwatermark.github.io/blind_watermark/旋转攻击.jpg)|'@guofei9987 开源万岁!'| +|随机截图|![截屏攻击](https://blindwatermark.github.io/blind_watermark/截屏攻击2_还原.jpg)|'@guofei9987 开源万岁!'| +|多遮挡| ![多遮挡攻击](https://blindwatermark.github.io/blind_watermark/多遮挡攻击.jpg) |'@guofei9987 开源万岁!'| +|横向裁剪50%|![横向裁剪攻击](https://blindwatermark.github.io/blind_watermark/横向裁剪攻击_填补.jpg)|'@guofei9987 开源万岁!'| +|纵向裁剪50%|![纵向裁剪攻击](https://blindwatermark.github.io/blind_watermark/纵向裁剪攻击_填补.jpg)|'@guofei9987 开源万岁!'| +|缩放攻击|![缩放攻击](https://blindwatermark.github.io/blind_watermark/缩放攻击.jpg)|'@guofei9987 开源万岁!'| +|椒盐攻击|![椒盐攻击](https://blindwatermark.github.io/blind_watermark/椒盐攻击.jpg)|'@guofei9987 开源万岁!'| +|亮度攻击|![亮度攻击](https://blindwatermark.github.io/blind_watermark/亮度攻击.jpg)|'@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度|![旋转攻击](https://blindwatermark.github.io/blind_watermark/旋转攻击.jpg)|![](https://blindwatermark.github.io/blind_watermark/旋转攻击_提取水印.png)| +|随机截图|![截屏攻击](https://blindwatermark.github.io/blind_watermark/截屏攻击2_还原.jpg)|![](https://blindwatermark.github.io/blind_watermark/旋转攻击_提取水印.png)| +|多遮挡| ![多遮挡攻击](https://blindwatermark.github.io/blind_watermark/多遮挡攻击.jpg) |![多遮挡_提取水印](https://blindwatermark.github.io/blind_watermark/多遮挡攻击_提取水印.png)| + + + +### 隐水印还可以是二进制数据 + +参考 [代码](/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 + +> 数字盲水印 + +* [![PyPI](https://img.shields.io/pypi/v/blind_watermark)](https://pypi.org/project/blind_watermark/) +[![Build Status](https://travis-ci.com/guofei9987/blind_watermark.svg?branch=master)](https://travis-ci.com/guofei9987/blind_watermark) +[![codecov](https://codecov.io/gh/guofei9987/blind_watermark/branch/master/graph/badge.svg)](https://codecov.io/gh/guofei9987/blind_watermark) +[![License](https://img.shields.io/pypi/l/blind_watermark.svg)](https://github.com/guofei9987/blind_watermark/blob/master/LICENSE) +![Python](https://img.shields.io/badge/python->=3.5-green.svg) +![Platform](https://img.shields.io/badge/platform-windows%20|%20linux%20|%20macos-green.svg) +[![stars](https://img.shields.io/github/stars/guofei9987/blind_watermark.svg?style=social)](https://github.com/guofei9987/blind_watermark/) +[![fork](https://img.shields.io/github/forks/guofei9987/blind_watermark?style=social)](https://github.com/guofei9987/blind_watermark/fork) +[![Downloads](https://pepy.tech/badge/blind-watermark)](https://pepy.tech/project/blind-watermark) +[![Discussions](https://img.shields.io/badge/discussions-green.svg)](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 0000000000000000000000000000000000000000..22b46f89dae35b181ef75a6c19653f5f6a1db334 GIT binary patch literal 36143 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

@Jp`&aX0N9TTabx01zSEutFn8i!&P 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 ANB{r; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d8bdeebe41327c70f6fa162011a883d863008520 GIT binary patch literal 13373 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 z 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 Z(wQZ=m=87EAmnW&K>q+f_2nMv|JfI^mnHxJ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d7eab4381e201507ad7c52edc85ea5f3d26fcfe3 GIT binary patch literal 30051 zcmbUIbyQnz&^L?*r#LNcDFsTALW@%ziWeyEq%97`-9mxl4lNWZ!5xCTJ4K5Ycemhy zgm7{{=Y5}Vt@GEn&TN~t_H|u5vod?+H?trAJ+1@DR1}mI02mkmfD(EGJZ=DDL04D$dzz5)9JOyB(ISlj$z@P)*{0|QRs9@0lFRz91>i>+v1OUQq0oea%j1l_! zAE8(NZ}$JWVr66eKPzTq{@>9UfNZS)<-egv10GKRA|Gv>+@0KPoSdKY^Sl9w$SSL0 z|F=4t{)cD#AC_!}F=iniP=kH&?a8lWxGeaw3qX#K@eTbPj28e*atthTjK@9z5dAA~ zF#d=BXBzs1fr*8UgNuj%gn$q|pq>nXiGhWMiH(JW^WS4I0?_vX*yK1*ncvFbKGXb+ z_risOFDNkwpGCI1n^Nm9jFtb3Yw!~SDry>9I<}YWuQ)gb1cih}MBmB1mse0!QdZH{ z(bdy8Ff=l^u(Yzav9)t^_we-c_VEq*9vT)N5gC<~{Nraz>aVo)+`RmP!lL4m(wf@3 z`i91)=9Zq`zW#y1q2WJM(=)Sk^ZynWH#WDncXs#o4-U^SF0Za{ZsB+L|H<{Aod1{p zzmom`$VHBp3lkd~3mfl0xiB!j{u7)W8;AKV?o$~}yw5JrUhoCsQ^+RfRChmN;n#vu zesTRvK*cJs!FK+iX#XSG|2x5g|NoNge+B!06LVR5O|GoGBnt5D7t0mdvB7g`B1FcM0 z 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