Package detail

signature-verifier

maikro0MIT1.0.0

中文手写签名验证工具,基于像素重合度算法,支持 Web、微信小程序。

signature, canvas, handwriting, verification

readme

cn-signature-verifier

一个轻量级的中文手写签名验证工具。 基于“骨架重合度”算法,无需后端 AI 模型,纯前端实现。可以有效防止用户乱涂乱画。

安装

npm install cn-signature-verifier

运行示例 (Run Examples)

本项目提供了三个平台的完整示例,位于 examples/ 目录下。

  1. Web / H5: 直接用浏览器打开 examples/web-h5/index.html

  2. 微信小程序: 使用微信开发者工具导入 examples/wechat-native 目录。

  3. UniApp: 使用 HBuilderX 导入 examples/uniapp 目录运行。

这份文档详细介绍了核心算法函数 verifyPixels 的用法、参数配置以及调优指南。

此函数是整个库的“大脑”,它是纯 JavaScript 实现的,不依赖 DOM,因此既可以在 浏览器/小程序 中使用,也可以在 Node.js 后端环境中使用。


verifyPixels API 使用文档

verifyPixels 是核心校验函数,用于对比“用户手写笔迹”与“标准参考字体”的像素重合度。

1. 函数签名

import { verifyPixels } from 'cn-signature-verifier/src/core.js';

const result = verifyPixels(userPixels, refPixels, options);

2. 参数说明

参数名 类型 必填 说明
userPixels Uint8ClampedArray 用户手写画布的像素数据。格式为 [r, g, b, a, ...] 的一维数组。
refPixels Uint8ClampedArray 标准参考字体画布的像素数据。格式同上。长度必须与 userPixels 一致
options Object 配置项对象,用于调整验证的严格程度(详见下表)。

options 配置项详解

属性名 类型 默认值 描述 调优建议
thresholdCoverage Number 0.12 覆盖率及格线 (0~1)。
计算公式:重合像素 / 参考字像素
表示用户笔迹覆盖了背景字的多少比例。
太严(难通过):调低 (如 0.08)。
太松(空心字也过):调高 (如 0.2)。
注意:如果背景字很粗,建议调低此值。
thresholdMess Number 0.65 越界率容忍上限 (0~1)。
计算公式:越界像素 / 用户总像素
表示用户的笔画有多少比例画在了字外面。
太严(连笔挂掉):调高 (如 0.75)。
太松(乱画也能过):调低 (如 0.5)。
minInkRatio Number 0.005 最小笔墨量
防止用户只点了一个点就提交。
通常保持默认即可。

3. 返回值说明

函数返回一个对象 Result

{
    pass: Boolean,      // 验证是否通过
    message: String,    // 提示信息 (例如 "验证通过" 或 "请勿乱涂乱画")
    score: {
        coverage: Number, // 实际覆盖率 (例如 0.15 代表 15%)
        mess: Number,     // 实际越界率 (例如 0.40 代表 40%)
        ink: Number       // 用户绘制的总像素点数
    }
}

4. 算法原理 (简述)

该算法基于 RGBA 像素分析

  1. 识别参考区:遍历 refPixels,Alpha 值 > 20 的区域被视为“标准答案区域”。
  2. 识别笔迹:遍历 userPixels,识别深色像素(R < 150 且 Alpha > 50)为“用户笔迹”。
  3. 对比计算
    • 重合点 (Overlap):用户笔迹 落在 标准答案区域内的点。
    • 越界点 (Mess):用户笔迹 落在 标准答案区域外的点。
  4. 判定
    • 如果 Overlop / 参考总面积 > thresholdCoverage
    • 并且 Mess / 用户总笔迹 < thresholdMess
    • 则判定为 通过

5. 使用示例

场景 A:在浏览器中使用 (原生 Canvas)

import { verifyPixels } from 'cn-signature-verifier/src/core.js';

// 1. 获取用户 Canvas 数据
const userCanvas = document.getElementById('userCanvas');
const userCtx = userCanvas.getContext('2d');
const userData = userCtx.getImageData(0, 0, 300, 150).data; // Uint8ClampedArray

// 2. 生成参考数据 (在内存中创建一个 Canvas)
const refCanvas = document.createElement('canvas');
refCanvas.width = 300;
refCanvas.height = 150;
const refCtx = refCanvas.getContext('2d');
// 绘制标准字
refCtx.font = 'bold 100px sans-serif';
refCtx.fillStyle = 'black';
refCtx.fillText('张三', 150, 75);
const refData = refCtx.getImageData(0, 0, 300, 150).data;

// 3. 调用验证
const result = verifyPixels(userData, refData, {
    thresholdCoverage: 0.1, // 稍微宽松一点
    thresholdMess: 0.6
});

if (result.pass) {
    console.log("验证成功!覆盖率:", result.score.coverage);
} else {
    console.error(result.message);
}

场景 B:在 Node.js 后端使用

如果你想在服务器端验证签名(例如用户上传了签名的 base64 图片),你需要使用 canvas 库。

npm install canvas
const { createCanvas, loadImage } = require('canvas');
const { verifyPixels } = require('cn-signature-verifier/src/core.js');

