watermark

package module
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 3, 2026 License: MIT Imports: 7 Imported by: 0

README

watermark_zero

The High-Efficiency Go Implementation of guofei9987/blind_watermark

This Go language implementation draws inspiration from the ideas and approach of guofei9987/blind_watermark regarding the fundamental signal processing logic for digital watermarks. The source of the base logic is the MIT License, and its copyright notice and license terms are included in the THIRD_PARTY_LICENSES.txt file within this repository.

Key Features

  • Golay Error Correction: Unlike the original implementation, this library incorporates Golay code (23,12) for robust error correction, significantly improving watermark extraction success rates
  • Default Parameters (21×9): Optimized block size providing stable extraction even with low embedding counts, while minimizing visible noise in low-frequency regions (e.g., blue skies)
  • High-Performance SVD: Utilizes gonum for efficient Singular Value Decomposition operations

Install

go get github.com/yyyoichi/watermark_zero

Example


func main() {
	var img image.Image // load from anywhere 
	w, _ := watermark.New(
		watermark.WithBlockShape(8, 6),
		watermark.WithD1D2(36, 20),
	)
	mark := mark.NewString("@Copyright2025_USERXX")
	markedImg, _ := w.Embed(context.Background(), img, mark)
}

WithoutECC vs WithGolay

This section provides a comparison and explanation of mark encoding methods.

WithoutECC uses traditional byte arrays converted directly into bit sequences for embedding. For example, a would be converted as: 0x61 -> 0b01100001

WithGolay utilizes Golay code (23,12) with error correction capability. Golay code is an error-correcting code that encodes 12 bits of data into 23 bits, enabling correction of up to 3-bit errors. For example, when a 12-bit sequence is Golay-encoded, it becomes a 23-bit sequence.

In conclusion, we strongly recommend using WithGolay.

Characteristics in Digital Watermarking

In digital watermark extraction, success is only achieved when all bits of the bit sequence are correctly extracted. In other words, if even 1 bit is incorrect, the entire process is considered a failure. For example, if you embed 100 bits and correctly extract 50 bits, 1 bit, or even 99 bits, anything less than 100 bits is considered a complete failure.

With WithoutECC, since not even a single bit can be incorrect, the extraction success rate is likely to be lower.

With WithGolay, although nearly twice the amount of information is embedded, error correction allows for tolerance of some errors while still achieving success, meaning it is expected to increase the extraction success rate.

Comparison

sgolay-vs-noecc

Reference: Golay Encoding Package

https://pkg.go.dev/github.com/yyyoichi/golay

go get github.com/yyyoichi/golay

Parameters and Noise Level

Increasing the D1 and D2 parameters improves the extraction success rate, but image quality will degrade. Additionally, dividing the image into finer blocks increases the embedding capacity, but also increases noise.

ssim

success-rate

Reference

These examples are implemented in ./exp. All experiments were conducted to investigate resistance to JPEG compression.

Benchmark

