sauvolaBinarize static method

Uint8List sauvolaBinarize(
  1. Uint8List gray,
  2. int width,
  3. int height, {
  4. int windowSize = 15,
  5. double k = 0.2,
  6. double r = 128.0,
})

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;
}