Sei interessato ai nostri servizi di consulenza?

1 Clicca nella sezione contatti
2 Compila il form
3 Ti ricontattiamo

Se hai bisogno urgente del nostro intervento puoi contattarci al numero 370 148 9430

RENOR & Partners

I nostri orari
Lun-Ven 9:00AM - 18:PM

Evaluation of the level of Blur by Laplacian Variance

by Simone Renzi / June 15, 2025
Post Image

This post is also available in: Italiano (Italian)

Our App for people with celiac disease and food intolerances, CeliachIA, allows users to instantly check for the presence of gluten in products that are not already database-censored: the user photographs the ingredient list on the label and a fine-tuned computer vision model processes the image, returning the answer in seconds.

During the development phase, we ran into a crucial hurdle: ensuring that the photograph of the ingredients was sharp enough to allow accurate text extraction (OCR) and, consequently, reliable analysis. On the backend, Google Cloud Vision already provides an OCR confidence indicator; by applying appropriate thresholds we could decide whether to continue with the analysis or reject the image for poor quality. However, delegating this control to the cloud comes at an unnecessary cost; we still pay for the invocation of an external service even when the photo is obviously unusable.

Therefore, to minimize costs, it was essential to move quality control to the client. We developed a JavaScript function, based on the Laplacian variance, that evaluates image sharpness in real time: the library can be run directly during camera preview, avoiding sending out-of-focus or blurry shots to the backend. In this way we achieve a double optimization: the user experience improves (the “blurry image” feedback is immediate) and cloud processing costs are significantly reduced.

Measuring the sharpness of an image with the Laplacian variance

The perceptual quality of a photograph is highly dependent on sharpness: an image out of focus or affected by motion blur looks unattractive and, in various application areas (computer vision, diagnostics, mobile photography), can compromise the entire automatic analysis process.

There are numerous techniques for quantitatively estimating the level of blur. In this paper, we present a fast approach, free of external dependencies and suitable for real time, based on the variance of the Laplacian operator. We will start with the theoretical fundamentals and then arrive at a complete implementation in JavaScript, accompanied by a small Web interface for testing purposes.

The ultimate goal will be to build a widget that assigns a score from 1 to 10 to the sharpness of any image uploaded by the user.

Sharpness as a frequency issue

Each digital image can be seen as the sum of components at different spatial frequencies:

  • Low frequencies have slow variations, uniform areas, and smooth gradients
  • High frequencies result in abrupt transitions, edges, fine details.

A blur acts as a low-pass filter, that is, it attenuates high frequencies. So the blurrier an image is, the less residual energy we will have in the high-frequency spectrum.

The project will then work on translating this observation into a single number that is easy to interpret.

The variance of the Laplacian: from millions of pixels to a single number

Once we apply the Laplacian filter to the image, we get a new matrix where each value represents the local change in light intensity. But how can we condense this information into a single numerical value that objectively describes how sharp the image is?

The simplest and most effective answer is: calculate the variance of these values.
Because of what was said before, a well-focused image has many sharp edges and transitions therefore the values of the Laplacian are widely distributed, both positive and negative: there is therefore high variance.
A blurred image, blurs the edges therefore the values of the Laplacian are close to zero and little variable: there is therefore low variance.

The variance measures precisely how far the values deviate from the mean: the larger this deviation, the more detail and sharpness the image contains.

Mathematical definition

Given a Laplacian response matrix $L_i$, where $i$ denotes each valid pixel (excluding the edge), we calculate:

\mu = \frac{1}{N}\sum_{i=1}^{N} L_i, \qquad \sigma^2 = \frac{1}{N}\sum_{i=1}^{N}(L_i - \mu)^2

Where:

  • $\mu$ is the average of the Laplacian values;
  • $N$ is the total number of pixels involved (usually $(w – 2)(h – 2)$);
  • $\sigma^2$ is the variance of the Laplacian, i.e., our sharpness indicator.