$ go test -bench=. ./bench/ -bench=^BenchmarkEmbed_FHD$ -benchmem -cpu=1,2,3,4 -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/yyyoichi/watermark_zero/bench
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkEmbed_FHD/4x4_D1                     18         683055045 ns/op        708325051 B/op  13349116 allocs/op
BenchmarkEmbed_FHD/4x4_D1-2                   12         931026118 ns/op        708289144 B/op  13349014 allocs/op
BenchmarkEmbed_FHD/4x4_D1-3                   18         732031677 ns/op        708301659 B/op  13349061 allocs/op
BenchmarkEmbed_FHD/4x4_D1-4                   15         767310390 ns/op        708324182 B/op  13349109 allocs/op
BenchmarkEmbed_FHD/4x4_D1D2                   14         774766221 ns/op        708326742 B/op  13349116 allocs/op
BenchmarkEmbed_FHD/4x4_D1D2-2                 12         912351192 ns/op        708287454 B/op  13349013 allocs/op
BenchmarkEmbed_FHD/4x4_D1D2-3                 16         750195511 ns/op        708301917 B/op  13349062 allocs/op
BenchmarkEmbed_FHD/4x4_D1D2-4                 14         783641077 ns/op        708324888 B/op  13349112 allocs/op
BenchmarkEmbed_FHD/8x8_D1                     24         454617080 ns/op        373166599 B/op   4892570 allocs/op
BenchmarkEmbed_FHD/8x8_D1-2                   21         533784334 ns/op        373136603 B/op   4892521 allocs/op
BenchmarkEmbed_FHD/8x8_D1-3                   28         443597994 ns/op        373147044 B/op   4892542 allocs/op
BenchmarkEmbed_FHD/8x8_D1-4                   26         443126212 ns/op        373163018 B/op   4892566 allocs/op
BenchmarkEmbed_FHD/8x8_D1D2                   26         462500377 ns/op        373164116 B/op   4892566 allocs/op
BenchmarkEmbed_FHD/8x8_D1D2-2                 21         548263726 ns/op        373137639 B/op   4892518 allocs/op
BenchmarkEmbed_FHD/8x8_D1D2-3                 25         459467989 ns/op        373150312 B/op   4892545 allocs/op
BenchmarkEmbed_FHD/8x8_D1D2-4                 26         441787512 ns/op        373163220 B/op   4892566 allocs/op
BenchmarkEmbed_FHD/12x12_D1                   24         459372212 ns/op        310172016 B/op   3326536 allocs/op
BenchmarkEmbed_FHD/12x12_D1-2                 21         530219781 ns/op        310152254 B/op   3326507 allocs/op
BenchmarkEmbed_FHD/12x12_D1-3                 27         442878278 ns/op        310159773 B/op   3326520 allocs/op
BenchmarkEmbed_FHD/12x12_D1-4                 25         461542104 ns/op        310172684 B/op   3326534 allocs/op
BenchmarkEmbed_FHD/12x12_D1D2                 25         462859746 ns/op        310172977 B/op   3326538 allocs/op
BenchmarkEmbed_FHD/12x12_D1D2-2               21         539403559 ns/op        310151166 B/op   3326505 allocs/op
BenchmarkEmbed_FHD/12x12_D1D2-3               26         447806862 ns/op        310160117 B/op   3326522 allocs/op
BenchmarkEmbed_FHD/12x12_D1D2-4               27         463405114 ns/op        310173060 B/op   3326537 allocs/op
BenchmarkEmbed_FHD/16x16_D1                   26         467656523 ns/op        286880552 B/op   2773204 allocs/op
BenchmarkEmbed_FHD/16x16_D1-2                 20         547519096 ns/op        286844256 B/op   2773178 allocs/op
BenchmarkEmbed_FHD/16x16_D1-3                 24         465889750 ns/op        286859918 B/op   2773190 allocs/op
BenchmarkEmbed_FHD/16x16_D1-4                 25         461271046 ns/op        286878939 B/op   2773205 allocs/op
BenchmarkEmbed_FHD/16x16_D1D2                 24         458887882 ns/op        286877920 B/op   2773204 allocs/op
BenchmarkEmbed_FHD/16x16_D1D2-2               20         540008836 ns/op        286843490 B/op   2773176 allocs/op
BenchmarkEmbed_FHD/16x16_D1D2-3               25         457357105 ns/op        286855818 B/op   2773190 allocs/op
BenchmarkEmbed_FHD/16x16_D1D2-4               24         463504708 ns/op        286878187 B/op   2773204 allocs/op
PASS
ok      github.com/yyyoichi/watermark_zero/bench        368.540s

Documentation

Overview

Example (Batch)
package main

import (
	"context"
	"fmt"
	"image"

	watermark "github.com/yyyoichi/watermark_zero"
	"github.com/yyyoichi/watermark_zero/mark"
)

func main() {
	ctx := context.Background()
	img := image.NewGray(image.Rect(0, 0, 200, 200))

	opts := []watermark.Option{
		watermark.WithBlockShape(4, 4),
		watermark.WithD1D2(21, 11),
	}

	batch := watermark.NewBatch(img)
	for _, m := range []string{"Hello!", "こんにちは!"} {
		mark := mark.NewString(m)
		markedImg, _ := batch.Embed(ctx, mark, opts...)

		extractedMark, _ := watermark.Extract(ctx, markedImg, mark, opts...)

		fmt.Println(extractedMark.DecodeToString())
	}

}
Output:

