Se hai bisogno urgente del nostro intervento puoi contattarci al numero 370 148 9430
RENOR & Partners è una società di consulenza d'impresa con alti standard qualitativi e con i prezzi più bassi del mercato.
RENOR & Partners S.r.l.
Via Cicerone, 15 - Ariccia (RM)
info@renor.it
This post is also available in:
English (Inglese)
La nostra App per persone celiache e con intolleranze alimentari, CeliachIA, consente di verificare istantaneamente la presenza di glutine nei prodotti che non risultano già censiti a database: l’utente fotografa la lista ingredienti sull’etichetta e un modello di computer vision fine-tuned elabora l’immagine restituendo la risposta in pochi secondi.
Durante la fase di sviluppo ci siamo imbattuti in un ostacolo cruciale: garantire che la fotografia degli ingredienti fosse sufficientemente nitida da permettere un’accurata estrazione del testo (OCR) e, di conseguenza, un’analisi affidabile. Sul backend, Google Cloud Vision fornisce già un indicatore di confidenza dell’OCR; applicando soglie opportune potremmo decidere se proseguire con l’analisi oppure respingere l’immagine per scarsa qualità. Tuttavia, delegare questo controllo al cloud comporta un costo inutile, paghiamo comunque l’invocazione di un servizio esterno anche quando la foto è palesemente inutilizzabile.
Per ridurre al minimo i costi era quindi indispensabile spostare il controllo qualità sul client. Abbiamo sviluppato una funzione JavaScript, basata sulla varianza del Laplaciano, che valuta in tempo reale la nitidezza dell’immagine: la libreria può essere eseguita direttamente durante l’anteprima della fotocamera, evitando di inviare al backend scatti fuori fuoco o mossi. In questo modo otteniamo una doppia ottimizzazione: l’esperienza utente migliora (il feedback di “immagine sfocata” è immediato) e i costi di elaborazione cloud si riducono sensibilmente.
La qualità percettiva di una fotografia dipende in larga misura dalla nitidezza: un’immagine fuori fuoco o affetta da motion blur appare poco gradevole e, in vari ambiti applicativi (visione artificiale, diagnostica, fotografia mobile), può compromettere l’intero processo di analisi automatica.
Esistono numerose tecniche per stimare quantitativamente il livello di sfocatura. In questo articolo presentiamo un approccio rapido, privo di dipendenze esterne e adatto al tempo reale, basato sulla varianza dell’operatore Laplaciano. Partiremo dai fondamenti teorici per poi giungere a un’implementazione completa in JavaScript, corredata da una piccola interfaccia Web per poter effettuare dei test.
L’obiettivo finale sarà quello di costruire un widget che assegni un punteggio da 1 a 10 alla nitidezza di qualsiasi immagine caricata dall’utente.
Ogni immagine digitale può essere vista come la somma di componenti a diverse frequenze spaziali:
Una sfuocatura agisce come filtro passa-basso ovvero attenua le alte frequenze. Quindi più un’immagine è sfuocata, meno energia residua avremo nello spettro ad alta frequenza.
Il progetto si occuperà quindi di tradurre questa osservazione in un singolo numero facile da interpretare.
Una volta applicato il filtro Laplaciano all’immagine, otteniamo una nuova matrice dove ogni valore rappresenta la variazione locale di intensità luminosa. Ma come possiamo condensare queste informazioni in un singolo valore numerico che descriva in modo oggettivo quanto l’immagine sia nitida?
La risposta più semplice ed efficace è: calcolare la varianza di questi valori.
Per quanto detto prima, un’immagine ben a fuoco presenta molti bordi e transizioni nette quindi i valori del Laplaciano sono ampiamente distribuiti, sia positivi che negativi: c’è quindi alta varianza.
Un’immagine sfocata, attenua i bordi pertanto i valori del Laplaciano sono vicini allo zero e poco variabili: c’è quindi bassa varianza.
La varianza misura proprio quanto i valori si discostino dalla media: più è grande questo scarto, più l’immagine contiene dettagli e risulta nitida.
Data una matrice di risposta Laplaciana , dove
indica ciascun pixel valido (escludendo il bordo), calcoliamo:
dove:
Ora dobbiamo passare da un valore grezzo a uno score interpretabile.
I valori di dipendono da variabili hardware e ambientali: risoluzione, rumore, compressione JPEG, qualità della lente. Perciò non ha senso usare una soglia fissa: occorre normalizzare il valore rispetto a un intervallo empirico.
Definiamo:
Quindi:
Dopo il calcolo, il valore viene arrotondato all’intero più vicino per restituire un punteggio compreso tra 1 e 10. Questo approccio rende il risultato leggibile anche per l’utente finale, oltre che utile per logiche di controllo lato software.
L’algoritmo può essere implementato in JavaScript puro utilizzando le API Canvas per accedere ai pixel di un’immagine caricata o ripresa da fotocamera. La funzione seguente prende in input un elemento HTML (<img>
, <canvas>
, <video>
o ImageBitmap
) e restituisce un oggetto con 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 };
}
Questa funzione è leggera, autonoma e può essere eseguita direttamente lato client, ad esempio in un’applicazione web per fotocamera live o analisi immagini.
Prossimo passo: creiamo un’interfaccia semplice con HTML e Tailwind per usare questa funzione in modo interattivo.
Consumiamo ora la funzione in una semplice interfaccia utente realizzata in HTML e Tailwind che permette all’utente di caricare un’immagine, visualizzarne l’anteprima e ricevere immediatamente uno score di nitidezza con una barra colorata da rosso (sfuocato) a verde (nitido).
<!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>
Abbiamo testato la funzione su tre diverse tipologie di immagine…
La varianza del Laplaciano si conferma uno strumento semplice ma estremamente potente ed efficace per valutare la nitidezza di un’immagine in contesti real-time client-side. La sua forza risiede nella rapidità computazionale, nell’assenza di dipendenze esterne e nella coerenza con la percezione visiva umana.
Nel nostro caso d’uso, applicato all’App CeliachIA, questa metrica ci ha permesso di anticipare il controllo qualità direttamente sul dispositivo dell’utente, evitando costi superflui di invocazioni cloud quando le immagini non sono idonee all’analisi OCR.
La pipeline realizzata con JavaScript e Canvas API, abbinata a una semplice interfaccia web sviluppata con Tailwind, rappresenta un esempio concredo di come concetti matematici e strumenti ingegneristici possano confluire in una soluzione robusta, economica e user-friendly.
In definitiva, dotarsi di un sistema automatico di scoring della nitidezza non è solo un vezzo tecnic, ma una componente essenziale per garantire qualità, efficienza e sostenibilità nelle pipeline di analisi delle immagini moderne.
Potete scaricare questo strumento dal mio account GitHub al link: https://github.com/thesimon82/Laplacian-Blur-Detector/