crmark


Namecrmark JSON
Version 1.1.0 PyPI version JSON
download
home_pagehttps://github.com/chenoly/crmark
SummaryA robust reversible watermarking method that can robustly extract the watermark in lossy channels and perfectly recover both the cover image and the watermark in lossless channels.
upload_time2025-07-23 03:09:33
maintainerNone
docs_urlNone
authorchenoly
requires_python>=3.7
licenseNone
keywords robust reversible watermarking
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ## 📝 Introduction

**CRMark: Cover-Recoverable Watermark**, a robust and reversible invisible image watermarking method. CRMark enables perfect reconstruction of the original cover image in lossless channels and robust watermark extraction in lossy channels.

CRMark leverages an **Integer Invertible Watermark Network (iIWN)** to achieve lossless and invertible mapping between cover-watermark pairs and stego images. It addresses the trade-off between robustness and reversibility in traditional robust reversible watermarking methods, offering significant improvements in robustness, visual quality, and computational efficiency.

Key features:
- **Robustness**: Enhanced against distortions through an Encoder-Noise Layer-Decoder framework.
- **Reversibility**: Ensures lossless recovery of both the cover image and the watermark in lossless channel.
- **Efficiency**: Reduces time complexity and auxiliary bitstream length.

---
## 🚀 Usage
```bash
pip install crmark
```