Hello!
こんにちは!
Example (MismatchedMarkOptions)
package main

import (
	"context"
	"fmt"
	"image"

	watermark "github.com/yyyoichi/watermark_zero"
	"github.com/yyyoichi/watermark_zero/mark"
)

func main() {
	ctx := context.Background()
	img := image.NewGray(image.Rect(0, 0, 200, 200))
	w, _ := watermark.New(
		watermark.WithBlockShape(4, 4),
		watermark.WithD1D2(21, 11),
	)

	// Embed a mark with Golay encoding (default seed)
	embedMark := mark.NewString("Test")
	markedImg, _ := w.Embed(ctx, img, embedMark)

	// Try to extract with different options (WithoutECC)
	wrongExtractMark1 := mark.NewExtract(embedMark.ExtractSize(), mark.WithoutECC())
	wrongDecoded1, _ := w.Extract(ctx, markedImg, wrongExtractMark1)
	result1 := wrongDecoded1.DecodeToString()
	fmt.Printf("WithoutECC matches 'Test': %v\n", result1 == "Test")

	// Try to extract with different seed
	wrongExtractMark2 := mark.NewExtract(embedMark.ExtractSize(), mark.WithGolay(99999))
	wrongDecoded2, _ := w.Extract(ctx, markedImg, wrongExtractMark2)
	result2 := wrongDecoded2.DecodeToString()
	fmt.Printf("Different seed matches 'Test': %v\n", result2 == "Test")

	// Extract with correct options (same as embedding)
	correctExtractMark := mark.NewExtract(embedMark.ExtractSize()) // Uses default Golay encoding and default seed
	correctDecoded, _ := w.Extract(ctx, markedImg, correctExtractMark)
	result3 := correctDecoded.DecodeToString()
	fmt.Printf("Correct options matches 'Test': %v\n", result3 == "Test")

}
Output:

WithoutECC matches 'Test': false
Different seed matches 'Test': false
Correct options matches 'Test': true
Example (Watermark)
package main

import (
	"context"
	"fmt"
	"image"

	watermark "github.com/yyyoichi/watermark_zero"
	"github.com/yyyoichi/watermark_zero/mark"
)

func main() {
	ctx := context.Background()
	img := image.NewGray(image.Rect(0, 0, 200, 200))
	// Initialize watermark processor with default settings
	w, _ := watermark.New(
		watermark.WithBlockShape(4, 4),
		watermark.WithD1D2(21, 11),
	)

	// Define a bit sequence to embed
	m := mark.NewString("Test-Mark")

	// Embed the watermark
	markedImg, _ := w.Embed(ctx, img, m)

	// Extract the watermark
	extractedMark, _ := w.Extract(ctx, markedImg, m)
	fmt.Println(extractedMark.DecodeToString())
	exM := mark.NewExtract(m.ExtractSize())
	extractedMark, _ = w.Extract(ctx, markedImg, exM)
	fmt.Println(extractedMark.DecodeToString())

}
Output:

Test-Mark
Test-Mark

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrTooSmallImage = errors.New("image is too small for embedding or extracting")
)

Functions

func Embed added in v0.2.0

func Embed(ctx context.Context, src image.Image, mark EmbedMark, opts ...Option) (image.Image, error)

Embed embeds a bit sequence into an image with the specified options. This is a convenience function that creates a Watermark instance and calls its Embed method.

Types

type Batch added in v0.2.0

type Batch struct {
	// contains filtered or unexported fields
}

Batch enables efficient multiple watermark operations on a single image by caching intermediate computation results (wavelets and DCT).

func NewBatch added in v0.2.0

func NewBatch(src image.Image) *Batch

NewBatch creates a new Batch instance and pre-computes wavelet transforms and initializes DCT cache for the given image.

func (*Batch) Embed added in v0.2.0

func (b *Batch) Embed(ctx context.Context, mark EmbedMark, opts ...Option) (image.Image, error)

Embed embeds a bit sequence into the cached image with specified options.

func (*Batch) Extract added in v0.2.0

func (b *Batch) Extract(ctx context.Context, markLen int, opts ...Option) ([]bool, error)