async function verifyOnServer(userImageBuffer, targetName) {
    const width = 300;
    const height = 150;

    // 1. 加载用户上传的图片
    const userCanvas = createCanvas(width, height);
    const userCtx = userCanvas.getContext('2d');
    const img = await loadImage(userImageBuffer);
    userCtx.drawImage(img, 0, 0, width, height);
    const userPixels = userCtx.getImageData(0, 0, width, height).data;

    // 2. 生成标准字图片
    const refCanvas = createCanvas(width, height);
    const refCtx = refCanvas.getContext('2d');
    refCtx.font = 'bold 100px "SimHei", sans-serif'; // 注意服务器要有对应字体
    refCtx.fillStyle = '#000';
    refCtx.textAlign = 'center';
    refCtx.textBaseline = 'middle';
    // 使用 strokeText 加粗,提高服务器端验证的容错率
    refCtx.lineWidth = 15;
    refCtx.strokeText(targetName, width / 2, height / 2);
    refCtx.fillText(targetName, width / 2, height / 2);

    const refPixels = refCtx.getImageData(0, 0, width, height).data;

    // 3. 验证
    // 注意:如果使用了加粗(lineWidth),建议降低 thresholdCoverage
    return verifyPixels(userPixels, refPixels, {
        thresholdCoverage: 0.08 
    });
}

6. 调优指南 (Troubleshooting)

当验证结果不符合预期时,请根据返回的 score 进行调整:

Q1: 用户写得很工整,但提示“请沿着背景字书写” (验证失败)

  • 原因:用户的笔画太细,或者背景字太细,导致重合面积不够;或者 thresholdCoverage 设得太高。
  • 解决
    1. 在 UI 上将背景字加粗 (lineWidth + strokeText)。
    2. options.thresholdCoverage 从默认的 0.12 降低到 0.08

Q2: 用户在旁边乱画圈,居然通过了?

  • 原因thresholdMess 设得太高,允许了过多的杂乱笔画。
  • 解决:将 options.thresholdMess 从默认的 0.65 降低到 0.5 或更低。

Q3: 用户还没写完(只写了一半),提示通过了?

  • 原因:用户写的那一半完美覆盖了背景字,导致 mess 很低,且 coverage 刚好过了及格线。
  • 解决:提高 options.thresholdCoverage

Q4: 我把背景字变得非常非常粗,结果很难通过?

  • 原因:分母(背景字面积)变大了,分子(重合面积)增加幅度不如分母大,导致覆盖率数值下降。
  • 解决字越粗,thresholdCoverage 应该越低。建议设为 0.06 - 0.08

7. 移动端体验优化指南

在手机端手写时,用户常反馈“明明写对了,但验证不通过”或“很难完全覆盖背景字”。这通常是因为:

  1. 手指遮挡:用户看不清笔尖落点。
  2. 背景字太细:标准字体的线条很细,用户稍微写偏一点就算“越界”或“未重合”。

解决方案:加粗字体 + 调整阈值

通过 Canvas 的 描边 (Stroke) 功能人为加粗背景字,并配合参数调整,可以显著提高通过率。

步骤 1:修改 UI 显示(让用户看到的字变粗)

在你的绘图初始化逻辑中(例如 initdrawBg 函数),使用 strokeText 来加粗文字。

// 1. 设置颜色
ctx.fillStyle = '#e0e0e0';
ctx.strokeStyle = '#e0e0e0'; // 描边颜色需与填充一致

// 2. 设置字体
ctx.font = 'bold 100px sans-serif'; // 使用粗体
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

// 3. 【关键】设置描边宽度 (加粗程度)
// 推荐 15-20px,数值越大字越粗,容错率越高
ctx.lineWidth = 15; 
ctx.lineJoin = 'round'; // 圆润转角

// 4. 同时绘制描边和填充
ctx.strokeText(targetName, x, y); // 先描边
ctx.fillText(targetName, x, y);   // 后填充

步骤 2:修改验证逻辑(让程序比对的字也变粗)

注意: 验证时生成的“参考图”必须与用户看到的“背景图”粗细一致。

如果你使用的是封装好的 verifyWebverifyUniApp,可能需要修改适配器源码,或者确保传入的 font 足够粗。如果需要极高的容错率,建议直接调整核心校验参数。

步骤 3:调整覆盖率阈值 (thresholdCoverage)

这是最重要的一步。 当字体变粗后,字体的总像素面积(分母)会大幅增加。如果用户的笔画粗细不变,计算出的覆盖率数值会自然下降。

  • 未加粗时:及格线通常设为 0.12 (12%)。
  • 加粗后 (lineWidth=15):建议将及格线降低至 0.06 ~ 0.08

完整优化配置示例

const result = verifyPixels(userPixels, refPixels, {
    // 降低覆盖率要求,因为分母(背景字)变大了
    thresholdCoverage: 0.08, 

    // 越界率保持不变,或者稍微放宽
    thresholdMess: 0.65,

    // 必须确保 refPixels 生成时也使用了 strokeText 加粗,
    // 否则用户画在描边区域会被算作“越界”。
});

附:常见粗细设置参考

场景 lineWidth 设置 推荐 thresholdCoverage 体验描述
PC 鼠标签字 0 (默认) 0.12 精准,要求高。
手机 - 适中 10 0.10 适合电容笔书写。
手机 - 宽松 15 0.08 推荐。手指书写体验最佳,不容易误判。
极度宽松 25 0.05 字非常肥大,用户几乎随便画画都能碰到字。