Files
boc/quixzoom-capture-pipeline/video-to-observation/cloud-vision.js
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

181 lines
4.9 KiB
JavaScript

/**
* QUIXZOOM Cloud Vision Integration
*
* Använder Google Cloud Vision API för objektdetektering.
* Fallback till mock om API-nyckel saknas.
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
class CloudVisionDetector {
constructor(config = {}) {
this.config = {
apiKey: config.apiKey || process.env.GOOGLE_VISION_API_KEY,
maxResults: config.maxResults || 50,
...config,
};
this.available = !!this.config.apiKey;
this.classMap = this.buildClassMap();
}
buildClassMap() {
return {
'Street light': 'street_lamp',
'Traffic sign': 'traffic_sign',
'Tree': 'tree',
'Manhole cover': 'manhole',
'Utility pole': 'utility_box',
'Bench': 'bench',
'Trash can': 'trash_can',
'Bicycle rack': 'bicycle_rack',
'Parking meter': 'utility_box',
'Fire hydrant': 'utility_box',
'Light': 'street_lamp',
'Pole': 'street_lamp',
'Sign': 'traffic_sign',
'Plant': 'tree',
};
}
async detect(imagePath) {
if (!this.available) {
console.log('[Cloud Vision] No API key, using mock');
return this.mockDetect(imagePath);
}
try {
const imageData = fs.readFileSync(imagePath);
const base64Image = imageData.toString('base64');
const requestBody = {
requests: [{
image: { content: base64Image },
features: [
{ type: 'OBJECT_LOCALIZATION', maxResults: this.config.maxResults },
{ type: 'LABEL_DETECTION', maxResults: 20 },
],
}],
};
const response = execSync('curl -s -X POST "https://vision.googleapis.com/v1/images:annotate?key=' + this.config.apiKey + '" -H "Content-Type: application/json" -d \'' + JSON.stringify(requestBody) + '\'', {
encoding: 'utf-8',
timeout: 30000,
});
const result = JSON.parse(response);
return this.parseResult(result, imagePath);
} catch (error) {
console.warn(`[Cloud Vision] Error: ${error.message}`);
return this.mockDetect(imagePath);
}
}
parseResult(result, imagePath) {
const objects = [];
if (!result.responses || !result.responses[0]) {
return objects;
}
const response = result.responses[0];
// Object localization
if (response.localizedObjectAnnotations) {
for (const obj of response.localizedObjectAnnotations) {
const quixType = this.classMap[obj.name];
if (!quixType) continue;
objects.push({
type: quixType,
confidence: obj.score,
bbox: this.normalizeBoundingBox(obj.boundingPoly),
name: obj.name,
});
}
}
// Label detection (fallback)
if (objects.length === 0 && response.labelAnnotations) {
for (const label of response.labelAnnotations) {
const quixType = this.classMap[label.description];
if (!quixType) continue;
objects.push({
type: quixType,
confidence: label.score,
name: label.description,
});
}
}
return objects;
}
normalizeBoundingBox(poly) {
if (!poly || !poly.normalizedVertices) return null;
const vertices = poly.normalizedVertices;
const xs = vertices.map(v => v.x || 0);
const ys = vertices.map(v => v.y || 0);
return {
x: Math.min(...xs),
y: Math.min(...ys),
width: Math.max(...xs) - Math.min(...xs),
height: Math.max(...ys) - Math.min(...ys),
};
}
mockDetect(imagePath) {
// Samma mock som YOLO men med mer varierande confidence
const frameNum = parseInt(path.basename(imagePath).match(/\d+/)?.[0] || '0');
const objects = [];
const templates = [
{ type: 'street_lamp', prob: 0.6, confRange: [0.65, 0.95] },
{ type: 'traffic_sign', prob: 0.3, confRange: [0.55, 0.85] },
{ type: 'tree', prob: 0.5, confRange: [0.60, 0.90] },
{ type: 'utility_box', prob: 0.2, confRange: [0.50, 0.80] },
{ type: 'manhole', prob: 0.25, confRange: [0.45, 0.75] },
];
const numObjects = 1 + Math.floor(Math.random() * 3);
for (let i = 0; i < numObjects; i++) {
const template = templates[Math.floor(Math.random() * templates.length)];
if (Math.random() < template.prob) {
const confidence = template.confRange[0] + Math.random() * (template.confRange[1] - template.confRange[0]);
objects.push({
type: template.type,
confidence: confidence,
source: 'mock',
});
}
}
return objects;
}
async detectBatch(imagePaths) {
const results = [];
for (const path of imagePaths) {
const detections = await this.detect(path);
results.push({
image: path,
objects: detections,
});
}
return results;
}
}
module.exports = CloudVisionDetector;