Extract extracts a bit sequence from the cached image with specified options.

type EmbedMark added in v0.2.0

type EmbedMark interface {
	// GetBit returns the bit value at the specified position as a float64.
	GetBit(at int) float64
	MarkCore
}

EmbedMark defines the interface for embedding watermarks. It provides methods to retrieve mark bits for embedding into media.

type ExtractMark added in v0.2.0

type ExtractMark interface {
	// NewDecoder receives the extracted bit sequence and initializes a MarkDecoder.
	NewDecoder([]bool) MarkDecoder
	MarkCore
}

ExtractMark defines the interface for extracting watermarks. It provides methods to initialize a decoder from extracted bit sequences.

type MarkCore added in v0.2.0

type MarkCore interface {
	// Len returns the bit length of the encoded mark after applying error correction.
	Len() int
	// ExtractSize returns the bit length required for watermark extraction.
	ExtractSize() int
}

MarkCore defines the core interface for mark operations. It provides methods to get the encoded mark length and the extraction size.

type MarkDecoder added in v0.2.0

type MarkDecoder interface {
	// DecodeToBytes decodes the watermark data and returns it as a byte slice.
	DecodeToBytes() []byte
	// DecodeToString decodes the watermark data and returns it as a string.
	DecodeToString() string
	// DecodeToBools decodes the watermark data and returns it as a boolean slice.
	DecodeToBools() []bool
}

MarkDecoder defines the interface for decoding extracted watermark data. It provides methods to convert the decoded data into various formats.

func Extract added in v0.2.0

func Extract(ctx context.Context, src image.Image, mark ExtractMark, opts ...Option) (MarkDecoder, error)

Extract extracts a bit sequence from an image with the specified options. This is a convenience function that creates a Watermark instance and calls its Extract method.

type Option

type Option func(*Watermark) error

func WithBlockShape

func WithBlockShape(width, height int) Option

WithBlockShape divides the image into blocks of the specified size for processing. For example, for a 600x480 image with an 8x6 block shape, it creates 75 horizontal and 80 vertical blocks.

Block shapes must be specified with even numbers, with a minimum size of 4x4. If odd numbers are provided, they are automatically rounded up to the next even number. If values smaller than 4 are provided, they are set to 4.

func WithD1

func WithD1(d1 int) Option

WithD1 specifies the d1 parameter for watermark embedding and extraction. Larger values increase noise but improve robustness. This option has less computational cost than WithD1D2 but may have lower robustness in comparison.

func WithD1D2

func WithD1D2(d1, d2 int) Option

WithD1D2 specifies both d1 and d2 parameters for watermark embedding and extraction. Larger values increase noise but improve robustness. This option has higher computational cost than WithD1 but may provide better robustness.

type Watermark

type Watermark struct {
	// contains filtered or unexported fields
}

func New

func New(opts ...Option) (*Watermark, error)

New initializes a watermark processing structure. The blockShape and watermark coefficients d1, d2 can be optionally specified. For default values, refer to the init function.

func (*Watermark) Embed

func (w *Watermark) Embed(ctx context.Context, src image.Image, mark EmbedMark) (image.Image, error)

Embed embeds a bit sequence into an image.

Process:

  1. Converts the image to YUV color channels.
  2. Applies Haar wavelet transform to each channel.
  3. Divides the low-frequency region (cA) of each channel into blocks.
  4. Embeds one bit per block using Discrete Cosine Transform and SVD.
  5. Applies inverse transforms.
  6. Reconstructs the image.

Returns an error if the image is too small for the bit sequence to be embedded.

func (*Watermark) Extract

func (w *Watermark) Extract(ctx context.Context, src image.Image, mark ExtractMark) (MarkDecoder, error)

Extract extracts a bit sequence from an image.

Process:

  1. Converts the image to YUV color channels.
  2. Applies Haar wavelet transform to each channel.
  3. Divides the low-frequency region (cA) of each channel into blocks.
  4. Extracts one bit per block using Discrete Cosine Transform and SVD.
  5. Determines boolean values using k-means clustering on the average values of each block's bits.

Returns an error if the image is too small for the expected bit sequence length.

Directories

Path Synopsis
internal
dct
dwt
svd
yuv

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL