@pilio/gemini-watermark-remover 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/README_zh.md +8 -1
- package/package.json +74 -3
- package/src/cli/gwrCli.js +1 -1
- package/src/cli/gwrRemoveCommand.js +82 -14
- package/src/core/adaptiveDetector.js +22 -3
- package/src/core/allenkFdncnnDenoise.js +326 -0
- package/src/core/allenkFdncnnNcnnModel.js +244 -0
- package/src/core/allenkFdncnnOnnxExport.js +337 -0
- package/src/core/allenkFdncnnOnnxRuntime.js +138 -0
- package/src/core/allenkFdncnnReferenceRuntime.js +169 -0
- package/src/core/alphaGradientMask.js +165 -0
- package/src/core/blendModes.js +9 -4
- package/src/core/candidateSelector.js +422 -33
- package/src/core/embeddedAlphaMaps.js +2 -0
- package/src/core/geminiSizeCatalog.js +44 -1
- package/src/core/restorationMetrics.js +44 -0
- package/src/core/watermarkEngine.js +2 -1
- package/src/core/watermarkProcessor.js +1048 -82
- package/src/sdk/index.d.ts +29 -1
- package/src/sdk/node.d.ts +13 -0
- package/src/sdk/node.js +7 -0
- package/src/sdk/video.d.ts +84 -0
- package/src/sdk/video.js +263 -0
- package/src/shared/debugFileHandoff.js +109 -0
package/README.md
CHANGED
|
@@ -127,11 +127,17 @@ node bin/gwr.mjs remove <input> --output <file>
|
|
|
127
127
|
gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json]
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
If you do not have `gwr` installed globally, use:
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
pnpm dlx @pilio/gemini-watermark-remover remove <input> --output <file>
|
|
134
|
-
```
|
|
130
|
+
If you do not have `gwr` installed globally, use:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pnpm dlx @pilio/gemini-watermark-remover remove <input> --output <file>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The default CLI file decoder/encoder uses `sharp`. SDK consumers that only use browser or `ImageData` APIs do not need it. If you use the CLI file path in your own project, install `sharp` alongside this package:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pnpm add sharp
|
|
140
|
+
```
|
|
135
141
|
|
|
136
142
|
### Can't Remove Your Watermark?
|
|
137
143
|
|
|
@@ -255,11 +261,12 @@ const result = await removeWatermarkFromBuffer(inputBuffer, {
|
|
|
255
261
|
- TypedArray (`Float32Array`, `Uint8ClampedArray`)
|
|
256
262
|
- for the website copy button: `navigator.clipboard.write(...)` and `ClipboardItem`
|
|
257
263
|
|
|
258
|
-
### CLI And Skill
|
|
259
|
-
|
|
260
|
-
- a local Node.js runtime capable of running this package
|
|
261
|
-
-
|
|
262
|
-
- for
|
|
264
|
+
### CLI And Skill
|
|
265
|
+
|
|
266
|
+
- a local Node.js runtime capable of running this package
|
|
267
|
+
- `sharp` installed when using the default CLI file decoder/encoder
|
|
268
|
+
- filesystem access for local input/output paths
|
|
269
|
+
- for repo-local usage:
|
|
263
270
|
|
|
264
271
|
```bash
|
|
265
272
|
node bin/gwr.mjs remove <input> --output <file>
|
package/README_zh.md
CHANGED
|
@@ -133,6 +133,12 @@ gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json]
|
|
|
133
133
|
pnpm dlx @pilio/gemini-watermark-remover remove <input> --output <file>
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
默认 CLI 文件解码/编码路径使用 `sharp`。只使用浏览器或 `ImageData` SDK 的消费者不需要它。如果你在自己的项目里使用 CLI 文件处理,请同时安装 `sharp`:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pnpm add sharp
|
|
140
|
+
```
|
|
141
|
+
|
|
136
142
|
### 去不掉的水印?
|
|
137
143
|
|
|
138
144
|
本工具专门针对 Gemini 可见水印(右下角半透明 Logo)。如果你的图片水印不属于 Gemini 已知格式,或需要去除其他类型的图片水印,可以试试通用 AI 去水印工具:
|
|
@@ -257,7 +263,8 @@ const result = await removeWatermarkFromBuffer(inputBuffer, {
|
|
|
257
263
|
|
|
258
264
|
### CLI 与 Skill
|
|
259
265
|
|
|
260
|
-
-
|
|
266
|
+
- 能运行本包的本地 Node.js 环境
|
|
267
|
+
- 使用默认 CLI 文件解码/编码路径时需要安装 `sharp`
|
|
261
268
|
- 可读写本地输入/输出路径的文件系统环境
|
|
262
269
|
- 在仓库内可直接使用:
|
|
263
270
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilio/gemini-watermark-remover",
|
|
3
3
|
"description": "Automatically removes watermarks from Gemini AI generated images",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.21",
|
|
5
5
|
"author": "GargantuaX",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"files": [
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"types": "./src/sdk/node.d.ts",
|
|
46
46
|
"import": "./src/sdk/node.js"
|
|
47
47
|
},
|
|
48
|
+
"./video": {
|
|
49
|
+
"types": "./src/sdk/video.d.ts",
|
|
50
|
+
"import": "./src/sdk/video.js"
|
|
51
|
+
},
|
|
48
52
|
"./runtime-browser": {
|
|
49
53
|
"types": "./src/runtime/browser.d.ts",
|
|
50
54
|
"import": "./src/runtime/browser.js"
|
|
@@ -54,15 +58,25 @@
|
|
|
54
58
|
"import": "./src/runtime/userscript.js"
|
|
55
59
|
}
|
|
56
60
|
},
|
|
57
|
-
"
|
|
61
|
+
"peerDependencies": {
|
|
58
62
|
"sharp": "^0.34.5"
|
|
59
63
|
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"sharp": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
60
69
|
"devDependencies": {
|
|
61
70
|
"@types/node": "^24.6.0",
|
|
62
71
|
"esbuild": "^0.24.0",
|
|
72
|
+
"onnxruntime-web": "^1.26.0",
|
|
63
73
|
"playwright": "^1.58.2",
|
|
74
|
+
"sharp": "^0.34.5",
|
|
64
75
|
"typescript": "^5.9.3"
|
|
65
76
|
},
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"mediabunny": "^1.46.0"
|
|
79
|
+
},
|
|
66
80
|
"scripts": {
|
|
67
81
|
"dev": "node build.js",
|
|
68
82
|
"build": "node build.js --prod",
|
|
@@ -78,12 +92,69 @@
|
|
|
78
92
|
"probe:real-page:compare": "node scripts/real-page-pixel-compare.js",
|
|
79
93
|
"benchmark:samples": "node scripts/sample-benchmark.js",
|
|
80
94
|
"benchmark:userscript": "node scripts/userscript-benchmark.js",
|
|
95
|
+
"compare:allenk-v2": "node scripts/create-allenk-v2-comparison-report.js",
|
|
96
|
+
"release:readiness": "node scripts/create-release-readiness-report.js",
|
|
97
|
+
"release:quality-gate": "pnpm compare:allenk-v2 -- --fail-on-incomplete && pnpm release:readiness -- --fail-on-not-ready",
|
|
98
|
+
"release:goal-audit": "node scripts/create-release-goal-audit-report.js",
|
|
99
|
+
"release:preflight": "pnpm test && pnpm build && pnpm package:extension && pnpm release:quality-gate && pnpm release:goal-audit -- --fail-on-incomplete",
|
|
100
|
+
"visible-residual:loop": "node scripts/run-visible-residual-loop.js",
|
|
101
|
+
"visible-residual:verify": "node scripts/verify-visible-residual-loop.js",
|
|
102
|
+
"visible-residual:validate-human-review": "node scripts/validate-visible-residual-human-review.js",
|
|
103
|
+
"visible-residual:create-gold-manifest": "node scripts/create-visible-residual-gold-manifest.js",
|
|
104
|
+
"visible-residual:review-status": "node scripts/report-visible-residual-review-progress.js",
|
|
105
|
+
"visible-residual:apply-focused-batch": "node scripts/apply-visible-residual-focused-review-batch.js",
|
|
106
|
+
"visible-residual:review-worksheet": "node scripts/create-visible-residual-review-worksheet.js",
|
|
107
|
+
"visible-residual:admission-report": "node scripts/create-visible-residual-admission-report.js",
|
|
108
|
+
"visible-residual:goal-audit": "node scripts/create-visible-residual-goal-audit-report.js",
|
|
109
|
+
"visible-residual:cluster-report": "node scripts/create-visible-residual-cluster-report.js",
|
|
110
|
+
"visible-residual:geometry-audit": "node scripts/audit-visible-residual-geometry.js",
|
|
81
111
|
"scan:sample-watermarks": "node scripts/scan-sample-watermarks.js",
|
|
82
112
|
"render:skipped-crops": "node scripts/render-skipped-candidate-crops.js",
|
|
113
|
+
"render:video-crops": "node scripts/render-video-crop-sheet.js",
|
|
114
|
+
"analyze:video-residual": "node scripts/analyze-video-residual.js",
|
|
115
|
+
"benchmark:video-crops": "node scripts/video-crop-benchmark.js",
|
|
116
|
+
"report:video-crops": "node scripts/report-video-crop-benchmark.js",
|
|
117
|
+
"diagnose:video-lowbody": "node scripts/diagnose-video-lowbody-regression.js",
|
|
118
|
+
"lab:video-frames": "node scripts/run-video-frame-backend-lab.js",
|
|
119
|
+
"lab:allenk-fdncnn-onnx-frames": "node scripts/run-allenk-fdncnn-onnx-frame-lab.js",
|
|
120
|
+
"lab:video-alpha-profile": "node scripts/run-video-alpha-profile-lab.js",
|
|
121
|
+
"lab:video-temporal-residual": "node scripts/run-video-temporal-residual-lab.js",
|
|
122
|
+
"fit:video-alpha-shape": "node scripts/fit-video-alpha-shape.js",
|
|
123
|
+
"gate:video-alpha-shape": "node scripts/gate-video-alpha-shape-candidates.js",
|
|
124
|
+
"gate:video-denoise": "node scripts/gate-video-denoise-candidates.js",
|
|
125
|
+
"gate:video-delivery": "node scripts/run-video-delivery-gate.js",
|
|
126
|
+
"report:video-alpha-policy-evidence": "node scripts/create-video-alpha-policy-evidence-report.js",
|
|
127
|
+
"report:video-frame-lab": "node scripts/report-video-frame-lab-sweep.js",
|
|
128
|
+
"report:video-review-pack": "node scripts/create-video-review-pack.js",
|
|
129
|
+
"report:video-review-index": "node scripts/create-video-review-index.js",
|
|
130
|
+
"report:video-review-screenshot": "node scripts/create-video-review-screenshot.js",
|
|
131
|
+
"report:video-review-thumbnail-sheet": "node scripts/create-video-review-thumbnail-sheet.js",
|
|
132
|
+
"report:video-dashboard-screenshot": "node scripts/create-video-review-screenshot.js --html .artifacts/video-delivery-dashboard/latest-video-dashboard.html --output .artifacts/video-delivery-dashboard/latest-video-dashboard.png --report .artifacts/video-delivery-dashboard/latest-video-dashboard-screenshot.json",
|
|
133
|
+
"report:video-review-decision": "node scripts/create-video-review-decision-report.js",
|
|
134
|
+
"report:video-acceptance-quickstart": "node scripts/create-video-acceptance-quickstart.js",
|
|
135
|
+
"report:video-pending-review-decision": "node scripts/create-video-pending-review-decision.js",
|
|
136
|
+
"report:video-alpha-policy-review-pack": "node scripts/create-video-alpha-policy-review-pack.js",
|
|
137
|
+
"report:video-light-polish-review-pack": "node scripts/create-video-light-polish-review-pack.js",
|
|
138
|
+
"report:video-polish-sweep-review-pack": "node scripts/create-video-polish-sweep-review-pack.js",
|
|
139
|
+
"report:video-delivery-dashboard": "node scripts/create-video-delivery-dashboard.js",
|
|
140
|
+
"report:video-goal-status": "node scripts/create-video-goal-status-report.js",
|
|
141
|
+
"report:video-delivery-bundle": "node scripts/create-video-delivery-bundle.js",
|
|
142
|
+
"verify:video-delivery-bundle": "node scripts/verify-video-delivery-bundle.js",
|
|
143
|
+
"export:video-backend": "node scripts/export-video-backend-variant.js",
|
|
144
|
+
"export:allenk-fdncnn-onnx-video": "node scripts/export-allenk-fdncnn-onnx-video.js",
|
|
145
|
+
"export:allenk-fdncnn-onnx-frame-video": "node scripts/export-allenk-fdncnn-onnx-frame-video.js",
|
|
146
|
+
"report:allenk-fdncnn-onnx-video-evidence": "node scripts/create-allenk-fdncnn-onnx-video-evidence.js",
|
|
147
|
+
"export:video-ui-preset": "node scripts/export-video-ui-preset.js",
|
|
148
|
+
"render:video-comparison-grid": "node scripts/render-video-comparison-grid.js",
|
|
83
149
|
"package:extension": "node scripts/package-extension-release.js",
|
|
84
150
|
"cli:smoke": "node bin/gwr.mjs --help",
|
|
85
151
|
"serve": "pnpm dlx serve dist",
|
|
86
152
|
"export:samples": "node scripts/export-fixed-samples.js",
|
|
87
|
-
"calibrate:preview-alpha": "node scripts/calibrate-preview-alpha.js"
|
|
153
|
+
"calibrate:preview-alpha": "node scripts/calibrate-preview-alpha.js",
|
|
154
|
+
"extract:allenk-fdncnn": "node scripts/extract-allenk-fdncnn-model.js",
|
|
155
|
+
"export:allenk-fdncnn-onnx": "node scripts/export-allenk-fdncnn-onnx.js",
|
|
156
|
+
"smoke:allenk-fdncnn-onnx-runtime": "node scripts/smoke-allenk-fdncnn-onnx-runtime.js",
|
|
157
|
+
"report:allenk-fdncnn-browser-spike": "node scripts/create-allenk-fdncnn-browser-spike-report.js",
|
|
158
|
+
"report:allenk-fdncnn-runtime-seam": "node scripts/create-allenk-fdncnn-runtime-seam-report.js"
|
|
88
159
|
}
|
|
89
160
|
}
|
package/src/cli/gwrCli.js
CHANGED
|
@@ -2,7 +2,7 @@ import { runRemoveCommand } from './gwrRemoveCommand.js';
|
|
|
2
2
|
|
|
3
3
|
export async function main(argv, io) {
|
|
4
4
|
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
5
|
-
io.stdout.write('Usage: gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json]\n');
|
|
5
|
+
io.stdout.write('Usage: gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json] [--video-page <url-or-file>]\n');
|
|
6
6
|
return 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { mkdir, readdir, stat } from 'node:fs/promises';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
inferMimeTypeFromPath,
|
|
6
|
+
inferVideoMimeTypeFromPath,
|
|
7
|
+
isVideoMimeType,
|
|
8
|
+
removeVideoWatermarkFromFile,
|
|
9
|
+
removeWatermarkFromFile
|
|
10
|
+
} from '../sdk/node.js';
|
|
5
11
|
|
|
6
12
|
const REMOVE_USAGE =
|
|
7
|
-
'Usage: gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json]';
|
|
13
|
+
'Usage: gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json] [--video-page <url-or-file>]';
|
|
8
14
|
|
|
9
15
|
export async function runRemoveCommand(argv, io) {
|
|
10
16
|
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
@@ -18,15 +24,7 @@ export async function runRemoveCommand(argv, io) {
|
|
|
18
24
|
return 2;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
const
|
|
22
|
-
io.stderr.write(`${error.message}\n`);
|
|
23
|
-
return null;
|
|
24
|
-
});
|
|
25
|
-
if (!codec) {
|
|
26
|
-
return 2;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const commandOptions = { ...options, codec };
|
|
27
|
+
const commandOptions = { ...options, imageCodecPromise: null };
|
|
30
28
|
const inputStats = await stat(commandOptions.input).catch(() => null);
|
|
31
29
|
if (!inputStats) {
|
|
32
30
|
io.stderr.write(`Input not found: ${commandOptions.input}\n`);
|
|
@@ -64,7 +62,11 @@ function parseRemoveArgs(argv) {
|
|
|
64
62
|
overwrite: false,
|
|
65
63
|
json: false,
|
|
66
64
|
decoder: null,
|
|
67
|
-
encoder: null
|
|
65
|
+
encoder: null,
|
|
66
|
+
videoPage: null,
|
|
67
|
+
videoDenoiseBackend: null,
|
|
68
|
+
videoTimeoutMs: null,
|
|
69
|
+
allowLowConfidence: false
|
|
68
70
|
};
|
|
69
71
|
|
|
70
72
|
let parseOptions = true;
|
|
@@ -116,6 +118,30 @@ function parseRemoveArgs(argv) {
|
|
|
116
118
|
continue;
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
if (token === '--video-page') {
|
|
122
|
+
const parsed = parseOptionValue(argv, index, '--video-page');
|
|
123
|
+
if (!parsed.ok) return parsed;
|
|
124
|
+
options.videoPage = parsed.value;
|
|
125
|
+
index = parsed.index;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (token === '--video-denoise-backend') {
|
|
130
|
+
const parsed = parseOptionValue(argv, index, '--video-denoise-backend');
|
|
131
|
+
if (!parsed.ok) return parsed;
|
|
132
|
+
options.videoDenoiseBackend = parsed.value;
|
|
133
|
+
index = parsed.index;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (token === '--video-timeout-ms') {
|
|
138
|
+
const parsed = parseOptionValue(argv, index, '--video-timeout-ms');
|
|
139
|
+
if (!parsed.ok) return parsed;
|
|
140
|
+
options.videoTimeoutMs = Number(parsed.value);
|
|
141
|
+
index = parsed.index;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
119
145
|
if (token === '--overwrite') {
|
|
120
146
|
options.overwrite = true;
|
|
121
147
|
continue;
|
|
@@ -126,6 +152,11 @@ function parseRemoveArgs(argv) {
|
|
|
126
152
|
continue;
|
|
127
153
|
}
|
|
128
154
|
|
|
155
|
+
if (token === '--allow-low-confidence') {
|
|
156
|
+
options.allowLowConfidence = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
129
160
|
return { ok: false, error: `Unknown option: ${token}` };
|
|
130
161
|
}
|
|
131
162
|
|
|
@@ -145,6 +176,10 @@ function parseRemoveArgs(argv) {
|
|
|
145
176
|
return { ok: false, error: '--decoder and --encoder must be used together.' };
|
|
146
177
|
}
|
|
147
178
|
|
|
179
|
+
if (options.videoTimeoutMs !== null && (!Number.isFinite(options.videoTimeoutMs) || options.videoTimeoutMs <= 0)) {
|
|
180
|
+
return { ok: false, error: '--video-timeout-ms must be a positive number.' };
|
|
181
|
+
}
|
|
182
|
+
|
|
148
183
|
return options;
|
|
149
184
|
}
|
|
150
185
|
|
|
@@ -301,17 +336,50 @@ async function processOneFile(inputPath, outputPath, options) {
|
|
|
301
336
|
}
|
|
302
337
|
|
|
303
338
|
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
339
|
+
if (isVideoPath(inputPath) || isVideoPath(outputPath)) {
|
|
340
|
+
const result = await removeVideoWatermarkFromFile(inputPath, {
|
|
341
|
+
outputPath,
|
|
342
|
+
mimeType: inferVideoMimeTypeFromPath(outputPath || inputPath),
|
|
343
|
+
pagePath: options.videoPage || undefined,
|
|
344
|
+
denoiseBackend: options.videoDenoiseBackend || undefined,
|
|
345
|
+
timeoutMs: Number.isFinite(options.videoTimeoutMs) && options.videoTimeoutMs > 0
|
|
346
|
+
? options.videoTimeoutMs
|
|
347
|
+
: undefined,
|
|
348
|
+
allowLowConfidence: options.allowLowConfidence
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
input: inputPath,
|
|
353
|
+
output: outputPath,
|
|
354
|
+
kind: 'video',
|
|
355
|
+
meta: result.meta
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const codec = await getImageCodec(options);
|
|
304
360
|
const mimeType = inferMimeTypeFromPath(outputPath);
|
|
305
361
|
const result = await removeWatermarkFromFile(inputPath, {
|
|
306
362
|
outputPath,
|
|
307
363
|
mimeType,
|
|
308
|
-
decodeImageData:
|
|
309
|
-
encodeImageData:
|
|
364
|
+
decodeImageData: codec.decodeImageData,
|
|
365
|
+
encodeImageData: codec.encodeImageData
|
|
310
366
|
});
|
|
311
367
|
|
|
312
368
|
return {
|
|
313
369
|
input: inputPath,
|
|
314
370
|
output: outputPath,
|
|
371
|
+
kind: 'image',
|
|
315
372
|
meta: result.meta
|
|
316
373
|
};
|
|
317
374
|
}
|
|
375
|
+
|
|
376
|
+
function isVideoPath(filePath) {
|
|
377
|
+
return isVideoMimeType(inferVideoMimeTypeFromPath(filePath));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function getImageCodec(options) {
|
|
381
|
+
if (!options.imageCodecPromise) {
|
|
382
|
+
options.imageCodecPromise = resolveCodecOptions(options);
|
|
383
|
+
}
|
|
384
|
+
return options.imageCodecPromise;
|
|
385
|
+
}
|
|
@@ -7,6 +7,8 @@ import { resolveGeminiWatermarkSearchConfigs } from './geminiSizeCatalog.js';
|
|
|
7
7
|
|
|
8
8
|
const DEFAULT_THRESHOLD = 0.35;
|
|
9
9
|
const EPSILON = 1e-8;
|
|
10
|
+
const REFERENCE_WATERMARK_SIZE = 96;
|
|
11
|
+
const MIN_COARSE_ADJUSTED_SCORE = 0.08;
|
|
10
12
|
|
|
11
13
|
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
12
14
|
|
|
@@ -172,6 +174,23 @@ function createScaleList(minSize, maxSize) {
|
|
|
172
174
|
return [...set].sort((a, b) => a - b);
|
|
173
175
|
}
|
|
174
176
|
|
|
177
|
+
export function computeSizeAdjustedConfidence(confidence, size, referenceSize = REFERENCE_WATERMARK_SIZE) {
|
|
178
|
+
if (
|
|
179
|
+
!Number.isFinite(confidence) ||
|
|
180
|
+
!Number.isFinite(size) ||
|
|
181
|
+
!Number.isFinite(referenceSize) ||
|
|
182
|
+
size <= 0 ||
|
|
183
|
+
referenceSize <= 0
|
|
184
|
+
) {
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// NCC tends to favor tiny templates. A cube-root penalty keeps that bias in
|
|
189
|
+
// check without crushing legitimate preview-tier watermarks.
|
|
190
|
+
const sizeWeight = Math.min(1, Math.cbrt(size / referenceSize));
|
|
191
|
+
return confidence * sizeWeight;
|
|
192
|
+
}
|
|
193
|
+
|
|
175
194
|
function buildSeedConfigs(width, height, defaultConfig) {
|
|
176
195
|
// Start adaptive search from both the coarse default anchor and any
|
|
177
196
|
// catalog-projected anchors for official or near-official Gemini sizes.
|
|
@@ -407,7 +426,7 @@ export function detectAdaptiveWatermarkRegion({
|
|
|
407
426
|
size: seedCandidate.size,
|
|
408
427
|
x: seedCandidate.x,
|
|
409
428
|
y: seedCandidate.y,
|
|
410
|
-
adjustedScore: seedCandidate.confidence
|
|
429
|
+
adjustedScore: computeSizeAdjustedConfidence(seedCandidate.confidence, seedCandidate.size)
|
|
411
430
|
});
|
|
412
431
|
}
|
|
413
432
|
|
|
@@ -424,8 +443,8 @@ export function detectAdaptiveWatermarkRegion({
|
|
|
424
443
|
if (!score) continue;
|
|
425
444
|
|
|
426
445
|
// Prefer sizes close to known watermark scales to avoid tiny-template bias.
|
|
427
|
-
const adjustedScore = score.confidence
|
|
428
|
-
if (adjustedScore <
|
|
446
|
+
const adjustedScore = computeSizeAdjustedConfidence(score.confidence, size);
|
|
447
|
+
if (adjustedScore < MIN_COARSE_ADJUSTED_SCORE) continue;
|
|
429
448
|
|
|
430
449
|
pushTopK({
|
|
431
450
|
size,
|