
简介jbig2enc-0.2.tar.gz 是 Apago JBIG2 编码程序的源代码包面向需要处理大量二值/黑白文档图像的开发者和归档人员可基于 JBIG2 标准将图像压缩为独立文件或 PDF 内嵌数据流相比传统 G4 编码具有更高压缩率。资源采用 gz 压缩格式打包共 13 个文件以 C 源代码和头文件为主辅以 Makefile 构建脚本、README 与 HTML 说明文档、PATENTS 专利说明及 Python 辅助脚本整个压缩包仅 24KB结构简洁轻量易部署。解压后可按标准编译流程生成命令行工具方便直接对黑白图片或扫描件进行 JBIG2 编码同时可研究模板匹配、区域编码与熵编码等核心实现为二次开发或算法学习提供参考适合电子书扫描页、传真文档、数字化图书馆等大批量二值图像压缩场景。已有 360 人学习/下载值得对提升二值图像存储效率或研究 JBIG2 算法感兴趣的读者参考。1. jbig2enc 是干什么的把双色调扫描页压到接近极限2006 年Adam Langley 把 jbig2enc-0.2.tar.gz 上传时JBIG2 已经被 PDF 1.4 和 TIFF 标准接纳多年但市面上能用的编码器几乎都是商用实现Apago JBIG2 就是当时最常被 PDF 产品捆绑的引擎。jbig2enc 的出现让开源社区第一次能用同一个算法做到一个双色调 A4 扫描页CCITT G4 压到 100 KB 上下JBIG2 无损模式能到 50 KB而有损符号模式常常再压一半。省出来的不是视觉质量而是“重复出现的字形只存一次后续只记坐标”的统计冗余。任何 PDF 阅读器、Ghostscript 都自带 JBIG2 解码器所以小文件不需要额外插件。这篇文章面向做扫描归档、PDF 预处理、存储优化的人从算法原理讲到编译、命令行参数、整条 PDF 收档流水线最后给你一套可执行的验收方法。2. JBIG 与 JBIG2 选型jbig2enc 的符号匹配压缩原理2.1 从 JBIG 到 JBIG2模板预测与符号字典的区别JBIG 和 JBIG2 的名字只差一个数字但压缩策略完全不同。JBIG 是“逐像素上下文建模”每个像素用周围 10 个左右已编码像素组成模板统计条件概率再送进自适应算术编码器。它对复印稿、抖动图这类随机排布的二进制图像有效但字符页面上大量结构化重复信息并没有被显式利用。JBIG2 在保留算术编码的同时引入了“区域 符号字典”的架构。整个页面被切分为文本区域、半色调区域和普通图像区域文本区域里的字形作为符号提取出来相同或相似的符号合并进字典页面主体只存储“在哪一页、哪一个坐标、引用字典第几号符号”。对于一页 500 字的合同扫描件字典往往只有几十到几百个符号正文部分变成纯坐标序列压缩率因此大幅领先。两者的适用对象也因此分道。JBIG 适合杂点多的整页图片JBIG2 适合文字、表格、工程图这类“符号重复出现”的版面。下表是选型时最常用的对比维度对比项JBIGJBIG2jbig2enc 实现编码单位像素上下文模板区域、符号字典、坐标流是否有损模式只支持无损支持有损符号匹配对文字页压缩率接近 G3/G4通常比 G4 高 2 到 5 倍解码端支持PDF 1.4 以前的老实现PDF 阅读器、Ghostscript、mupdf 均内置典型使用场景传真、低端扫描仪PDF 归档、电子卷宗、文档分发这也是 jbig2enc 一直值得用的原因JBIG2 是标准解码端已经被大面积普及编码器反而是稀缺资源。2.2 符号分割、哈希匹配与聚类的过程jbig2enc 的输入是 PBM 格式的二值位图也就是每个像素只有 0 或 1。处理流程大致分四步理解这四步才能解释后面所有参数第一扫描线分割。工具沿水平方向寻找黑色像素的连通区域把疑似字符的区域切成长宽不等的矩形。这一步不做识别只是把图像拆成一个个独立的“疑似符号”小块。第二哈希归类。每个切出来的矩形会通过某种哈希算法得到一个摘要摘要相同或接近的小块被分到同一类。这一步决定了“哪些字形是同一个符号”类内所有成员的像素形态不需要完全一致允许存在一个匹配误差上限。第三构建符号字典。每个类保存一个代表性位图通常是该类中第一次出现的那个后续出现的同类符号用“引用字典编号”表示。第四细化补偿。如果某个符号和字典代表的差异过大jbig2enc 可以额外记录一小块差值图称为 refine 区域。打开细化模式时误码率低但文件变大关掉细化时靠纯字典引用换取最小体积。常见误解是 jbig2enc 会 OCR 或识别文字实际上它根本不懂字形含义只认识“这两个方块长得像”。这种设计的好处是语言无关阿拉伯文、中文、手写批注都能压缩坏处也无法区分相似但语义不同的字形所以有损模式调激进时会出现个别字符形状微变。2.3 有损压缩发生在哪一步threshold 参数和像素误差的关系jbig2enc 的有损能力集中在“符号匹配”这一步。两个符号不需要完全一致才能合并只要差异像素占总像素的比例低于某个阈值就当作同一个符号处理。这个阈值就是--threshold取值范围 0 到 1默认通常取 0.5。0.5 的含义可以近似理解为像素差异不超过 50% 就合并。取值越小合并越严格文件越大取值越大合并越激进文件越小但字形细节丢失越多。阈值设在 0.2 到 0.3 时肉眼几乎分辨不出差别适合对外归档0.6 以上开始出现笔画粘连、细线消失只适合内部留存。实际操作中不建议一上来就调这个参数先固定默认值跑完整条链路再用第 5 章的像素对比法评估最后才动阈值。另外要说明--threshold只控制符号合并的宽容度和半色调图像的压缩无关。页面里若有大面积照片类内容JBIG2 对它的处理效率并不比旧标准高这也是后面章节里要做“G4 与 JBIG2 选型”判断的原因。3. 编译 jbig2enc 并跑通最小命令3.1 编译 jbig2enc-0.2.tar.gz 的常见坑jbig2enc-0.2.tar.gz 是 2006 年发布的版本源码量不大核心文件只有 jbig2.c 和 jbig2enc.c。直接解压后最可能遇到两个问题一是仓库缺少生成好的 configure 脚本二是系统缺少 libtiff 或 libpng 头文件。先确认系统依赖。Ubuntu 上通常执行sudo apt-get install libtiff-dev libpng-dev autoconf automake libtool然后进入源码目录按顺序重新生成构建脚本并编译tar xzf jbig2enc-0.2.tar.gz cd jbig2enc-0.2 autoreconf -fi ./configure --prefix/usr/local make sudo make install代码逻辑说明autoreconf -fi用于在源码自带 configure 缺失或版本过旧时由 autoconf 重新生成 configure-f强制覆盖旧文件-i会补齐缺失的 aux 文件。./configure --prefix/usr/local把安装路径固定在 /usr/local避免和发行版包管理器目录冲突。最后make sudo make install编译并安装。如果提示tiffio.h: No such file or directory说明 libtiff 开发包没装好。jbig2enc 对 TIFF 只是读输入用纯 PBM 流程不需要它可以用./configure --without-libtiff --without-libpng跳过两个可选依赖只保核心编码功能。编译过程中出现少数warning: implicit declaration是旧代码的常见现象只要最终生成 jbig2 可执行文件基本不影响使用。3.2 jbig2 命令与常用参数表编译后只有一个命令行工具名字就是jbig2。它读入一个或多个 PBM 文件在标准输出输出 JBIG2 位流。0.2 版本的核心参数和后期 0.28 版本保持一致常用组合如下参数作用我的建议-s符号模式即 2.2 节描述的符号分割压缩文字页和表格页必开-p符号细化压缩模式允许记录符号的差值修正需要更高保真时追加-v输出进度的详细日志输出到 stderr不影响位流首次跑批建议开启-b 前缀把编码结果写入前缀.jb2和前缀.sym多页处理时比重定向更可控--threshold 数值符号合并容差默认 0.5从默认值起步最后再调--segments n设置段号起始值用于手工合并多个文件时避免冲突单文件场景不用管--huffman用哈夫曼熵编码替代默认算术编码多数场景体积略小CPU 略增最安全的入门命令是把所有输出重定向到文件cd /tmp jbig2 -s -p -v page01.pbm page01.jb2参数说明-s -p组合表示“符号模式 细化模式”这是文字扫描页体积和保真最均衡的组合。-v只是日志真正编码结果走 stdout所以必须重定向到.jb2文件。如果省略-p编码会更快但遇到倾斜或笔画略粗的字形时重影概率上升。最终生成的 page01.jb2 就是一个完整 JBIG2 编码流可以直接放进支持该标准的解析器。3.3 最小可复现回路PBM → JB2 → PBM验证 jbig2enc 是否正常工作最可靠的办法是把编码结果再用解码器还原和原始 PBM 对比。解码器用 Artifex 出品的jbig2dec很多发行版可以直接安装包名就叫jbig2dec。jbig2 -s -p -v /tmp/page01.pbm /tmp/page01.jb2 ls -l /tmp/page01.pbm /tmp/page01.jb2 jbig2dec -o /tmp/page01.restored.pbm /tmp/page01.jb2 cmp /tmp/page01.pbm /tmp/page01.restored.pbm命令说明第一行编码第二行对比原始和压缩文件体积第三行用 jbig2dec 按严格标准解码还原第四行cmp是逐字节比较。如果这两条命令跑不出差异说明你的输入 PBM 内容正好和符号匹配完全吻合大多数真实扫描件在-s模式下就能肉眼看到笔画变化所以这里不用追求零差异重点验证工具链整体通畅。这一步还顺带帮你确认一件事jbig2dec 能解开 jbig2enc 的位流才说明压缩结果符合 JBIG2 标准可以放心进入 PDF 落盘流程。4. 把 jbig2 压缩接入 PDF 收档流水线4.1 从扫描 PDF 到 PBM 再到 jbig2 压缩生产环境里的输入很少是 PBM通常是扫描软件直接生成的 PDF。把 PDF 里的二进制图像提取成 PBM最常用的是 poppler 自带的pdftoppm。完整流水线可以写成一个 bash 脚本#!/bin/bash set -euo pipefail INPUT${1:-scan.pdf} OUTDIR${2:-jbig2_out} mkdir -p $OUTDIR # 提取单色扫描页300 dpi 是文字识别的入门分辨率 pdftoppm -mono -r 300 $INPUT $OUTDIR/page # 对每一页执行 jbig2enc 编码 for f in $OUTDIR/page-*.pbm; do base${f%.pbm} jbig2 -s -p -v -b $base $f done逻辑说明pdftoppm -mono -r 300把 PDF 每页渲染成单色 PBM-mono 会在渲染阶段做阈值化如果原 PDF 中的扫描件本身已经是二值图这一步不会损失信息。循环里用-b $base替代标准输出重定向生成的page-01.jb2文件名保留原始页码方便后面的质量回溯。需要注意pdftoppm渲染时原始 PDF 里的 DPI 信息会被丢掉只有输出文件的像素尺寸。所以写归档信息时建议把 DPI 作为外部元数据单独记录或者后续用 Ghostscript 重新嵌入。4.2 把 JB2 解码后重组为 PDF 文件JBIG2 解码端在阅读器里是免费的但 PDF 写入端不会直接吃一个裸 jb2 文件。最常见、也是最符合标准的做法是用 jbig2dec 把 jb2 还原成 PBM再用 Ghostscript 把所有 PBM 合成一个 PDF。这样做虽然表面上多绕了一圈但保证了最终 PDF 的渲染效果和被压缩前完全一致。mkdir -p $OUTDIR/restore for jb2 in $OUTDIR/page-*.jb2; do name$(basename $jb2 .jb2) jbig2dec -o $OUTDIR/restore/$name.pbm $jb2 done解码完成后用 Ghostscript 把它们合并成新的 PDFgs -q -dNOPAUSE -dBATCH -sDEVICEpdfwrite \ -sOutputFilecompressed.pdf \ $OUTDIR/restore/page-*.pbm命令说明-sDEVICEpdfwrite让 Ghostscript 把输入的 PBM 合理嵌入 PDF没有指定 JPEG 或 G4 时它会按页面内容自动选择无损压缩通道。这里的关键点是输入已经是 jbig2 恢复出的干净位图所以最终 PDF 的体积和直接嵌入 jb2 流并不完全相同但测试时通常能看到比原始扫描 PDF 小 40% 到 70% 的效果。这个流程的代价是要存两份中间文件磁盘紧张时可以在循环里处理一页删一页。我一般会保留 restore 目录到验收结束后再清理毕竟 jbig2dec 的还原结果就是质量追溯的物证。4.3 为什么不全依赖 Ghostscript 直接压缩有人会问既然 Ghostscript 能写 PDF为什么不用-dPDFSETTINGS/prepress直接压答案是 Ghostscript 的 pdfwrite 设备目前只负责解码 JBIG2不做 JBIG2 编码输出。你给它一个内嵌 JBIG2 的 PDF它读得懂但写出去时会转成 Flate 或其他格式。也就是说用 gs 直接压缩只能得到普通二次压缩收益体验不到符号匹配带来的高压缩率。jbig2enc 存在的意义就是这个空白它把编码侧补齐。标准、解码端、容器格式都不用改只在压缩环节替换掉旧的 CCITT G4 编码器即可。这也是为什么理解原理比背命令更重要的地方你的流水线里可以随时把 jbig2enc 换成任何合规 JBIG2 编码器只要格式是标准位流前后流程都不需要动。5. 压缩质量的三个验证技巧与参数收敛5.1 用 jbig2dec 还原做像素级对比我在 3.3 节用了cmp做字节级比较。真实项目里像素级差异更需要量化。可以用 ImageMagick、Python PIL 或者简单脚本统计差异像素数。推荐 Shell 加 Python 的组合jbig2dec -o restored.pbm encoded.jb2 python3 - EOF import sys from PIL import Image a Image.open(original.pbm).convert(1) b Image.open(restored.pbm).convert(1) diff sum((pa ! pb) for pa, pb in zip(a.getdata(), b.getdata())) print(ftotal pixels: {a.width * a.height}) print(fdiff pixels: {diff}) print(fdiff ratio: {diff / (a.width * a.height) * 100:.3f}%) EOF说明Image.convert(1)强制把两张图转化为二进制模式再逐像素比较避免 PBM 本身位排列造成误判。差异比例在 0.1% 以下通常说明视觉无损0.5% 以上就要回到源码找具体位置是不是细笔画被吞了。这个脚本也适合批量跑 50 页样本统计平均值比肉眼抽查可靠得多。5.2 什么时候别用 jbig2和 CCITT G4 的取舍jbig2 不是万能的。它强在重复符号多、版面干净的文字页。反过来手写批注大量连笔、页面底色不均、半色调照片占一半版面时符号匹配的收益会迅速下降甚至不如 G4。建议用一张表格作为你的选型依据输入特征首选方案理由印刷体合同、证书、票据jbig2enc -s -p重复符号极多压缩率高手写档案先试 G4连笔字难聚成稳定符号照片 文字混排G4 或 JPEG 2000符号模式对照片无优势300 dpi 以上文字jbig2enc 有损模式高分辨率下瑕疵不易被察觉必须保持绝对像素G4 或 JBIG2 无损 threshold0有损模式不适合做证据保留这个判断应该在压缩前做而不是压缩后看体积后悔。批量流程可以在脚本里先统计页面黑色像素率如果超过 30%可以直接跳过 jbig2enc 改用 G4 通道。5.3 阈值从 0.5 开始收敛的实操顺序最后给出我常用的调参次序按风险从低到高排列先跑-s -p --threshold 0.5看一下体积和还原差异体积不满意再降到0.35和0.2每降一次跑一遍像素对比脚本如果差异比例都能接受就保持最低阈值。注意反直觉的一点--threshold越大合并越激进、文件越小但差异像素比率上升剧烈。我见过为了省 10 KB 把阈值从 0.5 调到 0.8 的案例结果整页的“日”字全部被合并成“目”字形状。档案场景里这种失真不可接受所以阈值收敛顺序一定是“从默认往下试”而不是网上流传的“调大压得更狠”。到了 0.2 还嫌大问题多半不在编码参数而在扫描分辨率或二值化质量回头重扫比调参有效。本文还有配套的精品资源点击获取