nsF5 图像隐写工具 (Steganography)
目录
- English Overview
- 互动教学网站
- 功能总览
- 学习手册
- 同类工具与本项目定位
- 更正记录:曾经出现过的错误
- 安装与运行
- GUI 使用流程
- 目录结构
- 技术细节
- 有监督 ML 隐写分析(C++ 特征提取 + 校园照片训练)
- GPU 版 (v1.2):PyTorch 批量向量化的统计特征分析
- 正确性验证体系(v1.9.0 起成文)
- 持续集成 & 发版
- 版本历史
- 许可
English Overview
nsF5 Steganography is an open-source teaching and research toolkit for image steganography and steganalysis. It implements the classic nsF5 algorithm - Hamming syndrome-matrix coding combined with wet paper coding - plus blind steganalysis (chi-square and RS), SHA-256 content keying, and supervised machine-learning detection with two deployable LightGBM models.
The project is pure Python at its core and runs on Windows, Linux, macOS, and
Colab. Optional C++ accelerators speed up feature extraction, embedding, and
shuffling; build them with make cpp (or python scripts/build_cpp.py). They
are never shipped as binaries - the same sources build a .dll, .so or
.dylib as appropriate - and when they are absent the code falls back to
equivalent pure-Python implementations, so notebooks, Docker, and cloud
environments work out of the box. (Feature extraction is bit-identical between
the two; the nsF5 embedder is interoperable but not pixel-identical -- see the
note in the Chinese section.)
Highlights
- Embedding / decoding - UTF-8 messages hidden in 8-bit grayscale or color images with password keying and self-synchronizing SHA-256 content hashing;
- nsF5 core - binary Hamming codes
[n=2^p-1, k, 3]with syndrome matrix embedding, F5-style magnitude decrease, and wet paper coding (no shrinkage, no retries); - JPEG-domain nsF5 (v1.9.0) - the textbook battlefield: embedding on
quantized DCT coefficients via the sister package
yccstego(declared as a dependency on Python >= 3.10);--jpegacross the CLI, a domain switch in the GUI, and a DCT-fingerprint analyzer; - Reproducibility (v1.9.0) - one JSON experiment record per embed
(parameters and hashes only, never the plaintext), re-run and verified with
nsf5stego repro: byte-deterministic in both domains on a matching yccstego version (>=0.2.0), falling back to round-trip verification across versions; - Blind steganalysis - Westfeld chi-square and Fridrich RS analysis with a content-aware verdict and three sensitivity modes;
- ML steganalysis - 11-D statistical features (v1) and 143-D v2 features (SRM residuals + prefix chi-square statistics), trained with photo-grouped cross-validation; ships both a 143-D robust model and a 53-D interpretable model;
- Cross-platform - pure-Python fallbacks for features, embedding, and model inference; CI verifies Ubuntu, macOS, and Windows on every change;
- Teaching-first - GUI matrix-coding animation, one-click Colab/Jupyter notebooks per chapter, dataset downloader, Docker/JupyterLab image, and a bilingual web handbook.
Quick Start (Linux / macOS / Windows)
git clone https://github.com/Yukinoshita-lin/nsf5-steganography.git
cd nsf5-steganography
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install -e .
python src/test_core.py # algorithm self-tests
python src/test_steg.py # steganalysis self-tests
python src/run_e2e.py # full embed -> decode -> analyze demo
nsf5stego embed cover.png -m "msg" # CLI: embed / extract / analyze / gui
python src/gui.py # GUI (requires tkinter)
Or use the bundled Makefile: make install, make test, make e2e,
make notebooks, make dataset, make docker.
Windows installer: no Python needed — download
nsf5stego-setup-<version>.exefrom the Releases page (Start-menu shortcut, optional PATH entry, Chinese wizard). See the Chinese section "方式 0:Windows 安装版".
Learning Resources
- English handbook (PDF): Learning-Handbook-From-Zero-to-nsF5-Steganography.pdf
- Chinese handbook (PDF): 学习手册-从零读懂nsF5隐写项目.pdf
- Online bilingual handbook: Interactive learning lab · zh · en
- Per-chapter Colab/Jupyter notebooks and Docker instructions: teaching/README.md
- Dataset downloader (BOSSbase 1.01):
python scripts/download_datasets.py --out data/BOSSbase_1.01
License
Apache-2.0 - see LICENSE and NOTICE.
针对 8bit 灰度/彩色图像 的隐写研究工具,实现了基于伴随式矩阵编码(二元汉明码) 的 nsF5 隐写算法,并附带盲隐写分析、图像哈希键控与码族/嵌入效率可视化。
项目核心为纯 Python(依赖 numpy/Pillow,GUI 使用标准库 tkinter),可在
Windows / Linux / macOS / Colab 直接运行。另提供可选的 C++ 加速库
(cpp/fsfeatures.cpp 特征提取、cpp/nsf5embed.cpp 嵌入热路径 + 确定性置乱
nsf5_permute):三平台同一份源码,用 make cpp 编译成 .dll/.so/.dylib
(不入库,见 scripts/build_cpp.py)。库缺失时自动回退到同算法纯 Python
实现:嵌入/解码仍可逆、11-D/143-D 特征与双版本 ML 模型均可使用。
互动教学网站
GitHub Pages 首页已升级为交互式双语教学网站(不依赖手册即可动手理解项目):
- 🔗 主站: https://yukinoshita-lin.github.io/nsf5-steganography/
- 🌐 语言切换: 页面右上角一键中英切换,或使用
?lang=zh/?lang=en - 🧪 交互实验: LSB 位平面(可抽出单层观察 + 按权重叠加还原)/ LSB 嵌入→解码闭环
- 卡方/RS 实时自检 + 改动像素掩码 / 汉明伴随式找位 / 湿纸干点求解(自动演示) / nsF5 全流程对比(朴素 LSB vs 项目真实 nsF5Pixel:矩阵编码 + 减幅修改 + 湿点避让, 改动统计 / PSNR / 卡方·RS 指纹一图对比,含解码往返与固定种子置乱路径)/ ML 阈值判别 / 载荷扫描(真实项目统计)/ 10 题双语自测计分
- 🗺 教学层次: 首页"本页导航"把整页分成动手实验/原理/学习路径/FAQ+自测 四部分, 每节带部分徽章,末尾附双语 FAQ 手风琴答疑
- 🖼 可视化: 16 张教学示意图,覆盖 cover↔stego、位平面分层加权叠加、统计直方图、 湿纸、效率曲线、ROC 与双模型对比
- 📚 原手册仍保留:
/zh与/en - 💻 本地体验: 仓库根目录
make webapp(即python -m http.server 8080 --directory webapp) 后打开 http://127.0.0.1:8080。直接双击webapp/index.html时file://下 fetch 本地 JSON 会被浏览器拦, 载荷扫描实验拿不到数据 —— 所以要起 http 服务 - 🧪 自动化回归:
webapp/tests/(DOM 冒烟 + 交互 + 桌面/移动布局), 由.github/workflows/webapp-tests.yml在每次改动webapp/**时执行
功能总览
| 模块 | 说明 |
|---|---|
| 嵌入 / 解码 | 将 UTF-8 文本嵌入图像 LSB,解码还原;支持口令键控 |
| 伴随式矩阵编码 | nsF5 + F5 / LSB 矩阵编码,二元汉明码 [n=2^p-1, k, 3],块内至多改 1 系数 |
| 湿纸编码 | nsF5 核心:预标记"减幅归零=湿"位置,在干位解 GF(2) 线性方程,无收缩 |
| JPEG 压缩域 (v1.9.0) | 经 src/jpegstego.py 桥接姊妹项目 yccstego:在量化 DCT 系数上做教科书版 nsF5,CLI --jpeg / GUI 一键切换,输出标准 .jpg |
| 实验档案与重跑 (v1.9.0) | 每次嵌入可导出 JSON 档案(不含消息明文),nsf5stego repro 一键重跑校验:同版本下两域均逐字节复现,跨版本退回提取一致 |
| 往返自检 (v1.9.0) | GUI 一键做 嵌入→提取→比对 内存闭环,正确性当场可见,不只靠作者声称 |
| 图像哈希键控 | 载入时计算 SHA-256;隐藏路径由"内容哈希+口令"唯一决定,解码端自同步并感知篡改 |
| 盲隐写分析 | 卡方检验(Westfeld) + RS 分析(Fridrich),输出 0–1 隐写倾向概率与判读;--jpeg 走 DCT 域 |c|=1 指纹 |
| ML 隐写分类器(双版本) | 143d 稳健版(默认) + 53d 可解释版,详见下文"双版本部署策略" |
| 绘图 | 绘制码族(嵌入率 α vs 载荷)理论曲线 与 实测嵌入效率对比 |
| GUI | 载入图 → 选嵌入域 → 嵌入/解码 → 分析 → 自检/导出档案/绘图 一体化界面 |
学习手册
项目提供中英文双语学习手册,从零基础开始,12 周快速入门;想深入可预留 6–12 个月(见手册附录 F 的完整路线):
- 🖥 网页版: 中文 · English
- 🇨🇳
docs/学习手册-从零读懂nsF5隐写项目.pdf— 中文版, 75 页 - 🇬🇧
docs/Learning-Handbook-From-Zero-to-nsF5-Steganography.pdf— English, 84 pages - 📓 按章 Colab/Jupyter Notebook: 见
teaching/README.md - 🐳 Docker/JupyterLab 教学镜像:
docker compose up --build
姊妹项目:yccstego(pip install yccstego)——
把 nsF5 搬到 JPEG 量化 DCT 系数(Y 通道)上的压缩域实现,含自写的 DCT/Huffman 编解码。
它仍是独立仓库与独立发版的 PyPI 包,但从 v1.9.0 起已接入本项目主线:
nsf5stego 按 Python 版本自动声明对它的依赖(>=3.10),CLI 加 --jpeg、GUI 加"嵌入域"
切换、分析加 DCT 指纹,桥接层在 src/jpegstego.py(依赖缺失时
像素域功能完全不受影响)。手册第 11 章与附录 F 保留"进 yccstego 源码"的进阶路径。
涵盖: 数字图像基础 → Python 入门 → LSB 隐写 → 卡方/RS 分析 → 汉明矩阵编码 → F5/nsF5 → 湿纸编码 → 哈希键控 → 机器学习基础 → v1/v2 特征工程 → SRM 滤波 → 143d/53d 双版本模型 → C++/GPU 加速 → 综合实验。每章配有"动手做"实验与"想一想"思考题, 适合本科毕设自学。v1.9.0 起新增第 1½ 章"JPEG 是什么, DCT 系数是什么"——在进入 LSB 之前先看懂压缩域隐写的真正战场, 并直接调用已接入主线的 yccstego 动手做。
同类工具与本项目定位
这个领域不是没有实践工具, 而是资源分散、上手门槛高。诚实地列几个同类项目 (非穷举; 链接只列长期稳定、我们核对过的, 其余请按名字检索, 以原仓库为准):
| 工具 | 语言 | 侧重 |
|---|---|---|
| Aletheia | Python | 图像隐写分析工具箱: 经典统计攻击 + 特征提取 + ML 检测, 命令行驱动 |
| CONSEAL | C++ | 面向研究的隐写/隐写分析库, 高性能特征提取与嵌入模拟, 服务学术实验 |
| DDE Lab tools | Java | 高校实验室公开的隐写/隐写分析研究工具集 |
| steghide / outguess | C/C++ | 经典通用隐写工具 (嵌入侧重, 自带压缩/加密), 与 nsF5 算法族不同支 |
本项目的定位不是替代它们: 上述工具面向研究者做检测与攻击实验; 本项目做的是 interactive learning / visualization / reproducible experiments —— 把 nsF5 从像素到 DCT 系数端到端实现并配齐教学材料 (双语手册 / 按章 Notebook / 交互网站 / GUI 算法动画), 每一步实验可导出档案、一键重跑。想快速看清"算法正在发生什么", 用本项目; 要做研究级的检测对比, 请用 Aletheia / CONSEAL 等成熟工具链。
模型双版本(2026-09-06)
项目保留两套训练好的 LGB 模型,默认加载 143d 稳健版,可切换到 53d 可解释版:
from src.ml_predict import MLPredictor
# 默认 143d (稳健, 部署推荐)
pred = MLPredictor() # models/stego_classifier.joblib
# 切换 53d 可解释版 (AUC 略低, 但每一维都能解释; 教学/答辩推荐)
pred = MLPredictor(model_path='models/stego_classifier_v2_jpeg_lgb_51d.joblib',
clip_outliers=False)
# 预测
r = pred.predict(image)
# r = {'probability': 0.83, 'verdict': '含密(stego)', 'threshold': 0.168}
指标口径(重要):头条只引用 BOSSbase
同一个 143d 模型在 BOSSbase 上是 0.8062,在自建校园语料上是 0.8939 —— 差值反映 的是语料(难度、负样本构成都不同),不是模型强弱。对外引用、论文对比一律用 下面主表的 BOSSbase 数字:
| 语料 | 模型 | AUC | 协议 | 说明 |
|---|---|---|---|---|
| BOSSbase 1.01(领域标准基准,头条口径) | LGB-143d | 0.8062 [0.7894, 0.8225] | 按源图 holdout | 对外一律引用这一行 |
| LGB-53d | 0.7172 [0.6943, 0.7406] | 同上 | 同一份划分,可与 CNN 对比 | |
| LGB-11d | 0.7128 [0.6913, 0.7360] | 同上 | 11 维基线 | |
| 校园照片(自建语料,附表) | 143d 部署模型 | 0.8939;8-split 均值 0.8980 | 按源图 holdout | 含 414 张真实 JPEG 干净图,与上表不可并列 |
| 53d 部署模型 | 0.8391;8-split 均值 0.8461 | 同上 | 同上 |
⚠ 2026-09-14 审计更正:校园语料此前的数字(0.8946 / 0.9100,8-split 0.9085 / 0.9227)含源图泄漏 —— 414 个
clean_jpeg行被赋予了独立 photo_id,使"按源图划分"失效;同一批 GPU 产出的 SRM 特征尺度也与推理端 不一致。两处均已修复并重跑,详见 CHANGELOG 1.6.2。 修复后 143d 优于 53d(此前结论相反:SRM 90 维在错误尺度下才是"噪声")。
完整权威表(逐行标注语料 / 协议 / 是否可溯源)见 docs/RESULTS.md,
由 experiments/build_results_table.py 生成;README 内任何口径冲突都以它为准。
| 版本 | 文件 | 校园语料 AUC (8-split 均值) | 弱档 nsF5 p3 d=0.25 | 推荐场景 |
|---|---|---|---|---|
| 143d 默认 | stego_classifier.joblib |
0.8980 | 50.0% | 通用部署 / 真实图 |
| 53d 可解释 | stego_classifier_v2_jpeg_lgb_51d.joblib |
0.8461 | 41.4% | 论文 / 答辩 / 教学(逐维可解释) |
本表是校园照片语料(自建,含真实 JPEG 干净图)上的部署指标;与 BOSSbase 的数字分属不同语料,不能并列。
OOD(真实干净照片)误报率(2026-09-14 重建,1514 张 = 校园 414 + DIV2K 100 + ALASKA#2 1000,可追到
experiments/data/ood_summary.csv): 143d 9.58% [8.20, 11.16]、53d 28.86% [26.64, 31.20]。 旧的"1/8 / 3/8"只有 8 张样本、且生产者已丢失,已作废。 详见下文的 双版本部署策略 与docs/RESULTS.md第 5 节。
更正记录:曾经出现过的错误
这一节列的是本项目真实犯过、并且已经修复的错误。写在这里的动机很直接:隐写分析
项目如果连自己的评测数字都不可信,它的教学价值就是负的。逐条细节见
CHANGELOG.md 的 1.6.1–1.6.4,权威数字见
docs/RESULTS.md。
| # | 曾经的错误 | 影响(实测) | 现状 |
|---|---|---|---|
| 1 | 源图泄漏:414 个 clean_jpeg 行被赋了独立 photo_id,而其特征与对应 clean 行逐位相同(是副本) |
"按源图划分"名存实亡 —— 同一张源图可以跨训练/验证两侧。单独修分组后 143d held-out AUC 0.8946 → 0.7555、弱档检出 85.3% → 44.2% | 已修:experiments/add_jpeg_clean.py 让 photo_id 继承源图;train_deploy_models.py 开训前强制校验分组不变量 |
| 2 | SRM 特征尺度不一致:GPU 端在高通滤波前把像素 /255,残差缩小 255 倍、clip 形同虚设 |
语料用 GPU 特征、单图推理用 CPU 特征,两者相差约 30 倍,143d 模型一直吃分布外输入。修好后 BOSSbase 143d 0.7529 → 0.8062 | 已修:CPU/GPU 143 维特征逐项一致(max|Δ|≈3e-5),并有回归测试与 CI job |
| 3 | C++ 与 Python 特征不一致,且被自检掩盖:卡方自由度用了 n 而非 n−1(p 值差 26%)、20 段中位数取"上中位";自检把它误诊成"MinGW lgamma 精度偏移",容差放宽到 0.2 |
Windows(带 DLL)与 Linux(纯 Python)对同一张图给出不同的 chi2_pvalue / median_prefix_p |
已修:11 维逐项一致(max|d|≈6e-14),自检容差收回 1e-9 |
| 4 | 结论建立在错误数据上:曾写"53d 精简版 AUC 更高""SRM 90 维是噪声特征、去掉反而更好" | 该结论完全来自第 1、2 条缺陷 | 已推翻并重跑:143d 更准(8-split 0.8980 vs 0.8461);SRM 占 LightGBM gain 52.6%,去掉它 OOF AUC 掉 0.05 |
| 5 | OOD 数字只有 8 张样本("1/8、3/8"),且产生它的脚本与数据一起丢失 | 无法复核,也没有统计意义 | 已重建 experiments/ood_eval.py:1514 张真实干净照片,143d 9.58%、53d 28.86%(Wilson 95% CI) |
| 6 | 同一指标名跨语料/跨协议混用:"8-split" 指过两种协议;校园语料数字与 BOSSbase 数字被并列比较 | 读者会看到互相矛盾的数值 | 已修:docs/RESULTS.md 把语料与协议做成每行必填字段,并规定 README 头条只引用 BOSSbase |
| 7 | 多处数字只打印不落盘(GPU 管线 0.790/0.644/0.712、train_model 家族、gain importance) | 不可溯源,无法复核 | 已修:四个生产者补齐并重跑,不可溯源行 9 → 0(0.7903 / 0.6438 / 0.8889 等逐位复现) |
| 8 | 部署模型不可复现:仓库里没有能产出两个随包 .joblib 的脚本 |
clone 之后无法重建模型,"可用但不可复现" | 已补 experiments/train_deploy_models.py,并在全部 5796 个样本上验证其产出与随包模型预测一致 |
| 9 | 教学手册带着已被推翻的结论(DOCX、网页版、PDF 三处都有) | 教学材料带错结论比没有结论更糟 | 已修:teaching/handbook_facts.py 统一口径并纳入 CI;PDF 按新口径重新导出(中文 67 页 / 英文 74 页) |
| 10 | 教学 notebook 从未被执行过 | 03 号让学员嵌入 5000 字符,而封面图容量只有 3494 字节 —— 这个 cell 一直在抛 ValueError |
已修并进 CI:10/10 逐本执行通过,另有"入库 notebook 与生成器一致"的漂移检查 |
| 11 | LICENSE 缺 APPENDIX 段,结尾被换成自定义版权块 | GitHub 把 Apache-2.0 识别成 NOASSERTION,与徽章不符 |
已恢复标准 Apache-2.0 全文 |
| 12 | 若干使用即踩的缺陷:python src/run_e2e.py 在中文 Windows 控制台崩溃(✓ 无法用 GBK 编码);make_dataset 打印的样本数恒比真实值多 1;train_model 遇到空环境变量直接崩溃;README 引用过从未存在的 src/_add_jpeg_clean.py;wheel 安装示例版本过期 |
使用者直接踩到 | 均已修复,并新增控制台编码护栏测试 |
| 13 | wheel 里没有模型文件:两个部署模型只在仓库里,pyproject 没有把它们打进包,而 ml_predict 也只按仓库布局找路径 |
pip install nsf5stego 之后依赖里装着 lightgbm、README 写着有 ML 判定,但 MLPredictor.available 恒为 False(真实验证:安装布局下 FileNotFoundError) |
已修(2026-09-15):models/ 以 nsf5_models 包打进 wheel,ml_predict 按"仓库布局 → 安装布局"查找;CI 的 build 作业在干净 venv 里断言模型可用并能打出概率 |
| 14 | 事实校验器把一处陈旧引用放过去了:图 9-2 的图注写着"论文图 / thesis figure",而论文稿 2026-09-14 已删除;FORBIDDEN 用的是 "(论文图" / "(thesis figure"(左括号紧贴),实际文本是 "(log–log 坐标,论文图)",子串匹配被绕过 —— 于是这句话同时留在 DOCX、网页版和入库 PDF 里 |
教学材料指向不存在的文件;而"进了 CI 就不会再犯"的假设也因此不成立 | 已修:匹配放宽到 "论文图" / "thesis figure"(另禁 "project thesis",注意不能用裸 "thesis" —— 会命中 "hypothesis"),三处共 6 处修正并重新导出 PDF(67 / 74 页);PDF 的导出步骤也补成了脚本 teaching/export_handbook_pdf_word.py(make handbook-pdf) |
| 15 | 手册把姊妹项目说成本项目的一部分:中英文 ch03 / ch11 / 附录 F 都写"项目 yccstego 扩展",而 yccstego 是独立仓库与 PyPI 包(pip install yccstego),本仓库里没有它的代码、也没有任何链接 |
读者按手册去找,什么也找不到 —— 与第 14 条同类:教学材料指向不存在的东西 | 已修(1.7.1):中英各 5 处改成"姊妹项目 yccstego"并给出仓库地址,README 增"姊妹项目"一行,PDF 重新导出(英文 75 页);handbook_facts.py 的 REQUIRED 加上该地址,三份材料缺它就红 |
为什么保留这些记录,而不是悄悄把数字改掉: 第 1 条和第 3 条恰好是"评测设计本身 出错"的两个典型样本 —— 前者说明"按源图分组"这种纪律会在 id 分配这种细节上悄悄失效, 后者说明一个被误诊的容差可以把两种实现的差异藏住很久。它们现在是教学材料的一部分 (见第 7、8 章与
docs/RESULTS.md第 1 节)。
提交署名的一次清理(2026-09-14)
用 AI 编程助手协作时,它会在提交信息里自动追加
Co-Authored-By: Claude Code <noreply@anthropic.com> 这类尾注。GitHub 会把它当成
共同作者显示在提交流里 —— 与"贡献者列表"不同(本项目贡献者 API 一直只有仓库
所有者),但同样显眼。
git bundle create .git/backup.bundle --all # 先全量备份
python scripts/strip_ai_trailers.py < msg > new # 或直接用下面的 msg-filter
git filter-branch -f --msg-filter \
'python scripts/strip_ai_trailers.py' -- <起点>^..HEAD
git push --force-with-lease origin main
以后不会再发生:.githooks/commit-msg 会在提交时当场剔除这类尾注
(启用一次:make hooks,即 git config core.hooksPath .githooks),CI 的
attribution job 则对整段历史兜底检查。
安装与运行
方式 0:Windows 安装版(推荐普通用户)
到 Releases
下载 nsf5stego-setup-<版本>.exe 双击安装(简体中文向导,每用户免管理员),
装完后像普通桌面软件一样使用,不需要 Python:
- 开始菜单(可选桌面快捷方式)→ nsF5 隐写工具,双击即开图形界面;
- 向导里勾选"加入用户 PATH"后,
nsf5stego embed / extract / analyze命令行 直接可用(重开终端生效),卸载时自动从 PATH 移除; - 产物(含密图 / 效率图)写在
%APPDATA%\nsf5stego\output,不污染安装目录; - 免安装选择:同一 Release 的
nsf5stego-portable-<版本>-win64.zip解压即用; - 首次运行若遇 SmartScreen 提示,点"仍要运行"(项目未做代码签名)。
方式 A:从源码运行(Windows / Linux / macOS 通用)
git clone https://github.com/Yukinoshita-lin/nsf5-steganography.git
cd nsf5-steganography
python3 -m venv .venv
source .venv/bin/activate # Linux / macOS
# Windows: .venv\Scripts\activate
python -m pip install -e . # 或只装 numpy pillow
nsf5stego --help # 命令行界面(embed / extract / analyze / gui)
python src/gui.py # 图形界面(需要 tkinter)
跨平台说明:无需任何预编译二进制——
fsfeatures、cppembed、featurize_v2与ml_predict在检测不到cpp/下的库时自动使用纯 Python 实现;想要加速就make cpp(需要 g++/clang++)。仅 GUI 需要系统自带 tkinter(Ubuntu/Debian:sudo apt install python3-tk)。 也可用仓库根目录的Makefile:make test、make cpp、make e2e、make notebooks。
方式 B:安装打包的模块(wheel)
每个版本会以源码包发布,可构建并安装:
# 构建 wheel + sdist(需已安装 build)
python -m build
# 安装 wheel(核心模块:ns5_core / steganalysis / gui 等;文件名带版本号)
pip install dist/nsf5stego-<版本>-py3-none-any.whl
注意:wheel 仅含纯 Python 核心;C++ 加速库需自行
make cpp编译,缺失时 自动回退纯 Python。两个部署模型(models/*.joblib)已打进 wheel (安装为nsf5_models/,含模型卡),ml_predict会按"仓库布局 → 安装布局" 的顺序查找,所以pip install之后 ML 判定直接可用:from ml_predict import MLPredictor p = MLPredictor() assert p.available, p.load_error # 缺 lightgbm/pickle 版本不符时这里会说明原因 print(p.predict(img)) # {probability, verdict, threshold}(2026-09-15 修正:此前 wheel 里没有模型文件,安装后
available恒为 False。)
命令行界面(1.8.0 起;1.9.0 增 JPEG 域与实验重跑)
安装后除 GUI 外还有一条与 GUI 参数一一对应的命令行(服务器 / 脚本 / 批量
场景不再需要自己拼 ns5_core 调用):
nsf5stego embed cover.png -m "秘密文本" -p 口令 -o stego.png
cat msg.txt | nsf5stego embed cover.png # 文本也可从 stdin 传入
nsf5stego extract stego.png -p 口令 # 域/方法/p/口令 须与嵌入一致
nsf5stego analyze stego.png --sensitivity 宽松 --json # 盲分析, --json 供脚本解析
nsf5stego analyze *.png # 批量: 逐图一行汇总
# JPEG 压缩域 (v1.9.0): 在量化 DCT 系数上嵌入, 输出标准 .jpg
nsf5stego embed cover.png --jpeg -m "秘密" --quality 85
nsf5stego extract cover_stego.jpg --jpeg -p 口令 # 嵌入用了 --jpeg, 解码也必须加
nsf5stego analyze cover_stego.jpg --jpeg # DCT 域 |c|=1 指纹分析
# 实验档案与一键重跑 (v1.9.0)
nsf5stego embed cover.png -m "秘密" --json > experiment_20261003.json
nsf5stego repro experiment_20261003.json -m "秘密" # 重跑并逐项校验, 通过/失败逐条打印
nsf5stego gui # 图形界面(与 python src/gui.py 相同)
- 退出码:成功
0;运行失败(容量超限 / 口令不匹配 / 图像无法读取)1; 参数错误2—— 脚本可直接判断成败。 - 解码失败不输出乱码:口令或 方法/p 不匹配时解出的会是无效文本,CLI 统一 以非零码退出并提示"请确认 方法/p/口令 与嵌入时一致",而不是把替换字符打印出来。
- analyze 批量语义:单图
--json输出对象,多图输出对象数组(每项带image键);批量里某张图读不出来会记为error条目继续跑完,整体以 非零码退出。通配符(如*.png)由 CLI 自己展开 —— Windows 的 shell 不 展开,这条示例在三大平台都能直接用。单图行为与 1.8.0 初版完全兼容。 - JPEG 域语义(1.9.0):嵌入/解码/分析三个子命令都认
--jpeg。载荷住在 JPEG 位流的量化系数里,所以含密图必须原样保存/传输(重编码即毁);--quality只在嵌入时有效。依赖yccstego(Python>=3.10 自动随装;缺失时报错并给安装提示)。 - 实验档案(1.9.0):
embed --json输出的档案只含参数/哈希/统计,不含消息 明文与口令(只有"是否用了口令");repro重跑时封面哈希、提取一致、改动数 逐项校验 —— 两域在 yccstego 版本一致时均逐字节复现(0.2.0 起湿纸求解种子由输入派生); 版本不同自动退回"提取一致",改动数降为参考值并如实标注。 - 默认输出
<原名>_stego.png(--jpeg时为.jpg);目标文件已存在时会在 stderr 明示覆盖。 - 源码运行的等价形式:
python src/cli.py <子命令> ...。 - GUI 产物目录:源码运行写
仓库/output/;pip install安装后 GUI/效率图 自动改写当前工作目录的output/(不会写进 site-packages 或解释器目录)。
运行测试
python src/test_core.py # 核心算法自测(嵌入/解码 + 汉明矩阵 + 湿纸 + 口令)
python src/test_steg.py # 盲隐写分析自测(区分 干净/含密 图)
python src/run_e2e.py # 端到端验证(嵌入→保存→解码→分析→绘图)
python src/test_gui.py # GUI 冒烟测试(构建窗口/载入/预览)
# 全套(模型卡契约 / OOD 与实验链冒烟 / README 目录 / 权威表同步;条数见 CI 日志)
python -m pytest -q
make coverage # 同上 + 覆盖率报告(门槛 65%,当前约 69%)
GUI 使用流程
- 点击 载入原始图 / 含密图 选择 8bit 图像(或点 演示图 一键生成测试封面)。
- 在文本框输入待嵌入的 文本(UTF-8,支持中英文)。
- 在 参数 区选择 嵌入域(v1.9.0:
像素域改像素 LSB,JPEG 域改量化 DCT 系数并输出 .jpg)、 算法(仅像素域:nsF5或matrix)、参数 p(块比特数,越大效率越高)、可选 口令(JPEG 域还有质量 1–100)。 - 点击 嵌入并保存(Ctrl+E)→ 生成
output/stego_*.png(JPEG 域为.jpg),右侧预览含密图。 - 点击 解码提取(Ctrl+D)→ 从含密图还原字符串(须与嵌入使用相同 域/算法/p/口令)。
- 点击 分析(Ctrl+A)→ 「分析结果」页签显示 SHA256、卡方统计、RS 缺口、估计嵌入率、隐写概率与 ML 判定(JPEG 域改显 |c|=1 指纹与 DCT 域判定);过程细节在「运行日志」页签。
- 辅助工具:生成效率图(理论 vs 实测效率对比)、编码演示(伴随式校验动画)、载荷扫描(检测能力随载荷变化曲线)。
- 自证与可复现(v1.9.0):往返自检对当前图+当前参数做 嵌入→提取→比对 的内存闭环,
正确性当场可见;导出实验记录把最近一次嵌入存为 JSON 档案(不含消息明文),
命令行
nsf5stego repro <档案> -m 原文一键重跑校验。
解码与嵌入参数(域/方法/p/口令)必须一致;口令或图像内容不匹配将无法正确解码。 JPEG 域的含密图请原样保存传输——用其它工具重新另存一次(重编码)会毁掉载荷。
目录结构
nsf5-steganography/
├── README.md / CHANGELOG.md / LICENSE / NOTICE
├── Makefile # install/test/e2e/notebooks/docker/web 快捷命令
├── docker-compose.yml
├── cpp/ # 可选 C++ 加速库源码(make cpp 编译,缺失时纯 Python 回退)
├── data/ # 数据集 CSV 与 campus_jpg/BOSSbase 目录
├── docker/Dockerfile # Linux JupyterLab 教学镜像
├── gpu/ # PyTorch 批量特征 / GPU 训练
├── img/ # 示例封面图
├── models/ # 训练出的分类器 joblib
├── notebooks/ # 10 个按章 Colab/Jupyter Notebook
├── scripts/ # 数据集下载、网页资源生成等脚本
├── src/
│ ├── ns5_core.py # nsF5 核心:汉明码、湿纸、哈希键控、嵌入/解码
│ ├── jpegstego.py # JPEG 压缩域桥接 (v1.9.0): 统一包装 yccstego
│ ├── experiment.py # 实验档案 schema 与 repro 重跑校验 (v1.9.0)
│ ├── cppembed.py # C++ 嵌入封装(含纯 Python 回退)
│ ├── py_features.py # 纯 Python 11 维特征(跨平台)
│ ├── featurize_v2.py # v2 143 维特征
│ ├── srm_filter.py # SRM 高通滤波(numpy/torch)
│ ├── fsfeatures.py # C++ 特征绑定(DLL 缺失回退)
│ ├── steganalysis.py # 卡方 + RS 盲隐写分析
│ ├── ml_predict.py # 143d/53d 双版本模型推理
│ ├── make_dataset.py / train_model.py
│ ├── gui.py / efficiency.py / image_io.py
│ └── test_*.py # 回归测试
├── teaching/ # 教学文档、Notebook 生成器、Jupyter Book 站点
├── webapp/ # 交互式双语教学网站(Pages 首页)
└── output/ # 运行产物(gitignore)
技术细节
伴随式矩阵编码(nsF5)
二元汉明码 [n,k,d],n = 2^p - 1,校验矩阵 H 的列向量取 GF(2)^p 全部非零向量。
载体系数 LSB 奇偶向量 x 的伴随式 s = H·x (mod 2)。
- 嵌入
p比特消息m:若s == m不改动;否则d = s ⊕ m, 找到唯一列j(H_j == d)翻转该系数 → 每块至多改 1 个系数。 - 需要改动的概率
(2^p-1)/2^p,嵌入效率α(p) = p·2^p / (2^p-1),随 p 增大而提高。
F5 → nsF5
- F5:直流/减幅归零时"收缩",该块整块重嵌、载荷下降。
- nsF5:用湿纸编码预标记"减幅会归零 = 湿"的位置,湿位不动, 在干位解 GF(2) 线性方程完成嵌入 → 无收缩,效率与安全性更高。
图像哈希键控
载入原图计算 SHA-256(cover_hash):
- 头部区:用仅口令派生的种子预埋
cover_hash(认证头); - 正文区:用
cover_hash + 口令派生的种子键控置乱路径。
解码端先用口令种子读回头部,重算正文种子解码 → 隐藏路径由图像内容唯一决定, 改动任意像素会破坏解码结构,可经头部校验感知篡改。
盲隐写分析
- 卡方检验(Westfeld):相邻灰度对
(2i,2i+1)频率在嵌密后趋近均衡, p 值高表示该区已随机化/嵌入。 - RS 分析(Fridrich-Goljan-Du):统计正/负掩码的常规-奇异缺口
Gr、Gn;干净图 LSB 平面有结构(Gn 明显为正),隐写使其随机化而下降。 - 综合多个统计量给出 0–1 隐写倾向概率 与判读(不太可能 / 可能 / 高度可能)。
盲隐写分析本质为启发式:没有原始封面时无法给出精确绝对概率, 此处概率供评估与教学参考。
有监督 ML 隐写分析(C++ 特征提取 + 校园照片训练)
将特征提取从 Python 移植到 C++(cpp/fsfeatures.cpp),
可显著降低逐图统计开销;嵌入热路径同样提供 C++ 版(cpp/nsf5embed.cpp,
cppembed.py 封装)。
并用真实校园照片做有监督训练,得到一个可部署的分类器。
关于"两条路径是否一致":特征提取逐位一致(
fsfeatures与py_features在 RS/卡方/熵上误差 <1e-6,卡方 p 值因 MinGW 的lgamma半整数精度有约 0.02 的容忍度)。嵌入路径则不是逐像素一致:matrix逐像素相同;nsF5在 p≥3 时会有几十个像素不同(65536 中约 8–32 个),因为湿纸编码在多个等价解中 挑选哪一个 —— C++ 按固定下标顺序扫,Python 按种子派生顺序扫。两者都是合法解, 各自回环正确,且能互相解码。契约由src/test_pipeline.py::test_cpp_python_embed_contract固定。
训练管线
# 1) 用 data/campus_jpg 下照片批量生成 干净/含密 特征数据集
# (每张降采样 512x512 灰度, 1 干净 + 6 含密变体, C++ 提取 11 维特征 + C++ 嵌入)
python src/make_dataset.py
# 2) 训练/评估 (预留 held-out 测试集 + 5 折 GroupKFold 交叉验证选模)
python src/train_model.py
- 特征 (全由 C++ 计算):
RS_Gn, RS_Gr, Rm, Sm, Rn, Sn、chi2_pvalue, chi2_stat、diff_entropy、lsb_diff_entropy、median_prefix_p。 - 6 档含密变体:覆盖弱→强,
nsF5 p3(弱密度)至matrix p2(强)。 414 张照片 → 2898 样本(414 干净 + 2484 含密)。 - 5 折 GroupKFold:按照片分组交叉验证选模(杜绝同源泄漏), 再在留出测试集上报综合指标与每密度层检出率。
在 GUI 中启用
GUI 新增 判定灵敏度 下拉框(严格 / 均衡 / 宽松),作用于 3 分析:
- 严格 (低误报):提高含密判定阈值 → 干净图更少被误判;
- 均衡:默认。
- 宽松 (高检出):下调阈值 → 更易检出弱密度嵌入(代价是误报略升)。
它与下方 ML 分类含密概率 联动(同一张净图在三种灵敏度下阈值 0.95→0.77→0.57,ML 判决会由"干净"切换到"含密"),启发式概率也会围绕 0.5 上下牵引。GUI 3 分析会在原有启发式结果下方追加一行 ML 分类含密概率, 输入图像会自动按训练一致的方式(转灰度→512 缩放→特征提取)送入模型。
模型从哪来(重要):两个部署模型 models/stego_classifier.joblib(143d 默认)
与 models/stego_classifier_v2_jpeg_lgb_51d.joblib(53d 可解释)已随仓库分发,
clone 之后 ML 判定即可用(需要 lightgbm,已列为依赖)。
模型可复现:experiments/train_deploy_models.py 就是这两个文件的生产者,
口径已冻结:
- 语料
data/dataset_campus_v2_jpeg.csv(414 张校园照片 × 14 = 5796 样本) - 按源图划分
GroupShuffleSplit(test_size=0.25, random_state=seed) - LightGBM
learning_rate=0.03, num_leaves=31, n_estimators=800, min_child_samples=10, subsample=0.9, colsample_bytree=0.8
复现证据(2026-09-14 审计后重跑):143d 在 seed=0..7 上的 AUC 为
0.8939 / 0.8998 / 0.8987 / 0.8972 / 0.8892 / 0.9009 / 0.9145 / 0.8901
(均值 0.8980),53d 为 0.8391 / 0.8326 / 0.8531 / 0.8589 / 0.8408 / 0.8500 /
0.8615 / 0.8330(均值 0.8461);随仓库分发的两个 .joblib 就是该脚本的产物。
生产者会在训练前检验分组不变量(每个 photo_id 的 variant 集合必须一致),
语料一旦出现"孤儿 id 块"就直接拒绝运行 —— 这正是 1.6.2 修掉的那类源图泄漏。
语料本身的生成链见 python experiments/add_jpeg_clean.py(JPEG 干净行的 photo_id
继承源图,且是真实 JPEG 往返而非 clean 行的副本)。
可复现性的边界(如实说明):校园语料的 414 张照片是作者本人的
data/campus_jpg/(不入库,也无法分发)。因此两个部署模型的训练语料 不能从零重建——脚本链(make_dataset.py→add_jpeg_clean.py→train_deploy_models.py)是完整的,但需要自备同规模的 JPG 照片目录。 想完全从零复现,请走 BOSSbase 路线(scripts/download_datasets.py+experiments/featurize_bossbase_npz.py+experiments/sota_compare.py), 那条链只用公开基准,docs/RESULTS.md的主表就是它。
python experiments/train_deploy_models.py # 重训并覆盖 models/*.joblib(约 3 分钟)
python experiments/train_deploy_models.py --no-splits # 只跑 seed 0(约 10 秒)
python experiments/train_deploy_models.py --no-save # 只评估,不落盘
# 单独用 ML 判定单张图
python -c "import sys; sys.path.insert(0,'src'); from ml_predict import get_predictor; \
import numpy as np,os; from PIL import Image; from ns5_core import embed_string; \
a=np.asarray(Image.open(r'img/cover.png').convert('L').resize((512,512)).convert('L')); \
print(get_predictor().predict(a))"
局限:真实 JPEG 照片的 LSB 位平面天然近乎随机,弱密度 nsF5 嵌入的 统计足印很弱;ML 概率应与启发式判读交叉印证,不宜单独作为铁证。
SRM 高通滤波预处理层(检测性能提升)
src/srm_filter.py 提供 SRM (Spatial Rich Model) 高通滤波预处理层,在提取
11 维统计特征之前对图像做高通滤波,突出嵌入噪声、抑制图像内容,从而增强
弱密度隐写的统计足印。
- 30 个标准 SRM 核(Fridrich 体系,与 Ye-Net 所用一致):一阶差分 8 + 二阶 4 + 三阶 8 + 边缘 3×3 4 + 边缘 5×5 4 + 方形 3×3/5×5 各 1;按标准因子归一化并补齐到 5×5。
- 合成增强图:逐像素取 30 个残差的最大绝对响应,clip 到 ±T(默认 4)后平移缩放到 uint8 [0,255],输出单通道增强图,直接喂给现有特征器,其余管线不变。
- CPU (numpy) / GPU (torch conv) 双实现,接口一致
(
srm_residuals_np/srm_residuals_torch/preprocess_batch_torch)。
接入方式(两条管线均已内置开关):
# CPU数据集: --preprocess srm (默认 none=原图基线)
py src/make_dataset.py data/campus_jpg --out campus_srm --preprocess srm -j 16
# GPU特征: extract_features_gpu(x, use_srm=True) (默认开启)
# 自检: py gpu/featurize_gpu.py (含 SRM 路径冒烟)
实测效果(同一 414 张校园照片,LR,同协议 5 折 GroupKFold + 留出测试):
| 指标 | 原图基线 (dataset.csv) | SRM 增强 (dataset_campus_srm.csv) |
|---|---|---|
| 5 折 CV-AUC | 0.7594 | 0.7922 |
| held-out 测试 AUC | 0.7811 | 0.8085 |
| 低误报点含密检出率 | 41.2% | 50.3% |
弱密度 nsF5 p3 d0.25 检出 |
51.9% | 62.5% |
nsF5 p2 d0.35 检出 |
76.0% | 90.4% |
| 干净误报率(低误报阈值) | 9.6% | 9.6% |
SRM 在保持低误报不变的同时,将 CV-AUC 提升约 3.3 个百分点、测试 AUC 提升约 2.7 个百分点,弱密度档检测增益尤其显著,且 SRM 增强图约 0.35s/张、 16 核并行下生成全量数据集不受影响。
重要(跨源合并时结论相反):SRM 的收益只出现在同源内。接入 BOSSbase 全量做跨源合并训练时,SRM 反而显著拉低性能,CPU 与 GPU 两管线 相互印证:
跨源合并 CPU (校园+全量BOSSbase, 测试AUC) GPU (校园+BOSSbase2000源, 验证AUC) 非 SRM(基线) 0.741 0.651 SRM 0.704(−3.7pp) 0.572(−7.9pp) 机理:SRM 高通滤波在抑制图像内容的同时,也把不同相机/JPEG 压缩源之间的可区分 信号一并压平——同源时被压掉的是嵌入噪声(收益),跨异构源时被压掉的是跨源可分 性(损失)。因此默认模型采用未 SRM 的合并全量(
stego_classifier.joblib, 测试 AUC≈0.741),SRM 单源校园模型另存为stego_classifier_campus_srm.joblib(AUC≈0.8085,仅供校园同源场景)。
v2 扩展特征(143 维)与多档变体 A/B(2026-09)
在原 11 维之上扩展为 143 维 v2 特征集:30 个 SRM 残差的均值 / 绝对均值 / 标准差
(90 维)+ 20 段前缀卡方 p(20 维)+ texture_noise + est_rate(2 维)+ 20 段 LSB 前缀
卡方 p(20 维)。同时把训练变体从 6 档扩展到 12 档(追加 nsF5 p3 d=0.40、
matrix p3 d=0.40/0.60、lsb d=0.30/0.50/0.70)。
# 重新生成 v2 数据集 (单进程 GPU, 414 张 ~7 min)
py src/make_dataset.py --out campus_v2 --feature-set v2 --variants all
py src/make_dataset.py --out campus_v2_min --feature-set v2 --variants minimal # 6 档对照
# 训练 v2 143d + 4 模型 stacking (5 折 GroupKFold, 留出 25% 测)
DS_FILES="dataset_campus_v2.csv" python src/train_model.py
严格 A/B 对比(同一测试集照片 ID 分组,5 折 GroupKFold OOF):
| 数据集 | 特征 | OOF-AUC | 弱档 nsF5 p3 d=0.25 检出 |
模型 |
|---|---|---|---|---|
dataset.csv (6 档) |
v1 11 维 | 0.7678 | 49% | XGB |
dataset_campus_v2.csv (12 档) |
v1 11 维 | 0.7868 | — | LR |
dataset_campus_v2.csv (12 档) |
v2 143 维 | 0.8143 | 25% | LR |
dataset_campus_v2.csv (12 档) |
v2 143 维 (held-out) | 0.8278 | 25% | LR |
- v2 数据集 12 档 vs 6 档:+0.019(同 11 维)→ 真实信号(多档位学到档位差)。
- v2 143 维 vs v1 11 维:+0.027 → SRM 残差 / 20 段前缀 p 确实贡献判别力。
- 累计 +0.05 AUC(0.78 → 0.83)。
部署警告:v2 模型在 5 折 OOF 与 held-out 上 AUC 显著提升,但实际部署到真实 JPEG 干净图时倾向过激(SRM 残差对 JPEG 高频噪声过于敏感,干净 JPEG 几乎全被 判 1.0)。默认
stego_classifier.joblib仍为 v1 11 维 XGB 模型(held-out AUC=0.7435,干净 JPEG 误判率低);v2 模型另存为stego_classifier_v2_campus_stack.joblib(AUC=0.8278,仅供训练分布内的 PGM/BMP 灰度图使用)。
分布外(OOD)分析与缓解尝试
为修复 v2 在真实 JPEG 干净图上的过激(logit 263 vs 训练集 clean mean 1.68), 探索了两种部署抗偏移方案(均不替换默认模型,仅做参考):
| 方案 | 思路 | 真实 JPEG 干净 prob | 局限 |
|---|---|---|---|
| 原始 v2 | 无防护 | 1.000 | — |
| ① clip-to-±5σ | 把每维特征裁到训练集 μ±5σ | 0.996 | JPEG 干净 logit=5.6 已超训练集 clean p99=3.73 |
| ② OOD-cap | logit 超 clean p99 时封顶概率到 clean p99 prob (0.977) | 0.977 | 仍 ≥ thr 0.912;stego p5=1.30 已与 clean p99=3.73 重叠,单阈值无解 |
根本原因:训练集 clean 与 stego 的 logits 严重重叠(clean p99=3.73 vs stego p5=1.30)。v2 模型在训练分布内已"过激",JPEG 干净图即使 clip 也回不到训练分布内 位置。
✅ 根本修复:v2 训练集追加真实 JPEG 干净样本(2026-09)
把 data/campus_jpg/ 414 张真实 JPEG 干净图(campus PGM-derived 来源之外)作为
额外 clean 样本加入 v2 训练集,photo_id 独立区间 max_id+1000 ~ max_id+1413,
与原训练集完全 disjoint(防 group 泄漏)。重新训练 v2 4 模型 OOF stacking:
# 自动生成 v2 + JPEG 数据集 (campus_v2.csv + 414 JPEG clean -> campus_v2_jpeg.csv)
python experiments/add_jpeg_clean.py
# 训练 (与 v2 同样的 5 折 GroupKFold + 4 模型 stacking)
DS_FILES="dataset_campus_v2_jpeg.csv" python src/train_model.py
v2 特征可解释性(2026-09-14 审计后重算):
- 单特征 AUC(校园 v2_jpeg 语料,取 max(AUC, 1-AUC) 的中位数): BASE 11 → 0.610, PREFIX 20 → 0.646, LSB PREFIX 20 → 0.607, SRM absmean → 0.512, SRM std → 0.550
- 特征消融(5 折 GroupKFold OOF, 8 seed 均值): B11 0.8708 → 53d 子集 0.8513 → 完整 143d 0.9010,即 SRM 90 维贡献 +0.050 AUC; 按源图 GroupKFold(8) 上 143d 对 53d 是 8/8 全胜
- 结论(与此前相反): SRM 90 维在尺度正确时不是噪声。2026-09-06 那版结论
("SRM 近随机、去掉反而更好",gain 占比 SRM 26.6% / LSB PREFIX 42.2% 等)是在
GPU 端把像素先
/255的错误尺度上算出来的,已作废。 现已按当前口径重算(experiments/gain_importance.py):SRM 90 维占 gain 的 52.6%,BASE 11 占 20.7%、LSB PREFIX 20 占 20.6%、PREFIX 20 占 6.2%; Top-5 特征为Rm(7.6%) /lsb_prefix_p4(7.0%) /srm_absmean_c29(6.1%) /RS_Gr(5.5%) /srm_std_c29(4.6%)。旧表(SRM 26.6%)已作废。
双版本部署策略(2026-09-14 审计后更新)
经过可解释性对照实验,项目保留 143d 默认版 与 53d 可解释版 两套模型,各自适用场景不同:
语料提醒:本节(以及下方"模型对比""最终决策")的 AUC 全部来自校园照片 语料(自建,含 414 张真实 JPEG 干净图)。同一族模型在 BOSSbase 1.01 上是 0.8062(143d)/ 0.7172(53d),两者不可并列。权威表见
docs/RESULTS.md,头条口径见上文"指标口径"一节。
| 维度 | 143d 默认版(stego_classifier.joblib) |
53d 可解释版(stego_classifier_v2_jpeg_lgb_51d.joblib) |
|---|---|---|
| 特征构成 | BASE 11 + SRM 90 + PREFIX 20 + LSB PREFIX 20 + TEX/EST 2 | BASE 11 + PREFIX 20 + LSB PREFIX 20 + TEX/EST 2(去 SRM) |
| 8-split 平均 AUC(校园语料) | 0.8980 | 0.8461 |
| Held-out AUC(校园语料, seed=0) | 0.8939 | 0.8391 |
| 弱档检出 nsF5 p3 d=0.25 | 50.0% | 41.4% |
| 特征消融(5 折 OOF, 8 seed 均值) | 0.9010 | 0.8513 |
| 模型文件大小 | 2.8 MB | 2.8 MB |
| 可解释性 | 一般(143 维,LIME/SHAP 可对单图解释) | 强(51 维有明确统计定义,可直接列 Top 贡献) |
| 推荐场景 | 通用部署 / 异构数据 / 真实图像 | 论文 / 答辩 / 教学 / 单图分析 |
模型卡(2026-09-15):两个模型各有一份入库的 JSON 模型卡 —— 语料与协议、held-out / 8-split 指标、逐档检出率、OOD 误报率、特征列表与顺序、 超参、训练环境、适用边界与已知局限,以及该
.joblib的sha256。 入口:models/README.md(说明)与models/*.card.json(机器可读)。 校验:python experiments/model_card.py --check—— 它检查卡片与二进制是否脱钩, CI 里随pytest一起跑;重训后由experiments/train_deploy_models.py自动刷新。
核心结论(审计后):
- 143d 在 AUC 与弱档检出上更好:去掉 SRM 90 维后消融 OOF AUC 从 0.9010 掉到 0.8513
- 53d 的价值在可解释性:53 维里 51 维有明确统计含义,可逐维列出贡献;代价是 AUC 低约 0.05
- 此前"53d 全面胜出、143d 靠 OOD 稳"的结论建立在两个缺陷之上(SRM 尺度错误 + 语料源图泄漏),见 1.6.2
- 工程上保留双版本,默认加载 143d(更准),教学/答辩场景切 53d(可解释)
53d 精简版定位(审计后更正)
- 按源图 GroupKFold(8) 的 OOF:143d 0.8966 vs 53d 0.8470 —— 143d 8/8 全胜 (此前记录的是"53d 7/8 胜", 那是错误尺度 + 泄漏语料的产物)
- 可解释优势:53 维中 51 维有明确统计含义
Rm/Sm/Rn/Sn/RS_Gr/RS_Gn:RS 分析 6 个规则翻转率chi2_pvalue/chi2_stat:LSB 卡方 p 值与统计量diff_entropy/lsb_diff_entropy:全局/LSB 位平面熵差median_prefix_p:20 段前缀卡方 p 中位数prefix_p1~p20:全图像 20 段卡方 plsb_prefix_p1~p20:LSB 通道 20 段卡方 ptexture_noise:图像纹理方差归一化est_rate:从 χ²p + 前缀 p 反推估计嵌入率
切换 53d 模式(代码示例)
import joblib
from ml_predict import MLPredictor
# 默认 143d
pred_143 = MLPredictor() # model_path=stego_classifier.joblib
# 切换 53d
pred_53 = MLPredictor(model_path='models/stego_classifier_v2_jpeg_lgb_51d.joblib',
clip_outliers=False) # 53d 不需要 clip(单特征 AUC 高,训练分布更稳)
# 同一张图
result_143 = pred_143.predict(img) # AUC 更高 + 弱档检出更强
result_53 = pred_53.predict(img) # AUC 略低, 但可对每维特征解释
GUI 集成(规划)
src/gui.py 在 ML 模型加载处增加单选框:[●] 143d 默认(稳健) / [ ] 53d 可解释(AUC+)。
切换后:
- 143d 模式:与现状完全一致,部署推荐
- 53d 模式:增加"贡献特征"面板,显示 Top 5 特征 + 方向 + 强度,供研究/教学场景
下表是校园照片语料上的历史记录。2026-09-14 已把其中"只打印不落盘"的配置
全部重跑并落盘(experiments/data/train_model_metrics.csv),所以下面的数字现在
都有产物可查;重跑值与原值并列,差异来自修正后的特征口径与语料。
与 BOSSbase 的数字不可并列。
| 模型 | 训练集 | Held-out AUC | 真实 JPEG 干净 prob | nsF5 p3 d=0.25 检出 |
|---|---|---|---|---|
| v1 XGB (旧默认) | dataset.csv (11维, 6档) | 0.7435 → 重跑 0.7371 | 0.30 (正确) | 49% |
| v2 LR (未修复) | campus_v2 (143维, 12档) | 0.8278 | 1.00 (误判) | 25% |
| v2 XGB (旧默认) | campus_v2_jpeg (143维, 12档 + 414 JPEG clean) | 0.8723 | 0.14 (正确) | 15.6% |
| v2 XGB tuned | campus_v2_jpeg + 网格调优 (depth=5, n_est=500) | 0.8889 → 重跑 0.8889 | 0.14 (正确) | 67.9% |
| v2 XGB + WEAK_WEIGHT=3 | campus_v2_jpeg + 弱档加权×3 | 0.8534 → 重跑 XGB 0.8832 / LR 0.9055 | 0.14 (正确) | 20.2% |
| v2 XGB + WEAK_WEIGHT=5 | campus_v2_jpeg + 弱档加权×5 | 0.8377 | — | 25.7% |
| v2 STACK (WEAK_WEIGHT=3) | campus_v2_jpeg + 4 模型 LR meta | 0.8321 → 重跑 0.8672 | 0.10 (正确) | 32.1% |
| v2 LGB tuned (新默认) | campus_v2_jpeg + LGB 网格调优 (nl=31, ne=800, lr=0.03) | 0.8939 | 见下 | 50.0% |
重跑还暴露一件事:在当前(修正后的)语料上,LR(标准化 + 校准)常常是最强的 单模型(0.9055),高于调优后的 XGB(0.8889)与 STACK(0.8672)。这与旧口径 "XGB/LGB 更强"的印象相反,值得在下一轮实验里单独查清。
- 新默认
stego_classifier.joblib= v2 LGB tuned (num_leaves=31, n_estimators=800, learning_rate=0.03, min_child_samples=10)。 - 现口径(2026-09-14 重跑,可追到
experiments/data/deploy_model_metrics.csv): held-out AUC 0.8939、8-split 平均 0.8980 ± 0.0074;弱档 nsF5 p3 d=0.25 检出 50.0%、 nsF5 p2 d=0.35 71.2%、matrix p3 d=0.40 74.0%;验证集干净误报 27.9%。 - 表内"测试集 AUC 0.8946 / 弱档 85.3%"等为审计前记录,含源图泄漏与 SRM 尺度错误,不再引用。
- XGB tuned 已备份为
stego_classifier_v2_jpeg_xgb_tuned.bak.joblib。 - STACK 版 (
_weak3_stack.joblib) 仍保留供 OOD 严重场景切换(人工噪声 prob 0.24 vs LGB 0.99)。 - v1 11 维 XGB 保留为参考。
最终决策:项目保留双版本模型供不同场景使用:
版本 文件 适用 143d 默认版(更准) stego_classifier.joblib通用部署 / 异构数据 / 真实图像(默认加载) 53d 可解释版 stego_classifier_v2_jpeg_lgb_51d.joblib论文 / 答辩 / 教学 / 单图分析 143d 默认:LGB tuned(
num_leaves=31, n_estimators=800, learning_rate=0.03, min_child_samples=10), held-out AUC 0.8939(校园语料)、8-split 平均 0.8980、弱档检出 50.0%。 53d 可解释:同样超参,53 维特征(去 SRM),held-out AUC 0.8391、8-split 平均 0.8461、 弱档检出 41.4% —— 用约 0.05 AUC 换"每一维都能解释"。 两者在 BOSSbase 上的同族数字是 0.8062 / 0.7172(头条口径,见上文"指标口径")。⚠ 上述 AUC 均为校园语料(自建,更容易)。同一族模型在 BOSSbase 1.01 上是 0.7529 / 0.7172 —— 对外引用、论文对比请用后者,见
docs/RESULTS.md。 两个模型现已可由experiments/train_deploy_models.py现场复现(逐位一致)。 STACK 与 XGB tuned 仍保留供场景切换;v1 11 维 XGB 保留为参考。
GPU 版 (v1.2):PyTorch 批量向量化的统计特征分析
gpu/ 子目录提供一套 GPU(CUDA) 加速的隐写检测管线,复刻已验证的
11 维统计特征(RS、卡方、差分熵、LSB 熵、前缀中位 p,与 src/fsfeatures.py
参考实现 bit 级一致),把原来逐图 Python 循环(RS 逐组、20 段前缀卡方)
改写为 PyTorch 张量化算子,在 CUDA 上一批并行算完。
为什么是"特征法"而不是裸像素 CNN? 实测表明:在 414 张校园照片上, 从头训练的整图深度卷积网络(多架构/输入/正则组合)均无法跨照片泛化 (验证 AUC≤0.50)——有效独立样本只有照片数,弱 LSB 信号需数千张源图才能学稳。 统计特征法在相同数据上验证 AUC≈0.79,且特征提取可被 GPU 并行化, 因此 GPU 版选择加速这条真正管用的路径,而非裸 CNN。
管线
# 1) 生成数据集 (每张照片 1 干净 + 4 档含密变体, 完整 512x512, 不裁剪以保留统计)
python gpu/make_imageset.py [照片目录] [张数]
# 2) GPU 批量提取特征 + 按照片分组训练 + 评估
python gpu/train_ml_gpu.py # 输出 models/steg_classifier_gpu.joblib
# 3) 单张图像 GPU 检测
python gpu/predict_gpu.py <图像> [<图像>...]
实测 (RTX 4060 Laptop, 纯校园照片)
数据源已从 data/campus_jpg 中去除 DIP4E 教材灰度 tif,改用纯校园照片目录
data/campus_jpg(仅 414 张 jpg)作为唯一数据源,CPU 与 GPU 两条管线均已全量重跑。
- GPU 统计特征管线:特征 2070 张 512² 灰度约 5s(≈397 img/s),GPU 利用率
峰值 99% / 平均 74%;验证 AUC≈0.790,acc≈0.802(Youden 阈值 0.713)。
2026-09-14 复核:这一行已可复现(
experiments/data/gpu_pipeline_metrics.csv,--datas imageset --srm off得到 AUC 0.7903、阈值 0.7130、acc 0.8024)。 注意它对应--srm off;默认--srm auto(= on)在imageset上只有 AUC 0.6544 —— 两条口径不同,数字不可混用。 - CPU 特征管线:2898 样本(414 干净 + 2484 含密,6 档),CV 最佳为
LogisticRegression CV-AUC≈0.759;held-out AUC≈0.781、acc≈0.780;
matrix p2 d0.80/p3 d0.50检出≈100%/99%、nsF5 p2 d0.85≈95%、弱nsF5 p3 d0.25≈52%。 - 一致性:
python gpu/featurize_gpu.py自检,GPU 与 CPU 参考特征逐项一致 (RS 到 bit 级、浮点 ~1e-7)。注意该自检走use_srm=False;而gpu/train_ml_gpu.py默认--srm on,训练时先做 SRM 高通预处理再过 11 维 —— 与 CPU 侧"原始图直接提特征"不是同一口径,两边数字不可直接并列。
参考基准数据集 (BOSSbase 1.01) 重跑
用隐写分析领域事实标准基准 BOSSbase 1.01(官方 dde.binghamton.edu,1.67GB zip,
10,000 张 512×512 灰度 PGM,与管线工作尺寸完全一致)重新生成数据并重训
(make_imageset.py 已增补 *.pgm 支持)。
# 1) 下载解压至 data/BOSSbase_1.01\*.pgm (官方 zip 1.67GB)
# 2) 生成图像集 (本实验取前 2000 张源图 -> 10000 样本, 512x512)
py gpu/make_imageset.py nsf5-steganography\data/BOSSbase_1.01 2000
# 3) 训练 (默认读 imageset.npz, 覆盖 steg_classifier_gpu.joblib)
py gpu/train_ml_gpu.py
- 数据:2000 源图 → 10000 样本(2000 干净 + 8000 含密,1干净+4变体),npz ≈1.57GB。
- 实测 (验证 2000 样本, Youden 阈值 0.798):AUC≈0.644,acc≈0.655;逐档——
matrix p3 d0.50≈80%、nsF5 p2 d0.95≈70%、nsF5 p2 d0.50≈68%、弱nsF5 p3 d0.30≈57%、2026-09-14 复核:这一行已可复现(
--datas imageset_bossbase --srm off→ AUC 0.6438、阈值 0.7983、acc 0.6550)。 干净误报≈48%。 - 对比:相比校园照片基线(AUC≈0.767/0.79)下降,验证了文献公认结论——BOSSbase 经去马赛克加工、统计结构更强,弱密度 LSB 嵌入足印更弱,是更难的隐写分析基准; 远优于 CIFAR 32×32 补充实验(AUC≈0.555,已回退清理)。
- 原基于校园照片的
imageset.npz/steg_classifier_gpu.joblib已备份为imageset_photobase.npz/steg_classifier_gpu_photobase.joblib;BOSSbase 数据与 去 DIP4E 前的旧数据/模型同时备份于backup_20260905/。默认模型为纯校园照片版steg_classifier_gpu.joblib/stego_classifier.joblib。
BOSSbase 全量合并训练(校园 + BOSSbase 10,000 源图)
为最大化训练作用,将 BOSSbase 全量 10,000 张源图与校园照片合并训练,让单一 模型同时看到"自然校园 + 去马赛克基准"两类域。
- 数据生成:
make_imageset.py现以 memmap 逐张落盘(<base>_x.npy,避免大数组 一次性堆叠造成 OOM),支持--out / --id-offset / -n多源分工。py gpu/make_imageset.py data/campus_jpg --out campus # 校园 414 源 -> 2070 样本 py gpu/make_imageset.py data/BOSSbase_1.01 --out bossbase # BOSSbase 10000 源 -> 50000 样本 py gpu/train_ml_gpu.py # 合并两源训练(默认读取两源)
- 数据:校园 2070 + BOSSbase 50000 = 52070 样本(414+10000 源图,各 1干净+4变体)。
- 实测 (验证 10430 样本, Youden 阈值 0.795):AUC≈0.712,acc≈0.690;逐档——
matrix p3≈87%、nsF5 p2 d0.95≈79%、nsF5 p2 d0.50≈70%、弱nsF5 p3≈50%、干净误报≈41%。2026-09-14 复核:本仓库现有的 BOSSbase 图像集只有 10000 样本(旧数是 50000 样本的全量集),因此无法逐位复现 0.712。用现有数据重跑合并配置 (3410 + 10000 = 13410 样本,
--srm off)得到 AUC 0.6672、阈值 0.7921、 acc 0.6776,见experiments/data/gpu_pipeline_metrics.csv。 - 对比:合并 AUC(0.712) 介于纯校园(0.790) 与 BOSSbase 单跑(0.644) 之间,符合数据 难度梯度——模型在更难基准与自然场景间取得平衡;GPU 特征提取 52k 张约 146s,GPU 满载。
CPU 全量合并训练(校园 + BOSSbase 全量)
CPU 版同样接入 BOSSbase 全量,与校园照片合并训练,覆盖 10,000 张基准源图。
py src/make_dataset.py data/campus_jpg --out campus # 校园 414 源 -> 2898 样本(1干净+6变体)
py src/make_dataset.py data/BOSSbase_1.01 --out bossbase # BOSSbase 10000 源 -> 70000 样本
py src/train_model.py # 合并两源训练(默认读取两源)
- 数据:校园 2898 + BOSSbase 70000 = 72898 样本(10414 clean + 62484 stego,11 维特征)。
- 5 折 GroupKFold CV(照片分组防泄漏):RandomForest CV-AUC≈0.745(最佳)、 XGBoost 0.744、LogisticRegression 0.736、GradientBoosting 0.732。
- held-out 测试(18228 样本):AUC≈0.741,acc≈0.685,bacc≈0.668,thr=0.834;
逐档检出——
matrix p2 d0.80≈99%、matrix p3 d0.50≈85%、nsF5 p2 d0.85≈73%、nsF5 p2 d0.35≈59%、 弱nsF5 p3≈44–55%;低误报点(thr=0.910) 干净误报≈11.6%、含密检出≈38.9%。 - 代价:CPU 全量特征提取 70000 行约 79 分钟(单进程串行,见下文并行改造说明)。
- 对比:CPU AUC(0.741) 略高于 GPU(0.712),符合 CPU 逐张 6 档、更多 stego 变体覆盖更强的预期。
多进程并行(已实现):
make_dataset.py内置多进程并行(-j / --workers N,默认 用满 CPU 核数)。每张图内 6 档 C++ 嵌入/特征有强数据依赖无法图内并行,但图与图 相互独立,按图分片交多个子进程并行处理、主进程流式合并。实测 100 张(700 样本): 单进程 53.5s → 16 核并行 9.5s,加速约 5.6 倍,多进程与单进程输出逐行一致。 注意每进程各自加载fsfeatures.dll,内存按核数倍增(单进程约 54MB)。 命令:python src/make_dataset.py <目录> --out x -j 15
依赖:
torch(CUDA)、numpy、Pillow、scipy、scikit-learn、joblib、nvidia-ml-py(可选,用于上报 GPU 利用率)。样本数据gpu/data/*较大(含_x.npy大数组),不入库,可随时重新生成。
同 CPU 版一样,GPU 检测器输出的是统计含密概率,弱密度嵌入应结合启发式判读 交叉印证。GPU 特征提取亦可作为大批量图片的批量分析入口复用。
正确性验证体系(v1.9.0 起成文)
"作者的 nsF5 实现是正确的"这句话不值得相信,可检验的过程才值得。本项目把
正确性做成系统, 分四层, 全部进 CI (ci.yml), 任何人 push 一个提交都会重新跑一遍:
- 往返正确性 (unit 层): 嵌入→提取→比对 的闭环用例覆盖 UTF-8 往返、口令
错误、容量超限、GF(2) 求解、篡改感知等 (
src/test_core.py等); JPEG 压缩域 同样有往返 / 口令 / 容量 / 截断用例 (src/test_jpeg.py, 经桥接层真跑 yccstego)。 - 随机化与对抗: 干净图不得误判 (
test_false_positive.py)、退化图必须存活 (test_pipeline.py)、CLI 走真实子进程验证退出码与中文报错 (test_cli.py/test_cli_jpeg.py)。 - 产物可复现: 冻结 exe 对源码结果逐位一致 (
scripts/test_frozen.py); 权威结果表与实验数据有溯源契约 (docs/RESULTS.md+test_results_contract.py); 每次嵌入可导出 JSON 实验档案,nsf5stego repro按域选择校验强度 —— 像素域 同 yccstego 版本下逐字节复现, 跨版本退回"提取一致"(见src/experiment.py)。 - 教学材料不腐烂: 手册里的事实与数字由
handbook_facts.py --check钉住, 手册代码片段由verify_handbook_experiments.py真的执行, Notebook 由 CI 逐本运行。
当前规模: 115+ 个 pytest 用例 (还在增长), 覆盖率门槛 65% (CI 强制),
GUI 测试在 xvfb 下真实起窗, 浏览器端另有 Playwright + axe 无障碍审计。
本地一键复验: make pytest 或 python -m pytest -q。
持续集成 & 发版
- CI(
.github/workflows/ci.yml):任何对main的推送 / PR 都会自动运行test_core.py与test_steg.py(Python 3.9 / 3.11),并构建wheel + sdist。pytest作业另外守着模型卡与二进制不脱钩(src/test_model_cards.py: sha256 + payload + 指标交叉核对)、手册事实与代码片段、notebook 可执行。 浏览器回归(含 axe 无障碍审计)在webapp-tests.yml里单独跑。 - 自动发布:推送形如
v1.1.0的 tag 时,CI 会构建包并自动创建 GitHub Release, 附带wheel与sdist作为资产,同时自动生成发布说明。 - 发布流程:
# 提交改动并打 tag(本地)
git add -A && git commit -m "feat: v1.1"
git tag v1.1 && git push origin main --tags
版本历史
-
v1.9.0 (当前) — yccstego 接入教学主线 + 可复现性与自证系统
- JPEG 压缩域全面接入: 新增
src/jpegstego.py桥接姊妹项目 yccstego (按 Python>=3.10 自动声明的 PyPI 依赖), CLI embed/extract/analyze 全部支持--jpeg/--quality, GUI 参数区新增"嵌入域"切换 (JPEG 域固定 nsF5 语义, 输出标准 .jpg, 含密图原样字节保存); 分析在 JPEG 域走 |c|=1 系数指纹。 - 实验档案与一键重跑: 每次嵌入生成结构化 JSON 档案 (experiment.py,
schema
nsf5stego.experiment/1; 只记参数/哈希/统计, 消息明文与口令不落盘); CLIembed --json直接输出,nsf5stego repro <档案> -m 原文重跑并逐项 校验 —— 档案记录 yccstego 版本, 同版本两域均逐字节复现, 跨版本退回"提取一致"; GUI「导出实验记录」按钮。 - 往返自检按钮: GUI 一键对当前图+当前参数做 嵌入→提取→比对 的内存闭环, 算法正确性当场可见; README 新增"正确性验证体系"与"同类工具与本项目定位" (Aletheia / CONSEAL / DDE Lab tools 等, 写明本项目定位是交互教学与可复现 实验, 不替代研究工具链)。
- 手册新增第 1½ 章 "JPEG 是什么, DCT 系数是什么": 在进入 LSB 之前先建立 压缩域直觉, 代码片段走已接入主线的桥接层; webapp 新增 8×8 DCT 量化系数 实验室 (+3→+2 减幅高亮, 与汉明演示同风格的 canvas 交互)。
- 测试 96 → 115+ (新增 test_jpeg / test_experiment / test_cli_jpeg), CI pytest 作业补装 yccstego, PyInstaller spec 补 hiddenimports。
- JPEG 压缩域全面接入: 新增
-
v1.8.4 — GUI 操作台升级与品牌图标
- 操作台按工作流重排(输入/参数/执行三段 + 通栏分隔线), 分析结果与运行日志 收进右栏页签, 进度条仅忙时显示; 主流程三键加粗并带快捷键 tooltip; 跨平台字型(Win=YaHei UI / macOS=PingFang SC / Linux=Noto CJK); 空态改为行动引导文案, 待嵌入标签更正为 UTF-8。
- 品牌图标(照片卡+比特流+放大镜)接入 exe/安装器/窗口标题栏; 色板卡 (palette.png)沉淀为设计 token; 关于对话框附联系邮箱。
-
v1.8.3 — 红灯的 tag 不该往 PyPI 送包
- v1.8.2 的 tag 上
pytest是红的, 而发布到 PyPI/发布 GitHub Release照样成功 —— 失败的全量测试作业拦不住发布。原因:build只needs: [test]。 现在build需要全部验证作业通过(test / pytest / gui / feature-consistency / notebooks / handbook / attribution), 任一红则 build / release / pypi 都不跑(并行执行, 不增加墙钟时间)。 - 那次 pytest 红的是我自己写的"并行超时必须报错"用例: 1 秒上限在快 runner 上 来不及触发(2 张 256² 的小图 <1s 跑完)。现在压到 1 毫秒, "来不及"成为必然, 本机连跑三次全过。测试一旦依赖时序就一定会 flake —— 这是本项目第二次栽在 同一个坑上。
- v1.8.2 的 tag 上
-
v1.8.2 — 一个 Release 里出现了两个不同的 wheel
- 审计 v1.8.1 产物时发现 PyPI 的 wheel 与 Release 里的同名 wheel 哈希不同:
ci.yml(Linux)与release.yml(Windows)各构建了一次,后者以同名文件 覆盖了前者。两份的成员文件内容一致,只有METADATA/RECORD与时间戳不同, 但"一个版本一份字节"的溯源因此断了。 - 现在 Windows 作业不再上传 wheel,Release 的 wheel/sdist 一律来自
ci.yml(与 PyPI 同源同字节),它只提供 setup exe 与便携 zip。
- 审计 v1.8.1 产物时发现 PyPI 的 wheel 与 Release 里的同名 wheel 哈希不同:
-
v1.8.1 — 审计 1.8.0:发布链路的可验证性
- 审计发现 v1.8.0 的 tag CI 是红的(PyPI 作业走 trusted publishing,而
pypi.org 端从没配 pending publisher),修复只落在 tag 之后的提交上;不过
PyPI 上的 wheel 与 Release 里的那个字节相同(sha256
02d2177a…), 产物确实来自 tag 那次构建。 - 打包链此前只在 tag 上跑,所以它前两次真实运行(v1.8.0)才暴露问题。
现在
release.yml支持workflow_dispatch预演,且手动运行不会发布; 新增安装器静默安装/卸载冒烟(装到临时目录、不选任务 → 断言用户 PATH 没被动过 → 跑装好的 CLI 往返 → 静默卸载 → 断言目录清空)。 ci.yml改最小权限(workflow 级contents: read,只有 release 作业write; 去掉已不需要的id-token: write),并在 build 作业里真的调用安装后的nsf5stego(--version/embed→extract/analyze --json)—— 此前 CI 只验"模型随包可用",没验过发布入口本身。docs/PACKAGING.md的体积与验收按 CI 产物更正(85.9 / 113.4 MiB), PATH 还原的措辞按安装器真实行为收紧;make help加了完整性护栏 (这条漂移犯过两次);.zcodeignore收进.gitignore。
- 审计发现 v1.8.0 的 tag CI 是红的(PyPI 作业走 trusted publishing,而
pypi.org 端从没配 pending publisher),修复只落在 tag 之后的提交上;不过
PyPI 上的 wheel 与 Release 里的那个字节相同(sha256
-
v1.8.0 — 命令行界面
- 此前
pip install之后唯一的入口是弹 tkinter 窗口(nsf5stego = "gui:main"), 服务器、脚本与批量场景只能自己import ns5_core拼代码。1.8.0 起入口改为src/cli.py:embed(文本可-m或 stdin)/extract/analyze(卡方 + RS- ML,
--json机器可读)/gui四个子命令,参数与方法/p/口令和 GUI 一一对应; 退出码区分 成功(0)/运行失败(1)/参数错误(2),解码失败与容量超限都以明确提示 收场而不是栈回溯或乱码。nsf5stego gui与python src/gui.py行为不变, 且在 tkinter 缺失 / 无显示环境时给出可行动提示。
- ML,
- 另有三处小体验:
embed的 stdin 文本按 UTF-8 优先解码(cp936 控制台管道 传中文不再乱码嵌入); GUI 标题栏显示版本号;make webapp一条命令本地起 交互实验室。 - 安装布局修复:
pip install后 GUI 的产物目录原来是解释器根下的output/(系统 Python 直接 PermissionError),现在自动改写当前工作目录;analyze支持多图批量(逐行汇总,--json数组, 坏图容错); GUI 演示图 缺失时按 seed=42 确定性配方一键生成。 - GUI 蓝白"国企风"改版: 深蓝横幅 + 白色卡片 + 主操作深蓝按钮, 统一浅蓝边框与微软雅黑字体; 演示面板同风格、教学语义配色不变; 顺手修复灵敏度提示叠字与窗口高度不足两处布局问题。
- 测试:
src/test_cli.py十五个用例走真实子进程 + 进程内 stdin 解码单测, 另新增src/test_pathutil.py(输出目录两种布局),CLI 控制台输出同时受 GBK 静态护栏约束。
- 此前
-
v1.7.2 — 同类隐患的全仓排查
- 1.7.1 修掉 OOD 评估的 fork 死锁之后,把这类隐患全仓扫了一遍:进程池只有三处
(
ood_eval已修、make_dataset本次修、video_engine_v2本来就是 spawn), DataLoader 默认num_workers=0。src/make_dataset.py改为显式 spawn +--pool-timeout断路器,并补两条慢测试:workers=2与workers=1逐行一致、 超时必须以非零码报错 —— 这条多进程分支此前从未被执行过(CLI 冒烟只看--help)。 - 给部署模型的生产者补上第一条端到端冒烟测试(自造 143 维小语料跑完整条链),
它第一次跑就抓到:
youden_threshold会返回inf(roc_curve的首个阈值就是 inf,弱可分数据上argmax常落在那里)——阈值成了 inf 之后,模型对任何图都不判 含密且不报错。两处实现都已加np.isfinite过滤;随仓库分发的两个模型没踩到 (0.9493 / 0.9595 都是有限值),重跑真实语料与入库指标逐项一致。 - 手册里的"置换加速 263 倍"被更正:它与项目自己的
bench_permute.csv对不上, 而且自己给的耗时(12.3 s / 0.223 s)算出来也只有约 55 倍。现在重跑基准并统一为 "4096²:15.6 s → 0.24 s(约 65×),小 N 处最高约 240×,数据见experiments/data/bench_permute.csv",网页 / DOCX / PDF / 视频脚本七处一致, 并让handbook_facts.py守住(263列入禁词、产物名列入必填)。
- 1.7.1 修掉 OOD 评估的 fork 死锁之后,把这类隐患全仓扫了一遍:进程池只有三处
(
-
v1.7.1 — 教学材料的悬空引用
- 手册(中英文 ch03 / ch11 / 附录 F)把
yccstego写成"项目yccstego扩展", 但它是独立仓库与 PyPI 包(pip install yccstego),本仓库里没有它的代码 —— 读者按手册去找会一无所获。现已改为"姊妹项目yccstego"并给出仓库地址, README 新增"姊妹项目"一行;handbook_facts.py把该地址列进REQUIRED, DOCX / 网页 / 入库 PDF 三份材料缺它即 CI 红。入库 PDF 重新导出(英文 74 → 75 页)。 - OOD 评估进 CI:
experiments/ood_eval.py产出 README 的头条数字,此前从未 在 CI 里跑过(要 1514 张外部照片),1.6.9 新增的--workers并行路径更是零覆盖。 新增src/test_ood_smoke.py:用合成照片跑通这条链,钉住"判据=payload 阈值""并行 与单进程逐位一致""fp_rate/Wilson CI/ALL 行自洽"。 - 备用 PDF 路径补字形:xelatex 那条路径此前有 22 个符号(
① ᵖ ₄等)在字体里 没有字形,日志里只有一行Missing character、退出码仍是 0,两份 PDF 各有 80 余处 会变空白;现已逐个映射并让脚本缺字形时返回非零。 - 一次 CI 挂死事故的修复:新加的 OOD 冒烟测试在 pytest 进程里 fork 出进程池
(Linux 默认),与已初始化的 LightGBM/OpenMP 线程池撞成死锁,让
pytest作业从 1 分 44 秒变成挂满 6 小时。现在ood_eval.py显式用 spawn、每个分片带超时 (卡住即报错)、冒烟测试改跑 CLI 子进程,CI 各作业也设了timeout-minutes兜底。
- 手册(中英文 ch03 / ch11 / 附录 F)把
-
v1.7.0 — 模型治理:模型卡
- 两个随仓库分发的
.joblib此前是裸二进制:语义只存在于 README 的散文 与 pickle 的 payload 里,读 payload 得先装齐依赖再反序列化,而且 "模型换了、文档没换"没有任何护栏。现在每个模型配一份入库的 JSON 模型卡 (models/*.card.json):语料与协议、指标(held-out / 8-split / 逐档检出 / 真实照片误报率 / 跨语料参考)、特征列表与顺序、超参、训练环境与 git 版本、 适用边界、不适用场景、已知局限,以及sha256。 - 新增契约测试
src/test_model_cards.py(sha256 硬绑定 + payload 逐字段核对 + 有数据时与experiments/data/*.csv交叉核对),随pytest进 CI; 生产者experiments/train_deploy_models.py在落盘后自动刷新模型卡,make model-cards/make model-cards-check是本地入口。 - 卡里如实写下边界:143d 在 1514 张公开真实干净照片上误报 9.58%、53d 28.86%(DIV2K 那 100 张是 51% / 47%),因此两者都不适合单独定案; 对外引用请用 BOSSbase 口径(0.8062 / 0.7172),而不是校园语料的 0.8939 / 0.8391。
- 两个随仓库分发的
-
v1.6.1–v1.6.9 — 审计修复、发布链路与性能
- 逐版记录见
CHANGELOG.md。要点:源图泄漏与 SRM 特征尺度 两处缺陷的修复(1.6.2)、DOI/PyPI 发布链路与仓库治理(1.6.6)、 覆盖率测量口径的三次修正(1.6.7/1.6.8)、SRM 残差向量化与 OOD 并行 (20 分钟 → 1.7 分钟)以及彩色输入的训练/推理偏差修复(1.6.9)。
- 逐版记录见
-
v1.6.0 — 可验证性加固
- CNN 对比实验补上真实实现与真实数据:
gpu/train_cnn.py与gpu/models/{xunet,yenet}.py此前并不存在(论文引用的路径是悬空的), 而sota_compare.py会把 53 维请求静默降级成 11 维、仍标成LGB-53d。 现在特征列缺失即报错,feat_set与dataset分列,所有基线共用同一份 按源图划分与同一套按源图 bootstrap 的置信区间。实测(BOSSbase,按源图 holdout):Ye-Net 0.9541、LGB-143d 0.7529、LGB-53d 0.7172、LGB-11d 0.7128、 Xu-Net 0.5007(未收敛,训练集 AUC 也是 0.50,故不构成"CNN 不如手工 特征"的证据)。CNN 的实现与训练入口在gpu/train_cnn.py与gpu/models/。 - 两个部署模型入库:
models/stego_classifier.joblib(143d) 与models/stego_classifier_v2_jpeg_lgb_51d.joblib(53d),并补上此前缺失的lightgbm依赖 —— 否则 clone 后 ML 判定仍是available=False。 - C++ 改为源码跨平台编译:不再入库任何二进制,
make cpp三平台各自 产出.dll/.so/.dylib;CI 现在真的编译并执行 C++ 一致性自检, 随后再删掉产物验证纯 Python 回退路径。 - 测试全量进 CI:新增 pytest job 与无头 GUI job(xvfb);
test_gui.py此前不在任何 workflow 里;test_false_positive.py里一处ok = ok恒真 赋值让整个假阳性测试变成空断言。 - 浏览器回归测试真正跑起来:
webapp-tests.yml此前未被提交,且即使提交 也会因python命令与 npmmirror 镜像源而失败。 - 教学视频质检修复:两张质检图不可用(一张 33 字节空图、一张缺失),
根因是
qa_sheet()在无抽帧时间时静默写出零高度 PNG 并中断后续章节。 - 已知缺口(如实记录,已于 v1.6.1 关闭):两个部署模型当时没有仓库内的
生产者,
src/train_model.py训不出它们 —— 现由experiments/train_deploy_models.py补齐(见本文件"模型从哪来"一节)。 - 注:实验数据(
experiments/data/、data/dataset_*.csv)按项目约定不入库 (可重新生成)。本版新增的 CNN 实现放在gpu/。
- CNN 对比实验补上真实实现与真实数据:
-
v1.5.0 — 学习手册发布 + Zenodo DOI
- 发布中英文学习手册(PDF)至
docs/:docs/学习手册-从零读懂nsF5隐写项目.pdf(中文, 12 周快速入门路线, 深入版见附录 F, 67 页)docs/Learning-Handbook-From-Zero-to-nsF5-Steganography.pdf(英文, 12-week quick-start roadmap, deep 6–12 month track in Appendix F, 74 页)
- 涵盖 v1.4.0 全部新特性: 143d/53d 双版本 ML 模型、SRM 高通滤波、特征可解释性分析
- 零基础: 从"像素与二进制"到"LGB 分类器超参调优"的完整学习路径
- 新增 Zenodo 存档 DOI 徽章
- 发布中英文学习手册(PDF)至
-
v1.4.0 — 双版本 ML 模型 ⚠ 本节数字已于 2026-09-14 审计作废 (源图泄漏 + SRM 特征尺度错误;现口径见 CHANGELOG 1.6.2: 143d held-out 0.8939 / 8-split 0.8980,53d 0.8391 / 0.8461,且 143d 优于 53d)
- 143d 默认版(
stego_classifier.joblib) — LGB tuned (num_leaves=31, n_estimators=800, learning_rate=0.03, min_child_samples=10)- Held-out AUC 0.8946,8 split 平均 0.9085
- OOD 鲁棒:1/8 fp(median prob 0.011),真实校园 JPEG 干净
- 训练数据:
data/dataset_campus_v2_jpeg.csv(143d,12 档变体 + 414 张 JPEG 干净)
- 53d 可解释版(
stego_classifier_v2_jpeg_lgb_51d.joblib) — 去 SRM 90 维- Held-out AUC 0.9100(+0.015),8 split 平均 0.9227(+0.014)
- 弱档检出全面优于 143d:nsF5 p3 d=0.25 87.2%、matrix p3 d=0.40 95.4%
- OOD 鲁棒:3/8 fp(牺牲少量鲁棒性换 AUC 与可解释性)
- 51 维有明确统计定义,可对单图输出 Top 贡献特征(论文/教学推荐)
- 关键发现:v2 143d 中
73% 增益来自 31 个可解释特征,SRM 90 维平均单特征 AUC 仅 0.500.52 (近随机),是 LGB 中的"噪声特征";但 SRM 残差对 JPEG 高频噪声有过滤作用,故 143d 在 OOD 上更稳
- 143d 默认版(
-
v1.3.1 (待发布)
- 补齐运行时依赖
matplotlib/joblib/scikit-learn/pandas(此前缺失导致 ML/绘图模块导入崩溃)
- 补齐运行时依赖
-
v1.3.0
- 新增矩阵编码演示面板(GUI):随机/可点击块 LSB,实时计算伴随式
s与目标m的差值d,在汉明校验矩阵H中定位命中的列并高亮被改系数, 执行修改后校验H·x==m。核心逻辑独立于src/matrix_demo.py。 - 新增隐写分析随载荷扫描面板:
payload滑条 0→0.4,逐档重新嵌入并实时刷新 卡方 p 值 / RS 估计嵌入率 / ML 含密概率三曲线 (单图无真 AUC,以 ML 概率作区分趋势示意)。逻辑位于src/scan_panel.py。
- 新增矩阵编码演示面板(GUI):随机/可点击块 LSB,实时计算伴随式
-
SRM 高通滤波预处理层
- 新增
src/srm_filter.py(30 个标准 SRM 核,numpy/torch 双实现,合成单通道增强图);make_dataset.py增--preprocess srm、featurize_gpu.extract_features_gpu增use_srm开关(train_ml_gpu增--srm on/off)。 - 同源校园:CV-AUC 0.7594→0.7922、测试 AUC 0.7811→0.8085,弱密度检出明显提升。
- 跨源合并(校园+BOSSbase 全量):CPU 测试 AUC 0.741→0.704、GPU 校园+BOSSbase2000源 验证 AUC 0.651→0.572,SRM 均下降,两管线相互印证(详见上文 SRM 小节"重要"段)。
- 结论与默认:默认模型=未 SRM 合并全量(≈0.741),SRM 单源校园(≈0.8085)另存为
stego_classifier_campus_srm.joblib备用。
- 新增
-
数据重跑(移除 DIP4E)
- 从数据源去除 DIP4E 教材灰度 tif,建立纯校园照片目录
data/campus_jpg(仅 414 jpg), CPU 与 GPU 管线全量重跑:GPU 验证 AUC≈0.790、CPU held-out AUC≈0.781; 旧数据/模型备份至backup_20260905/。
- 从数据源去除 DIP4E 教材灰度 tif,建立纯校园照片目录
-
v1.2.2
- GPU 数据集支持 jpg/tif 等多格式混合(
gpu/make_imageset.py),去掉默认 150 张上限、默认全量; 曾用加入 DIP4E tif 后的 682 张 / 3410 样本 重训(DIP4E 已于后续数据重跑中移除)。 - README 检测指标按数据集区分(自然照片 A:AUC≈0.79 / 含 tif 混合 B:AUC≈0.767); 模型为二进制、不入库,与 PyPI 上 ver1.2.1 明确区分。
- GPU 数据集支持 jpg/tif 等多格式混合(
-
v1.2.1
- 修复仅 1 个有效灰度对的强二值图(
letterA/B/T.tif)隐写分析lgamma(0)崩溃,返回中性 p 值。 - 新增 C++
nsf5_permute确定性置乱加速:4096² 置乱 524ms→210ms、整体嵌入约 540→281ms; DLL 缺失自动回退同算法 Python,编码/解码两端序列恒定可逆。 - GUI 绘图预览崩溃修复,并按屏幕尺寸 1:1 高质量展示。
- 修复仅 1 个有效灰度对的强二值图(
-
v1.2.0
- 新增 GPU 版(
gpu/):PyTorch 批量向量化复刻 11 维统计特征(RS/卡方/熵/前缀 p, 与 CPU 参考实现 bit 级一致),GPU 提取 2070 张特征 ≈5s、利用率峰值 99%。 - 新增 GPU 训练/推理管线
train_ml_gpu.py、predict_gpu.py;验证 AUC≈0.79。 - 实测与文档说明了"裸像素深度 CNN 需海量独立源图、局部特征法更适合小样本隐写检测"。
- 新增 GPU 版(
-
v1.1.0
- 新增 C++ 嵌入加速
cpp/nsf5embed.dll(修复汉明缓存越界;与 Python 像素级一致并经回环校验)。 - 监督学习升级:数据集扩展为 6 档密度变体、构建提速约 8→90 倍,
train_model.py改为 5 折 GroupKFold 交叉验证选模。 - 修复低误报阈值选择 bug(
threshold_for_fp取最低阈值而非最高,保障真实低误报检出率)。 - GUI 新增 判定灵敏度(严格 / 均衡 / 宽松),联动启发式与 ML 判决阈值。
- 新增 CI、Apache-2.0 License、构建与发版说明。
- 新增 C++ 嵌入加速
-
v1.0.0
- nsF5 伴随式矩阵编码 + 湿纸编码;图像哈希键控;盲隐写分析;GUI;码族与效率绘图。
- C++ 特征提取
cpp/fsfeatures.dll;一版有监督分类器(LR,AUC≈0.78)。
许可
Metadata
Release files for nsf5stego 1.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nsf5stego-1.9.0.tar.gz | 2.5 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nsf5stego-1.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.9 MB
Release files / nsf5stego-1.9.0.tar.gz
| Download URL | nsf5stego-1.9.0.tar.gz |
|---|---|
| Size | 2.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
60cfd7d776be2f52fedab6ddb1f4480ce274372a629a2359ba959f4726266469
|
|
BLAKE2b-256 checksum How to use checksums |
b810a31470b1ec31746769083e5ced2879b6ac5c4ff6b89711fc98460d33de7d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / nsf5stego-1.9.0-py3-none-any.whl
| Download URL | nsf5stego-1.9.0-py3-none-any.whl |
|---|---|
| Size | 2.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
62052d61d1db0aac06afef8f88adb17dcadb19a8357acc843db39a4208a98c1b
|
|
BLAKE2b-256 checksum How to use checksums |
b24da253c5cc7e57e1e31fd478adacb552540597cdab3dcde14f1066d72b0be6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|