mirror of
https://github.com/cnwhy/QRCode-decode.git
synced 2026-08-12 20:31:40 +08:00
v0.0.6
This commit is contained in:
Vendored
+140
-7
@@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* qr-decode v0.0.5
|
||||
* qr-decode v0.0.6
|
||||
* (c) cnwhy <w.why@163.com>
|
||||
* Released under the ISC License.
|
||||
*/
|
||||
@@ -2416,6 +2416,10 @@
|
||||
throw "point error";
|
||||
}
|
||||
var point = x * 4 + y * base.width * 4;
|
||||
// 透明/半透明像素按白色背景处理,避免透明区域被误判为黑色模块
|
||||
if (data[point + 3] < 128) {
|
||||
return 255;
|
||||
}
|
||||
return (data[point] * 33.33 + data[point + 1] * 33.33 + data[point + 2] * 33.33) / 100;
|
||||
};
|
||||
};
|
||||
@@ -2430,13 +2434,142 @@
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
function decode(imageDate, debugfn) {
|
||||
var base = {
|
||||
|
||||
// 默认二值化阈值
|
||||
var DEFAULT_THRESHOLD = 153;
|
||||
|
||||
// 图像预处理策略:输入 ImageData,输出 {data, width, height, threshold?}
|
||||
// threshold 可以是单值或数组(多阈值依次尝试)
|
||||
// 后续优化(自适应二值化、放大等)只需在此数组追加策略即可
|
||||
var preprocessStrategies = [null,
|
||||
// 0. 原图原阈值
|
||||
invertImage,
|
||||
// 1. 颜色反转(适配深色背景的反色二维码)
|
||||
otsuStrategy,
|
||||
// 2. Otsu 自动阈值(适配整体偏亮/偏暗的图)
|
||||
invertedOtsuStrategy,
|
||||
// 3. 反转 + Otsu(适配整体偏暗的反色二维码)
|
||||
multiThresholdStrategy // 4. 多阈值扫描(兜底 Otsu 盲区:深色装饰背景等多峰分布美化二维码)
|
||||
];
|
||||
|
||||
// 多阈值扫描集合:仅在前 4 个策略全部失败后触发
|
||||
// 覆盖 Otsu 无法处理的多峰分布图(如“深色背景+灰模块+白间隔”三峰,Otsu 会选错谷),
|
||||
// 以及带过渡带的低对比度图(Otsu 分割点偏低,需更高阈值才能获得清晰模块)
|
||||
// 步长 15 覆盖全区间:o3.png 有效区间 125~150,o2.png 有效区间 180~240
|
||||
var multiThresholdValues = [60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240];
|
||||
|
||||
// 多阈值策略:复用原图数据,对多阈值集合依次尝试
|
||||
function multiThresholdStrategy(imageDate) {
|
||||
return {
|
||||
data: imageDate.data,
|
||||
width: imageDate.width,
|
||||
height: imageDate.height,
|
||||
debugfn: debugfn
|
||||
threshold: multiThresholdValues
|
||||
};
|
||||
return process(imageDate.data, base);
|
||||
}
|
||||
|
||||
// 颜色反转:RGB 取反,alpha 保持不变
|
||||
function invertImage(imageDate) {
|
||||
var data = new Uint8ClampedArray(imageDate.data);
|
||||
for (var i = 0; i < data.length; i += 4) {
|
||||
data[i] = 255 - data[i];
|
||||
data[i + 1] = 255 - data[i + 1];
|
||||
data[i + 2] = 255 - data[i + 2];
|
||||
}
|
||||
return {
|
||||
data: data,
|
||||
width: imageDate.width,
|
||||
height: imageDate.height
|
||||
};
|
||||
}
|
||||
|
||||
// Otsu 大津法:穷举 0-255 阈值,求类间方差最大者作为最优全局阈值
|
||||
function otsuThreshold(imageDate) {
|
||||
var data = imageDate.data;
|
||||
|
||||
// 1. 灰度直方图(一次扫描,跳过透明/半透明像素)
|
||||
// 透明区域在二值化时按白处理(见 Pixel),不影响检测;
|
||||
// 但统计时计入会拉偏阈值,故只统计真实可见像素
|
||||
var hist = new Array(256);
|
||||
for (var i = 0; i < 256; i++) hist[i] = 0;
|
||||
var total = 0;
|
||||
for (var i = 0; i < data.length; i += 4) {
|
||||
if (data[i + 3] < 128) continue;
|
||||
var gray = (data[i] * 33.33 + data[i + 1] * 33.33 + data[i + 2] * 33.33) / 100 | 0;
|
||||
hist[gray]++;
|
||||
total++;
|
||||
}
|
||||
|
||||
// 2. 遍历阈值,找类间方差最大的
|
||||
var sum = 0; // 全局灰度加权和
|
||||
for (var i = 0; i < 256; i++) sum += i * hist[i];
|
||||
var sumB = 0; // 背景类灰度加权和
|
||||
var wB = 0; // 背景类像素数
|
||||
var maxVar = -1;
|
||||
var bestT = DEFAULT_THRESHOLD;
|
||||
for (var t = 0; t < 256; t++) {
|
||||
wB += hist[t];
|
||||
if (wB === 0) continue;
|
||||
var wF = total - wB;
|
||||
if (wF === 0) break;
|
||||
sumB += t * hist[t];
|
||||
var mB = sumB / wB; // 背景类均值
|
||||
var mF = (sum - sumB) / wF; // 前景类均值
|
||||
var between = wB * wF * (mB - mF) * (mB - mF); // 类间方差
|
||||
// 用 >= 在方差平坦时取最亮侧;阈值取两类均值中点,
|
||||
// 避免双离散值图(类间方差平台)时阈值卡在类边界导致模块误判为白
|
||||
if (between >= maxVar) {
|
||||
maxVar = between;
|
||||
bestT = Math.floor((mB + mF) / 2);
|
||||
}
|
||||
}
|
||||
return bestT;
|
||||
}
|
||||
|
||||
// Otsu 策略:复用原图数据,仅提供自动计算的阈值(由 process 的 threshold 参数消费)
|
||||
function otsuStrategy(imageDate) {
|
||||
return {
|
||||
data: imageDate.data,
|
||||
width: imageDate.width,
|
||||
height: imageDate.height,
|
||||
threshold: otsuThreshold(imageDate)
|
||||
};
|
||||
}
|
||||
|
||||
// 反转 + Otsu 策略:先反转,再对反转后的图计算 Otsu 阈值
|
||||
// 覆盖“深色不够深、浅色不够浅”的偏暗反色二维码(固定阈值反转覆盖不了)
|
||||
function invertedOtsuStrategy(imageDate) {
|
||||
var inv = invertImage(imageDate);
|
||||
return {
|
||||
data: inv.data,
|
||||
width: inv.width,
|
||||
height: inv.height,
|
||||
threshold: otsuThreshold(inv)
|
||||
};
|
||||
}
|
||||
function decode(imageDate, debugfn) {
|
||||
var lastErr;
|
||||
for (var i = 0; i < preprocessStrategies.length; i++) {
|
||||
var img = preprocessStrategies[i] ? preprocessStrategies[i](imageDate) : imageDate;
|
||||
var base = {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
debugfn: debugfn
|
||||
};
|
||||
// 支持多阈值:threshold 为数组时依次尝试,任一成功即返回
|
||||
var thresholds = img.threshold;
|
||||
if (!(thresholds instanceof Array)) {
|
||||
thresholds = [thresholds];
|
||||
}
|
||||
for (var j = 0; j < thresholds.length; j++) {
|
||||
try {
|
||||
return process(img.data, base, thresholds[j]);
|
||||
} catch (e) {
|
||||
lastErr = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastErr;
|
||||
}
|
||||
|
||||
// ECI 声明中常见的字符集(QR 规范:ECI 编号 -> 字符集)
|
||||
@@ -2562,11 +2695,11 @@
|
||||
// 4. 兜底默认 UTF-8
|
||||
return decodeUTF8(bytes);
|
||||
};
|
||||
var process = function process(data, base) {
|
||||
var process = function process(data, base, threshold) {
|
||||
// var start = new Date().getTime();
|
||||
// var image = grayScaleToBitmap(grayscale(),base);
|
||||
// var image = binarize(128);
|
||||
var image = binarize(data, base, 153); //转为位图;
|
||||
var image = binarize(data, base, threshold || DEFAULT_THRESHOLD); //转为位图;
|
||||
|
||||
// 位图转为QR矩阵
|
||||
var detector = new Detector_1(image, base);
|
||||
|
||||
Reference in New Issue
Block a user