Sisteme de supraveghere AI: confidențialitate-în primul rând cu computer Vision
2020 a transformat examenul de facultate într-o problemă de inginerie. Odată cu pandemia, milioane de studenți s-au trezit să susțină teste de competență de acasă și din instituții școlile au trebuit să răspundă rapid cu sisteme digitale de supraveghere. Rezultatul a fost o explozie a pieței Proctoring AI, dar și o conștientizare tot mai mare a implicațiilor etice și juridice ale acestor sisteme.
În 2025, disciplina a atins o maturitate critică. THE'Legea UE AI, a intrat pe deplin în vigoare, clasifică sistemele biometrice din educație drept sisteme publicitare risc ridicat, impunând o transparență strictă, documentare și supraveghere umană. The GDPR adaugă constrângeri suplimentare privind prelucrarea datelor biometrie. Platformele care ignoră aceste cerințe riscă amenzi de până la 4%. cifra de afaceri globala anuala.
În această lucrare, construim un sistem de supraveghere AI care pornește de la confidențialitate ca principiu fondator, nu ca după gândire. Vom vedea cum să implementăm detectarea feței, privirea urmărirea și detectarea anomaliilor menținând în același timp datele biometrice procesate local, cu minimizarea colectării și conformitatea totală cu GDPR.
Ce vei învăța
- Arhitectură de confidențialitate: procesare pe dispozitiv vs procesare în cloud
- Detectarea feței cu MediaPipe FaceMesh: 468 de repere în timp real
- Urmărirea privirii: estimarea direcției privirii de la camerele web standard
- Detectarea anomaliilor pentru comportament suspect: audio, comutare filă, cu mai multe fețe
- Conformitatea GDPR: minimizarea datelor, consimțământ, politici de păstrare
- Supravegherea umană: sistem de semnalizare și evaluare umană obligatorie
1. Principiile de proiectare în primul rând confidențialitate
Un sistem de supraveghere a confidențialității inversează tradiționalul „strânge totul, filtrează mai târziu”. Principiile fundamentale sunt:
- Minimizarea datelor: Nu colectați videoclipuri continue. Numai înregistrare evenimente anormale cu un tampon de timp limitat (de exemplu, ±10 secunde de la anomalie).
- Procesare pe dispozitiv: Procesați datele biometrice în browser cu WebAssembly/JavaScript. Serverul nu vede niciodată fluxul video brut, ci doar metadate anonimizarea evenimentelor.
- Pseudoanonimizare: Folosiți hashuri ireversibile pentru ID-urile studenților în sistemul de supraveghere, separat de identitatea înregistrată.
- Limitarea scopului: datele colectate pot fi folosite doar pentru sesiunea specifică de examen, cu anulare automată după 30 de zile.
- Supraveghere umană obligatorie: nu există decizii automate obligatorii. AI semnalează anomalii, un om decide consecințele.
// Architettura Privacy-First: on-device processing con MediaPipe
// Nessun dato biometrico trasmesso al server - solo eventi anonimizzati
import * as faceMesh from '@mediapipe/face_mesh';
import * as camera from '@mediapipe/camera_utils';
interface ProctoringEvent {
type: 'gaze_away' | 'face_missing' | 'multiple_faces' | 'tab_switch' | 'audio_anomaly';
timestamp: number;
duration?: number; // ms di persistenza dell'anomalia
confidence: number; // 0-1
// NESSUN dato biometrico raw - solo metadata
}
interface ProctoringSession {
sessionId: string; // Pseudoanonimo - hash dell'exam_id + student_hash
startTime: number;
events: ProctoringEvent[];
snapshotCount: number; // Solo contatore, no immagini
}
class PrivacyFirstProctor {
private faceMeshInstance: faceMesh.FaceMesh | null = null;
private session: ProctoringSession;
private gazeTracker: GazeTracker;
private audioAnalyzer: AudioAnomalyDetector;
private eventBuffer: ProctoringEvent[] = [];
// Soglie configurabili per bilanciare falsi positivi/negativi
private readonly CONFIG = {
GAZE_AWAY_THRESHOLD_DEG: 30, // Gradi di deviazione per "gaze away"
GAZE_AWAY_DURATION_MS: 3000, // 3s continuativi per flaggare
FACE_MISSING_DURATION_MS: 2000, // 2s senza volto per flaggare
MAX_EVENTS_PER_SESSION: 100, // Limite per GDPR data minimization
SNAPSHOT_BUFFER_SEC: 10, // Buffer ±10s per snapshot anomalia
};
constructor(private readonly sessionHash: string) {
this.session = {
sessionId: sessionHash,
startTime: Date.now(),
events: [],
snapshotCount: 0
};
this.gazeTracker = new GazeTracker(this.CONFIG.GAZE_AWAY_THRESHOLD_DEG);
this.audioAnalyzer = new AudioAnomalyDetector();
}
async initialize(videoElement: HTMLVideoElement): Promise<void> {
// Inizializza MediaPipe FaceMesh - processing LOCALE nel browser
this.faceMeshInstance = new faceMesh.FaceMesh({
locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`
});
this.faceMeshInstance.setOptions({
maxNumFaces: 3, // Rileva fino a 3 volti per multi-face detection
refineLandmarks: true, // Landmark oculari precisi per gaze tracking
minDetectionConfidence: 0.7,
minTrackingConfidence: 0.7
});
this.faceMeshInstance.onResults((results) => {
this.processResults(results);
});
// Setup camera
const cameraInstance = new camera.Camera(videoElement, {
onFrame: async () => {
await this.faceMeshInstance!.send({ image: videoElement });
},
width: 640,
height: 480
});
await cameraInstance.start();
// Setup visibility API per tab switching
this.setupTabSwitchDetection();
// Setup audio monitoring (opzionale, richiede consenso separato)
await this.audioAnalyzer.initialize();
}
private processResults(results: faceMesh.Results): void {
const landmarks = results.multiFaceLandmarks;
const faceCount = landmarks?.length || 0;
// Anomalia: nessun volto rilevato
if (faceCount === 0) {
this.recordEvent({
type: 'face_missing',
timestamp: Date.now(),
confidence: 0.9
});
return;
}
// Anomalia: più di un volto (possibile persona che aiuta)
if (faceCount > 1) {
this.recordEvent({
type: 'multiple_faces',
timestamp: Date.now(),
confidence: Math.min(0.6 + faceCount * 0.1, 0.95)
});
}
// Analisi gaze per il volto principale
const primaryFace = landmarks[0];
const gazeResult = this.gazeTracker.analyze(primaryFace);
if (gazeResult.isLookingAway) {
this.recordEvent({
type: 'gaze_away',
timestamp: Date.now(),
confidence: gazeResult.confidence,
// NON salvare la direzione precisa dello sguardo - solo il flag
});
}
}
private recordEvent(event: ProctoringEvent): void {
// GDPR: limit massimo eventi per minimizzazione dati
if (this.session.events.length >= this.CONFIG.MAX_EVENTS_PER_SESSION) {
console.warn('Max events per session reached - data minimization applied');
return;
}
this.session.events.push(event);
}
private setupTabSwitchDetection(): void {
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.recordEvent({
type: 'tab_switch',
timestamp: Date.now(),
confidence: 1.0 // Deterministica
});
}
});
// Previeni apertura DevTools (limitata efficacia, ma segnale utile)
window.addEventListener('resize', () => {
const threshold = 160;
if (
window.outerWidth - window.innerWidth > threshold ||
window.outerHeight - window.innerHeight > threshold
) {
this.recordEvent({
type: 'tab_switch', // Approssimazione per DevTools
timestamp: Date.now(),
confidence: 0.5
});
}
});
}
getReport(): ProctoringReport {
return ProctoringReportGenerator.generate(this.session);
}
}
2. Urmărirea privirii cu MediaPipe FaceMesh
Urmărirea privirii camerei web standard (fără hardware specializat) estimează direcția a privirii prin analiza pozitiei pupilei fata de marginea ochiului. MediaPipe FaceMesh oferă 468 de repere faciale, inclusiv 71 de repere pentru fiecare ochi cu suficientă precizie pentru a detecta abaterile semnificative.
// Indici landmark MediaPipe FaceMesh per gli occhi
// Documentazione: https://google.github.io/mediapipe/solutions/face_mesh.html
const FACE_MESH_INDICES = {
// Occhio sinistro
LEFT_EYE: {
OUTER: 33, // Angolo esterno
INNER: 133, // Angolo interno
TOP: 159, // Palpebra superiore
BOTTOM: 145, // Palpebra inferiore
IRIS_CENTER: 468, // Centro iride (solo con refineLandmarks: true)
},
// Occhio destro
RIGHT_EYE: {
OUTER: 362,
INNER: 263,
TOP: 386,
BOTTOM: 374,
IRIS_CENTER: 473,
},
// Naso (per orientamento testa)
NOSE_TIP: 4,
LEFT_CHEEK: 234,
RIGHT_CHEEK: 454
};
interface GazeResult {
isLookingAway: boolean;
horizontalAngle: number; // Gradi: negativo=sinistra, positivo=destra
verticalAngle: number; // Gradi: negativo=basso, positivo=alto
confidence: number;
}
class GazeTracker {
private gazeAwayStartTime: number | null = null;
private readonly AWAY_THRESHOLD_DEG: number;
private readonly AWAY_DURATION_MS = 3000;
constructor(awayThresholdDeg: number = 30) {
this.AWAY_THRESHOLD_DEG = awayThresholdDeg;
}
analyze(landmarks: faceMesh.NormalizedLandmarkList): GazeResult {
// Stima orientamento della testa tramite landmark
const headPose = this.estimateHeadPose(landmarks);
// Stima posizione iride relativa all'occhio
const leftGaze = this.estimateEyeGaze(
landmarks,
FACE_MESH_INDICES.LEFT_EYE,
FACE_MESH_INDICES.LEFT_EYE.IRIS_CENTER
);
const rightGaze = this.estimateEyeGaze(
landmarks,
FACE_MESH_INDICES.RIGHT_EYE,
FACE_MESH_INDICES.RIGHT_EYE.IRIS_CENTER
);
// Media pesata occhio sinistro e destro
const combinedHorizontal = (leftGaze.horizontal + rightGaze.horizontal) / 2;
const combinedVertical = (leftGaze.vertical + rightGaze.vertical) / 2;
// Combina con head pose
const totalHorizontal = combinedHorizontal + headPose.yaw;
const totalVertical = combinedVertical + headPose.pitch;
const isAway = (
Math.abs(totalHorizontal) > this.AWAY_THRESHOLD_DEG ||
Math.abs(totalVertical) > this.AWAY_THRESHOLD_DEG
);
// Timing per durata
if (isAway && this.gazeAwayStartTime === null) {
this.gazeAwayStartTime = Date.now();
} else if (!isAway) {
this.gazeAwayStartTime = null;
}
const isPersistentlyAway = this.gazeAwayStartTime !== null &&
(Date.now() - this.gazeAwayStartTime) > this.AWAY_DURATION_MS;
return {
isLookingAway: isPersistentlyAway,
horizontalAngle: totalHorizontal,
verticalAngle: totalVertical,
confidence: 0.7 // Stima conservativa per webcam standard
};
}
private estimateEyeGaze(
landmarks: faceMesh.NormalizedLandmarkList,
eyeIndices: typeof FACE_MESH_INDICES.LEFT_EYE,
irisIndex: number
): { horizontal: number; vertical: number } {
const outer = landmarks[eyeIndices.OUTER];
const inner = landmarks[eyeIndices.INNER];
const top = landmarks[eyeIndices.TOP];
const bottom = landmarks[eyeIndices.BOTTOM];
const iris = landmarks[irisIndex];
if (!iris) {
// refineLandmarks disabilitato o iris non rilevata
return { horizontal: 0, vertical: 0 };
}
// Centro geometrico dell'occhio
const eyeCenterX = (outer.x + inner.x) / 2;
const eyeCenterY = (top.y + bottom.y) / 2;
// Dimensioni dell'occhio
const eyeWidth = Math.abs(outer.x - inner.x);
const eyeHeight = Math.abs(top.y - bottom.y);
// Offset normalizzato dell'iride rispetto al centro
const normalizedX = (iris.x - eyeCenterX) / (eyeWidth / 2 + 1e-6);
const normalizedY = (iris.y - eyeCenterY) / (eyeHeight / 2 + 1e-6);
// Converti in gradi approssimativi
return {
horizontal: normalizedX * 45, // Max ~45 gradi
vertical: normalizedY * 30 // Max ~30 gradi verticali
};
}
private estimateHeadPose(
landmarks: faceMesh.NormalizedLandmarkList
): { yaw: number; pitch: number } {
const noseTip = landmarks[FACE_MESH_INDICES.NOSE_TIP];
const leftCheek = landmarks[FACE_MESH_INDICES.LEFT_CHEEK];
const rightCheek = landmarks[FACE_MESH_INDICES.RIGHT_CHEEK];
// Yaw: asimmetria sinistra/destra delle guance
const cheekMidX = (leftCheek.x + rightCheek.x) / 2;
const cheekWidth = Math.abs(rightCheek.x - leftCheek.x);
const yawRatio = (noseTip.x - cheekMidX) / (cheekWidth / 2 + 1e-6);
const yaw = yawRatio * 45;
// Pitch: posizione verticale del naso
const cheekMidY = (leftCheek.y + rightCheek.y) / 2;
const pitchRatio = (noseTip.y - cheekMidY) / 0.1;
const pitch = Math.max(-30, Math.min(30, pitchRatio * 20));
return { yaw, pitch };
}
}
3. Detectarea anomaliilor multimodale
Un sistem robust de supraveghere combină semnale din mai multe surse pentru a reduce false pozitive. Un singur semnal (de exemplu, privirea în altă parte timp de 3 secunde) are un nivel ridicat probabil să fie un comportament inofensiv. Combinația de semnale crește semnificativ eficiența.
interface AnomalyScore {
overall: number; // 0-1
breakdown: {
gaze: number;
face: number;
audio: number;
behavior: number;
};
riskLevel: 'low' | 'medium' | 'high';
requiresHumanReview: boolean;
}
class MultiModalAnomalyDetector {
/**
* Calcola uno score di anomalia composito.
* Nessuna decisione automatica - solo scoring per review umana.
*/
computeRiskScore(session: ProctoringSession): AnomalyScore {
const events = session.events;
const sessionDuration = (Date.now() - session.startTime) / 60000; // minuti
// Normalizza per durata sessione (eventi per 10 minuti)
const normalize = (count: number) => Math.min(count / (sessionDuration / 10 + 1), 1.0);
// Score gaze: ponderato per confidenza degli eventi
const gazeEvents = events.filter(e => e.type === 'gaze_away');
const gazeScore = normalize(
gazeEvents.reduce((sum, e) => sum + e.confidence, 0)
);
// Score face: assenza o volti multipli
const faceEvents = events.filter(e =>
e.type === 'face_missing' || e.type === 'multiple_faces'
);
const faceScore = Math.min(
normalize(faceEvents.length) +
(faceEvents.some(e => e.type === 'multiple_faces') ? 0.3 : 0),
1.0
);
// Score audio: anomalie audio
const audioEvents = events.filter(e => e.type === 'audio_anomaly');
const audioScore = normalize(audioEvents.length);
// Score comportamentale: tab switching
const tabEvents = events.filter(e => e.type === 'tab_switch');
const behaviorScore = Math.min(tabEvents.length * 0.25, 1.0);
// Score overall: media pesata
const overall = (
gazeScore * 0.25 +
faceScore * 0.35 +
audioScore * 0.20 +
behaviorScore * 0.20
);
const riskLevel = overall < 0.3 ? 'low' : overall < 0.6 ? 'medium' : 'high';
return {
overall: Math.round(overall * 100) / 100,
breakdown: {
gaze: Math.round(gazeScore * 100) / 100,
face: Math.round(faceScore * 100) / 100,
audio: Math.round(audioScore * 100) / 100,
behavior: Math.round(behaviorScore * 100) / 100
},
riskLevel,
// GDPR EU AI Act: review umana OBBLIGATORIA per qualunque livello di rischio
requiresHumanReview: riskLevel === 'high' || behaviorScore > 0.5
};
}
}
// Report finale - struttura GDPR-compliant
interface ProctoringReport {
sessionId: string; // Pseudoanonimo
examId: string;
eventCount: number;
anomalyScore: AnomalyScore;
recommendation: 'pass' | 'review_required';
reviewDeadline: Date; // 30 giorni per revisione, poi cancellazione
dataRetentionDate: Date; // Data cancellazione automatica
// NESSUN video, NESSUNA immagine biometrica
}
4. Conformitatea cu GDPR și EU AI Act
Sistemele de supraveghere AI care colectează date biometrice sunt supuse articolului 9 din GDPR (categorii speciale de date) și EU AI Act (sisteme cu risc ridicat în contexte). educațional). Principalele cerințe sunt:
| Cerinţă | GDPR | Legea UE AI | Implementarea |
|---|---|---|---|
| Consimțământ explicit | Articolul 9 alineatul (2) litera (a) | Articolul 13 | Formular de consimțământ înainte de examen |
| Minimizarea datelor | Articolul 5 alineatul (1) litera (c) | Articolul 10 | Numai metadatele evenimentului, fără videoclip continuu |
| Retenție limitată | Articolul 5 alineatul (1) litera (e) | - | Ștergere automată după 30 de zile |
| Supravegherea umană | Articolul 22 | Articolul 14 | Fără decizii automate obligatorii |
| Transparenţă | Articolul 13/14 | Articolul 13 | Informații clare despre ceea ce este detectat |
| DPIA obligatoriu | Articolul 35 | - | Evaluarea impactului înainte de desfășurare |
// Implementazione consenso GDPR-compliant
// Il sistema non si avvia senza consenso esplicito e registrato
interface ConsentRecord {
studentHash: string; // Hash irreversibile dell'ID studente
examId: string;
timestamp: number;
consentVersion: string; // Versione dell'informativa
dataProcessed: string[]; // Lista esplicita di dati raccolti
retentionDays: number;
signature: string; // Hash consenso per audit
}
class GDPRConsentManager {
private static readonly CONSENT_VERSION = '2025-v2';
private static readonly DEFAULT_RETENTION_DAYS = 30;
async requestConsent(
studentId: string,
examId: string
): Promise<ConsentRecord | null> {
const consentData = {
dataProcessed: [
'Rilevamento presenza volto (si/no)',
'Direzione sguardo (deviazione significativa si/no)',
'Numero di volti rilevati',
'Cambio scheda/finestra (si/no)',
'Anomalie audio (si/no)'
],
notProcessed: [
'Video continuo della sessione',
'Riconoscimento facciale o identificazione biometrica',
'Screenshot dello schermo',
'Contenuto audio'
],
retentionDays: this.constructor.DEFAULT_RETENTION_DAYS,
canWithdraw: true,
consequenceOfWithdrawal: 'Impossibilita di sostenere l\'esame in modalità remota'
};
// Mostra UI consenso e attende risposta (implementazione UI omessa)
const accepted = await this.showConsentDialog(consentData);
if (!accepted) return null;
const studentHash = await this.hashStudentId(studentId);
const consentRecord: ConsentRecord = {
studentHash,
examId,
timestamp: Date.now(),
consentVersion: GDPRConsentManager.CONSENT_VERSION,
dataProcessed: consentData.dataProcessed,
retentionDays: consentData.retentionDays,
signature: await this.computeConsentSignature(studentHash, examId)
};
// Persistenza del consenso per audit trail
await this.persistConsent(consentRecord);
return consentRecord;
}
private async hashStudentId(studentId: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(studentId + process.env.PROCTORING_SALT);
const hash = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
async scheduleDataDeletion(consentRecord: ConsentRecord): Promise<void> {
const deleteAt = new Date(
consentRecord.timestamp + consentRecord.retentionDays * 86400 * 1000
);
// Schedule deletion job (implementazione dipende dall'infrastruttura)
await scheduleJob({
type: 'delete_proctoring_data',
sessionId: consentRecord.studentHash + '_' + consentRecord.examId,
executeAt: deleteAt.toISOString()
});
}
}
Avertisment: părtinire în sistemele de supraveghere AI
Cercetările de la MIT (2019) și mai târziu au arătat că sistemele de recunoaștere facial arată rate de eroare semnificativ mai mari pentru persoanele cu piele întunecată, femei și indivizi cu ochelari. Un sistem de supraveghere trebuie testat pe seturi de date reprezentative din punct de vedere demografic și să includă în monitorizare indicatori de echitate continuu. False pozitive sistematic mai mari pentru anumite grupuri constituie a discriminarea indirectă în temeiul Legii UE privind IA.
5. Interfață de revizuire umană
Principiul fundamental al Actului UE AI pentru sistemele cu risc ridicat este că deciziile care indivizii cu impact semnificativ necesită o supraveghere umană semnificativă. Interfața de revizuire este o componentă critică a sistemului.
// API endpoint per il pannello di review dei docenti
// Solo chi ha role 'exam_reviewer' accede ai report
import express from 'express';
import { authenticate, requireRole } from './auth';
const router = express.Router();
// GET /api/proctoring/review/:examId
router.get('/review/:examId',
authenticate,
requireRole('exam_reviewer'),
async (req, res) => {
const { examId } = req.params;
// Solo sessioni con flag HIGH o che richiedono review
const flaggedSessions = await db.query(`
SELECT
session_id,
overall_score,
risk_level,
event_count,
gaze_score,
face_score,
behavior_score,
created_at,
review_deadline
FROM proctoring_sessions
WHERE exam_id = $1
AND (risk_level = 'high' OR requires_human_review = true)
AND reviewed_by IS NULL
ORDER BY overall_score DESC
`, [examId]);
res.json({
examId,
pendingReviews: flaggedSessions.rows.length,
sessions: flaggedSessions.rows,
reviewGuidelines: {
highRisk: 'Score > 0.6: valuta attentamente tutti gli eventi',
mediumRisk: 'Score 0.3-0.6: verifica pattern di tab switching e multi-face',
note: 'Lo score AI e indicativo. La decisione finale spetta al revisore umano.'
}
});
}
);
// POST /api/proctoring/review/:sessionId/decision
router.post('/review/:sessionId/decision',
authenticate,
requireRole('exam_reviewer'),
async (req, res) => {
const { sessionId } = req.params;
const { decision, notes, reviewerId } = req.body;
// Validation
if (!['pass', 'fail', 'inconclusive'].includes(decision)) {
return res.status(400).json({ error: 'Invalid decision value' });
}
await db.query(`
UPDATE proctoring_sessions
SET
final_decision = $1,
reviewer_notes = $2,
reviewed_by = $3,
reviewed_at = NOW()
WHERE session_id = $4
`, [decision, notes, reviewerId, sessionId]);
// Audit log obbligatorio per EU AI Act
await auditLog.record({
action: 'proctoring_decision',
sessionId,
decision,
reviewerId,
timestamp: new Date().toISOString()
});
res.json({ success: true, decision });
}
);
Concluzii
Un sistem responsabil de supraveghere AI nu este o contradicție în termeni: este posibil construiți instrumente eficiente pentru integritatea academică care respectă demnitatea studenților și respectă legislația în vigoare. Cheia este confidențialitate prin design: procesează datele la nivel local, colectează doar metadate despre evenimente anormale și asigură atâta timp cât o ființă umană are ultimul cuvânt.
Cu Actul UE AI în vigoare, respectarea nu este opțională. Instituţiile care implementarea sistemelor de supraveghere trebuie să completeze DPIA, să documenteze sistemul, instruirea auditorilor și asigurarea capacității de a contesta deciziile.
Articole similare din seria EdTech
- Articolul 00: Arhitectură LMS scalabilă: model multi-chiriași
- Articolul 01: Algoritmi de învățare adaptivă
- Articolul 06: Learning Analytics cu xAPI și Kafka