code
```bash
import os
import random
import string
import numpy as np
from PIL import Image
from crmark import CRMark

# Create output directory if not exists
os.makedirs("images", exist_ok=True)

# Initialize CRMark in color mode
crmark = CRMark(model_mode="color_256_64_complex")


# Generate a random string of length 3 (total 24 bits)
def generate_random_string(n: int) -> str:
    characters = string.ascii_letters + string.digits
    return ''.join(random.choices(characters, k=n))


# Calculate PSNR between two images
def calculate_psnr(img1, img2):
    mse = np.mean((img1 - img2) ** 2)
    if mse == 0:
        return float('inf')
    PIXEL_MAX = 255.0
    psnr = 20 * np.log10(PIXEL_MAX / np.sqrt(mse))
    return psnr


# Random string message
str_data = generate_random_string(5)
str_data = "hello"
print(str_data)

# Define image paths
 cover_path = "images/cover_color.png"
rec_cover_path = "images/rec_color_cover.png"
attack_rec_cover_path = "images/attack_rec_color_cover.png"
stego_path_clean = "images/color_stego_clean.png"
stego_path_attacked = "images/color_stego_attacked.png"
residual_path_clean = "images/color_residual.png"

# === Case 1: Without attack ===
# Encode string into image
cover_image = np.float32(Image.open(cover_path).resize((256, 256)))
success, stego_image = crmark.encode(cover_image, str_data)
print("psnr:", calculate_psnr(np.float32(stego_image), cover_image))

if success:
    stego_image.save(stego_path_clean)
    stego_clean_image = np.float32(stego_image)
    residual = np.abs((stego_clean_image - cover_image) * 10.) + 127.5
    Image.fromarray(np.uint8(np.clip(residual, 0, 255))).save(residual_path_clean)

    # Recover cover and message from clean image
    stego_clean_image = np.float32(Image.open(stego_path_clean))
    is_attacked_clean, rec_cover_clean, rec_message_clean = crmark.recover(stego_clean_image)
    is_decoded, extracted_message_clean = crmark.decode(stego_clean_image)
    rec_cover_clean.save(rec_cover_path)

    # Compute pixel difference between original and recovered cover
    cover = np.float32(Image.open(cover_path).resize((256, 256)))
    rec_clean = np.float32(rec_cover_clean)
    diff_clean = np.sum(np.abs(cover - rec_clean))

    # === Case 2: With attack ===
    # Slightly modify the image to simulate attack
    stego = np.float32(Image.open(stego_path_clean))
    H, W, C = stego.shape
    rand_y = random.randint(0, H - 1)
    rand_x = random.randint(0, W - 1)
    rand_c = random.randint(0, C - 1)

    # Apply a small perturbation (±1)
    perturbation = random.choice([-1, 1])
    stego[rand_y, rand_x, rand_c] = np.clip(stego[rand_y, rand_x, rand_c] + perturbation, 0, 255)

    stego = np.clip(stego + np.random.randint(0, 20, size=stego.shape), 0, 255)

    Image.fromarray(np.uint8(stego)).save(stego_path_attacked)

    # Recover from attacked image
    stego_attacked_image = np.float32(Image.open(stego_path_attacked))
    is_attacked, rec_cover_attacked, rec_message_attacked = crmark.recover(stego_attacked_image)
    is_attacked_flag, extracted_message_attacked = crmark.decode(stego_attacked_image)
    rec_cover_attacked.save(attack_rec_cover_path)

    rec_attacked = np.float32(rec_cover_attacked)
    diff_attacked = np.sum(np.abs(cover - rec_attacked))
    attack_rec_psnr = calculate_psnr(rec_attacked, cover)
    # === Print results ===
    print("=== Without Attack ===")
    print("Original Message:", str_data)
    print("Recovered Message:", rec_message_clean)
    print("Extracted Message:", extracted_message_clean)
    print("Was Attacked:", is_attacked_clean)
    print("L1 Pixel Difference:", diff_clean)

    print("\n=== With Attack ===")
    print("Recovered Message:", rec_message_attacked)
    print("Extracted Message:", extracted_message_attacked)
    print("Was Attacked:", is_attacked)
    print("L1 Pixel Difference:", diff_attacked)
    print("L1 Pixel Psnr:", attack_rec_psnr)

else:
    print("Emebdding failed!")

```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/chenoly/crmark",
    "name": "crmark",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "robust reversible watermarking",
    "author": "chenoly",
    "author_email": "chenoly@outlook.com",
    "download_url": "https://files.pythonhosted.org/packages/34/f8/5802bff2c93339e59eb6a8c2ca0aaac84d67fd1c61f51b93434336fa262e/crmark-1.1.0.tar.gz",
    "platform": null,
    "description": "## \ud83d\udcdd Introduction\r\n\r\n**CRMark: Cover-Recoverable Watermark**, a robust and reversible invisible image watermarking method. CRMark enables perfect reconstruction of the original cover image in lossless channels and robust watermark extraction in lossy channels.\r\n\r\nCRMark leverages an **Integer Invertible Watermark Network (iIWN)** to achieve lossless and invertible mapping between cover-watermark pairs and stego images. It addresses the trade-off between robustness and reversibility in traditional robust reversible watermarking methods, offering significant improvements in robustness, visual quality, and computational efficiency.\r\n\r\nKey features:\r\n- **Robustness**: Enhanced against distortions through an Encoder-Noise Layer-Decoder framework.\r\n- **Reversibility**: Ensures lossless recovery of both the cover image and the watermark in lossless channel.\r\n- **Efficiency**: Reduces time complexity and auxiliary bitstream length.\r\n\r\n---\r\n## \ud83d\ude80 Usage\r\n```bash\r\npip install crmark\r\n```\r\n\r\ncode\r\n```bash\r\nimport os\r\nimport random\r\nimport string\r\nimport numpy as np\r\nfrom PIL import Image\r\nfrom crmark import CRMark\r\n\r\n# Create output directory if not exists\r\nos.makedirs(\"images\", exist_ok=True)\r\n\r\n# Initialize CRMark in color mode\r\ncrmark = CRMark(model_mode=\"color_256_64_complex\")\r\n\r\n\r\n# Generate a random string of length 3 (total 24 bits)\r\ndef generate_random_string(n: int) -> str:\r\n    characters = string.ascii_letters + string.digits\r\n    return ''.join(random.choices(characters, k=n))\r\n\r\n\r\n# Calculate PSNR between two images\r\ndef calculate_psnr(img1, img2):\r\n    mse = np.mean((img1 - img2) ** 2)\r\n    if mse == 0:\r\n        return float('inf')\r\n    PIXEL_MAX = 255.0\r\n    psnr = 20 * np.log10(PIXEL_MAX / np.sqrt(mse))\r\n    return psnr\r\n\r\n\r\n# Random string message\r\nstr_data = generate_random_string(5)\r\nstr_data = \"hello\"\r\nprint(str_data)\r\n\r\n# Define image paths\r\n cover_path = \"images/cover_color.png\"\r\nrec_cover_path = \"images/rec_color_cover.png\"\r\nattack_rec_cover_path = \"images/attack_rec_color_cover.png\"\r\nstego_path_clean = \"images/color_stego_clean.png\"\r\nstego_path_attacked = \"images/color_stego_attacked.png\"\r\nresidual_path_clean = \"images/color_residual.png\"\r\n\r\n# === Case 1: Without attack ===\r\n# Encode string into image\r\ncover_image = np.float32(Image.open(cover_path).resize((256, 256)))\r\nsuccess, stego_image = crmark.encode(cover_image, str_data)\r\nprint(\"psnr:\", calculate_psnr(np.float32(stego_image), cover_image))\r\n\r\nif success:\r\n    stego_image.save(stego_path_clean)\r\n    stego_clean_image = np.float32(stego_image)\r\n    residual = np.abs((stego_clean_image - cover_image) * 10.) + 127.5\r\n    Image.fromarray(np.uint8(np.clip(residual, 0, 255))).save(residual_path_clean)\r\n\r\n    # Recover cover and message from clean image\r\n    stego_clean_image = np.float32(Image.open(stego_path_clean))\r\n    is_attacked_clean, rec_cover_clean, rec_message_clean = crmark.recover(stego_clean_image)\r\n    is_decoded, extracted_message_clean = crmark.decode(stego_clean_image)\r\n    rec_cover_clean.save(rec_cover_path)\r\n\r\n    # Compute pixel difference between original and recovered cover\r\n    cover = np.float32(Image.open(cover_path).resize((256, 256)))\r\n    rec_clean = np.float32(rec_cover_clean)\r\n    diff_clean = np.sum(np.abs(cover - rec_clean))\r\n\r\n    # === Case 2: With attack ===\r\n    # Slightly modify the image to simulate attack\r\n    stego = np.float32(Image.open(stego_path_clean))\r\n    H, W, C = stego.shape\r\n    rand_y = random.randint(0, H - 1)\r\n    rand_x = random.randint(0, W - 1)\r\n    rand_c = random.randint(0, C - 1)\r\n\r\n    # Apply a small perturbation (\u00b11)\r\n    perturbation = random.choice([-1, 1])\r\n    stego[rand_y, rand_x, rand_c] = np.clip(stego[rand_y, rand_x, rand_c] + perturbation, 0, 255)\r\n\r\n    stego = np.clip(stego + np.random.randint(0, 20, size=stego.shape), 0, 255)\r\n\r\n    Image.fromarray(np.uint8(stego)).save(stego_path_attacked)\r\n\r\n    # Recover from attacked image\r\n    stego_attacked_image = np.float32(Image.open(stego_path_attacked))\r\n    is_attacked, rec_cover_attacked, rec_message_attacked = crmark.recover(stego_attacked_image)\r\n    is_attacked_flag, extracted_message_attacked = crmark.decode(stego_attacked_image)\r\n    rec_cover_attacked.save(attack_rec_cover_path)\r\n\r\n    rec_attacked = np.float32(rec_cover_attacked)\r\n    diff_attacked = np.sum(np.abs(cover - rec_attacked))\r\n    attack_rec_psnr = calculate_psnr(rec_attacked, cover)\r\n    # === Print results ===\r\n    print(\"=== Without Attack ===\")\r\n    print(\"Original Message:\", str_data)\r\n    print(\"Recovered Message:\", rec_message_clean)\r\n    print(\"Extracted Message:\", extracted_message_clean)\r\n    print(\"Was Attacked:\", is_attacked_clean)\r\n    print(\"L1 Pixel Difference:\", diff_clean)\r\n\r\n    print(\"\\n=== With Attack ===\")\r\n    print(\"Recovered Message:\", rec_message_attacked)\r\n    print(\"Extracted Message:\", extracted_message_attacked)\r\n    print(\"Was Attacked:\", is_attacked)\r\n    print(\"L1 Pixel Difference:\", diff_attacked)\r\n    print(\"L1 Pixel Psnr:\", attack_rec_psnr)\r\n\r\nelse:\r\n    print(\"Emebdding failed!\")\r\n\r\n```\r\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A robust reversible watermarking method that can robustly extract the watermark in lossy channels and perfectly recover both the cover image and the watermark in lossless channels.",
    "version": "1.1.0",
    "project_urls": {
        "Bug Reports": "https://github.com/chenoly/crmark/issues",
        "Documentation": "https://crmark.readthedocs.io",
        "Homepage": "https://github.com/chenoly/crmark",
        "Source": "https://github.com/chenoly/crmark"
    },
    "split_keywords": [
        "robust",
        "reversible",
        "watermarking"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "0718c25db6fa44855ce2d28c6b14f92d8906bfa4db5a9c1716cc19323db0b9cf",
                "md5": "f069bf2288f0e1c728f6b3f53795c6e0",
                "sha256": "9cc136fa8553b293986139069d02a6102a874da135a489c5321f8be1dc355f74"
            },
            "downloads": -1,
            "filename": "crmark-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f069bf2288f0e1c728f6b3f53795c6e0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 43677,
            "upload_time": "2025-07-23T03:09:31",
            "upload_time_iso_8601": "2025-07-23T03:09:31.843131Z",
            "url": "https://files.pythonhosted.org/packages/07/18/c25db6fa44855ce2d28c6b14f92d8906bfa4db5a9c1716cc19323db0b9cf/crmark-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "34f85802bff2c93339e59eb6a8c2ca0aaac84d67fd1c61f51b93434336fa262e",
                "md5": "b015a910224ee5761a9ba711c14d9a44",
                "sha256": "73365b8fe1595e09185d87bc6979a0b33b1bbeffc27010c205c5e88bf189d29a"
            },
            "downloads": -1,
            "filename": "crmark-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "b015a910224ee5761a9ba711c14d9a44",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 42959,
            "upload_time": "2025-07-23T03:09:33",
            "upload_time_iso_8601": "2025-07-23T03:09:33.251350Z",
            "url": "https://files.pythonhosted.org/packages/34/f8/5802bff2c93339e59eb6a8c2ca0aaac84d67fd1c61f51b93434336fa262e/crmark-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-23 03:09:33",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "chenoly",
    "github_project": "crmark",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "crmark"
}
        
Elapsed time: 1.63794s