sauvolaBinarize static method
Sauvola's Local Adaptive Binarization algorithm for document OCR & barcode decoding.
Formula: T(x,y) = m(x,y) * 1 + k * (s(x,y)/R - 1)
where m = local mean, s = local std dev, k = 0.2, R = 128.
Implementation
static Uint8List sauvolaBinarize(
Uint8List gray,
int width,
int height, {
int windowSize = 15,
double k = 0.2,
double r = 128.0,
}) {
final binarized = Uint8List(width * height);
final halfWindow = windowSize ~/ 2;
// Build Integral Image and Integral Squared Image for fast local box sums
final intW = width + 1;
final intH = height + 1;
final integral = Float64List(intW * intH);
final integralSq = Float64List(intW * intH);
for (int y = 0; y < height; y++) {
double rowSum = 0.0;
double rowSumSq = 0.0;
for (int x = 0; x < width; x++) {
final val = gray[y * width + x].toDouble();
rowSum += val;
rowSumSq += val * val;
final idx = (y + 1) * intW + (x + 1);
final topIdx = y * intW + (x + 1);
integral[idx] = integral[topIdx] + rowSum;
integralSq[idx] = integralSq[topIdx] + rowSumSq;
}
}
for (int y = 0; y < height; y++) {
final y0 = math.max(0, y - halfWindow);
final y1 = math.min(height - 1, y + halfWindow);
for (int x = 0; x < width; x++) {
final x0 = math.max(0, x - halfWindow);
final x1 = math.min(width - 1, x + halfWindow);
final area = (x1 - x0 + 1) * (y1 - y0 + 1);
// Box query on integral images
final i00 = y0 * intW + x0;
final i01 = y0 * intW + (x1 + 1);
final i10 = (y1 + 1) * intW + x0;
final i11 = (y1 + 1) * intW + (x1 + 1);
final sum = integral[i11] - integral[i01] - integral[i10] + integral[i00];
final sumSq = integralSq[i11] - integralSq[i01] - integralSq[i10] + integralSq[i00];
final mean = sum / area;
final variance = math.max(0.0, (sumSq / area) - (mean * mean));
final stdDev = math.sqrt(variance);
final threshold = mean * (1.0 + k * ((stdDev / r) - 1.0));
final pixel = gray[y * width + x];
binarized[y * width + x] = pixel >= threshold ? 255 : 0;
}
}
return binarized;
}