Now we need to go from a raw value to an interpretable score.

Values of $\sigma^2$ depend on hardware and environmental variables: resolution, noise, JPEG compression, lens quality. Therefore, it does not make sense to use a fixed threshold: we need to normalize the value against an empirical range.

We define:

  • $T_{min}$: typical variance of a very blurry photo → corresponds to score 1;
  • $T_{max}$: typical variance of a perfectly sharp photo → corresponds to score 10.

Therefore:

\text{score} = 1 + 9 \times \operatorname{clamp}\left(\frac{\sigma^2 - T_{min}}{T_{max} - T_{min}}, 0, 1\right)

After calculation, the value is rounded to the nearest integer to return a score between 1 and 10. This approach also makes the result readable for the end user, as well as useful for software-side control logic.

Implementation in JavaScript

The algorithm can be implemented in pure JavaScript using the Canvas API to access the pixels of an image uploaded or taken by camera. The following function takes as input an HTML element ( <img>, <canvas>, <video> o ImageBitmap) and returns an object with score (1-10) e variance.

/**
 * Evaluate image sharpness using Laplacian variance.
 * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|ImageBitmap} source
 * @param {{thresholdMin?: number, thresholdMax?: number}=} opts
 * @return {Promise<{score: number, variance: number}>}
 */
export async function blurMeter(source, opts = {}) {
  const T_min = opts.thresholdMin ?? 9;    // calibrated: blurry photo
  const T_max = opts.thresholdMax ?? 50;   // calibrated: sharp photo

  // 1. Prepare off-screen canvas
  const w = source.width  || source.videoWidth  || source.naturalWidth;
  const h = source.height || source.videoHeight || source.naturalHeight;
  if (!w || !h) throw new Error("Source has invalid dimensions");

  const off = typeof OffscreenCanvas === "function"
    ? new OffscreenCanvas(w, h)
    : (() => { const c = document.createElement("canvas"); c.width = w; c.height = h; return c; })();
  const ctx = off.getContext("2d");
  ctx.drawImage(source, 0, 0, w, h);
  const { data } = ctx.getImageData(0, 0, w, h);

  // 2. Convert to grayscale
  const gray = new Float32Array(w * h);
  for (let i = 0, j = 0; i < data.length; i += 4, ++j) {
    gray[j] = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
  }

  // 3. Apply Laplacian (4-neighbour kernel)
  const lap = new Float32Array(w * h);
  const idx = (x, y) => y * w + x;
  for (let y = 1; y < h - 1; ++y) {
    for (let x = 1; x < w - 1; ++x) {
      const i = idx(x, y);
      lap[i] = 4 * gray[i] - gray[idx(x - 1, y)] - gray[idx(x + 1, y)] - gray[idx(x, y - 1)] - gray[idx(x, y + 1)];
    }
  }

  // 4. Compute variance
  let sum = 0, sumSq = 0, count = (w - 2) * (h - 2);
  for (let y = 1; y < h - 1; ++y) {
    for (let x = 1; x < w - 1; ++x) {
      const v = lap[idx(x, y)];
      sum += v;
      sumSq += v * v;
    }
  }
  const mean = sum / count;
  const variance = (sumSq / count) - (mean * mean);

  // 5. Map to score
  const norm = Math.max(0, Math.min(1, (variance - T_min) / (T_max - T_min)));
  const score = Math.round(1 + 9 * norm);

  return { score, variance };
}

This function is lightweight, self-contained, and can be performed directly client-side, such as in a live camera web application or image analysis.

Next step: we create a simple interface with HTML and Tailwind to use this function interactively.

HTML + Tailwind interface

We now consume the function in a simple user interface made in HTML and Tailwind that allows the user to load an image, preview it, and immediately receive a sharpness score with a colored bar from red (blurry) to green (sharp).

<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8">
  <title>Blur Meter Demo</title>
  <!-- Tailwind CSS via CDN -->
  <script src="https://cdn.tailwindcss.com"></script>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body class="min-h-screen bg-slate-100 flex items-center justify-center p-4">
  <div class="w-full max-w-lg bg-white shadow-xl rounded-2xl p-6 space-y-6">
    <h1 class="text-2xl font-semibold text-center">Image Blur Meter</h1>

    <!-- File input -->
    <label class="flex flex-col items-center justify-center w-full h-40 px-4 transition bg-white border-2 border-dashed rounded-lg cursor-pointer hover:border-indigo-500">
      <span class="text-sm text-gray-500">Click here to upload an image</span>
      <input id="uploader" type="file" accept="image/*" class="hidden">
    </label>

    <!-- Image preview -->
    <div id="previewWrapper" class="hidden">
      <img id="preview" alt="preview" class="mx-auto max-h-64 rounded-lg shadow-md"/>
    </div>

    <!-- Results -->
    <div id="result" class="hidden space-y-3">
      <p id="scoreText" class="text-center text-lg font-medium"></p>

      <!-- Progress bar -->
      <div class="w-full bg-gray-200 rounded-full h-4">
        <div id="scoreBar" class="h-4 rounded-full transition-all duration-500" style="width:0%"></div>
      </div>

      <p id="varianceText" class="text-center text-sm text-gray-500"></p>
    </div>
  </div>

  <script type="module">
    import { blurMeter } from './js/blur-meter.js';

    const uploader      = document.getElementById('uploader');
    const preview       = document.getElementById('preview');
    const previewWrap   = document.getElementById('previewWrapper');
    const scoreText     = document.getElementById('scoreText');
    const varianceText  = document.getElementById('varianceText');
    const scoreBar      = document.getElementById('scoreBar');
    const resultBlock   = document.getElementById('result');

    const scoreToColor = score => {
      const t = (score - 1) / 9;
      const r = Math.round(220 + (22  - 220) * t);
      const g = Math.round( 38 + (163 -  38) * t);
      const b = Math.round( 38 + ( 74 -  38) * t);
      return `rgb(${r},${g},${b})`;
    };

    uploader.addEventListener('change', async e => {
      const file = e.target.files[0];
      if (!file) return;

      const url = URL.createObjectURL(file);
      preview.src = url;
      previewWrap.classList.remove('hidden');

      await new Promise(res => preview.onload = res);

      const { score, variance } = await blurMeter(preview);

      scoreText.textContent    = `Sharpness Score: ${score}/10`;
      varianceText.textContent = `Laplacian variance: ${variance.toFixed(2)}`;
      const percent            = ((score - 1) / 9) * 100;
      scoreBar.style.width     = percent + '%';
      scoreBar.style.backgroundColor = scoreToColor(score);
      resultBlock.classList.remove('hidden');

      URL.revokeObjectURL(url);
    });
  </script>
</body>
</html>

Functional testing

We tested the function on three different types of image…

Completely out of focus

Moderately blurred

Sharp

Conclusion

The Laplacian variance proves to be a simple but extremely powerful and effective tool for assessing the sharpness of an image in real-time client-side contexts. Its strength lies in its computational speed, absence of external dependencies and consistency with human visual perception.

In our use case, applied to the CeliachIA App, this metric allowed us to anticipate quality control directly on the user’s device, avoiding unnecessary costs of cloud invocations when images are not suitable for OCR analysis.

The pipeline built with JavaScript and Canvas API, coupled with a simple Web interface developed with Tailwind, is a concredo example of how mathematical concepts and engineering tools can come together in a robust, cost-effective, and user-friendly solution.

Ultimately, having an automated sharpness scoring system is not just a technical quirk, but an essential component of ensuring quality, efficiency, and sustainability in modern image analysis pipelines.

You can download this tool from my GitHub account at the link: https://github.com/thesimon82/Laplacian-Blur-Detector/

Simone Renzi
Seguimi

Scegli un'area

CONTATTACI

Ti risponderemo entro 24 ore

TORNA SU