This commit is contained in:
Carlos Correia
2026-05-29 11:03:29 +01:00
parent 967584f083
commit fee538eebd
14 changed files with 1349 additions and 1149 deletions

View File

@@ -1,204 +1,80 @@
# 👁️ Camada de Visão por IA
# Camada de Imagem e Visão
## Objetivo
## Estado atual
Usar IA de visão computacional para **identificar automaticamente** o que está na foto tirada pelo utilizador, sugerindo:
- Nome do item
- Categoria
- Tags relevantes
O DayMaker guarda imagens dos itens, mas **não faz reconhecimento automático de imagem** no produto atual.
Isto reduz o esforço do utilizador ao mínimo.
A imagem de cada item é tratada como dado visual associado ao inventário. A app espera receber ou guardar URLs em `item_images(image_url)` e usa essas imagens para:
- Mostrar cards no inventário.
- Mostrar detalhes do item.
- Mostrar itens planeados na semana.
- Mostrar sugestões da IA com imagem quando há correspondência com itens reais.
---
## Serviço escolhido: Google Vision API
## Fluxo atual de imagem
### Porquê Google Vision?
- Fácil integração com Firebase (mesmo ecosistema Google)
- Excelente deteção de objetos do quotidiano
- Labels em múltiplos idiomas
- Tier gratuito generoso (1.000 unidades/mês)
- Documentação extensa
### Funcionalidades usadas no MVP
| Feature | Uso |
|---------|-----|
| `LABEL_DETECTION` | Identificar o objeto (ex: "T-shirt", "Laptop") |
| `OBJECT_LOCALIZATION` | Confirmar que existe um objeto na foto |
> No MVP **não** usamos OCR, Safe Search ou outras features avançadas.
---
## Integração técnica
### Chamada à API (exemplo React Native)
```javascript
// services/visionApi.js
const VISION_API_KEY = process.env.GOOGLE_VISION_API_KEY;
const VISION_API_URL = `https://vision.googleapis.com/v1/images:annotate?key=${VISION_API_KEY}`;
export async function analyzeImage(base64Image) {
const requestBody = {
requests: [
{
image: { content: base64Image },
features: [
{ type: 'LABEL_DETECTION', maxResults: 10 },
{ type: 'OBJECT_LOCALIZATION', maxResults: 5 }
]
}
]
};
const response = await fetch(VISION_API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
const data = await response.json();
return parseVisionResponse(data);
}
```
### Parsing da resposta
```javascript
// services/visionApi.js (continuação)
export function parseVisionResponse(data) {
const labels = data.responses[0]?.labelAnnotations || [];
const objects = data.responses[0]?.localizedObjectAnnotations || [];
// Extrair labels com score > 0.7
const highConfidenceLabels = labels
.filter(l => l.score > 0.7)
.map(l => l.description.toLowerCase());
// Mapear para categoria da app
const category = mapLabelsToCategory(highConfidenceLabels);
// Sugerir nome baseado no objeto mais confiante
const suggestedName = objects[0]?.name || labels[0]?.description || 'Item';
return {
suggestedName,
category,
tags: highConfidenceLabels.slice(0, 5),
rawLabels: highConfidenceLabels
};
}
```text
Item criado/editado
Imagem associada ao item
URL guardado em item_images.image_url
Ecrãs carregam items com item_images(image_url)
Image.network mostra a imagem ou fallback por categoria
```
---
## Mapeamento de Labels → Categorias
## Ficheiros relevantes
```javascript
// constants/categoryMapping.js
export const LABEL_TO_CATEGORY = {
// Roupa
't-shirt': 'clothing',
'shirt': 'clothing',
'dress': 'clothing',
'jacket': 'clothing',
'coat': 'clothing',
'jeans': 'clothing',
'trousers': 'clothing',
'sweater': 'clothing',
'clothing': 'clothing',
'fashion': 'clothing',
// Eletrónica
'laptop': 'electronics',
'computer': 'electronics',
'smartphone': 'electronics',
'tablet': 'electronics',
'headphones': 'electronics',
'camera': 'electronics',
'charger': 'electronics',
'cable': 'electronics',
'electronics': 'electronics',
// Calçado
'shoe': 'footwear',
'boot': 'footwear',
'sneaker': 'footwear',
'sandal': 'footwear',
'footwear': 'footwear',
// Acessórios
'bag': 'accessories',
'backpack': 'accessories',
'watch': 'accessories',
'sunglasses': 'accessories',
'hat': 'accessories',
'belt': 'accessories',
// Documentos
'passport': 'documents',
'document': 'documents',
'book': 'documents',
'notebook': 'documents',
};
export function mapLabelsToCategory(labels) {
for (const label of labels) {
if (LABEL_TO_CATEGORY[label]) {
return LABEL_TO_CATEGORY[label];
}
}
return 'other'; // fallback
}
```
| Ficheiro | Responsabilidade |
|----------|------------------|
| `lib/Screens/add_item_screen.dart` | Criação de itens e associação de dados |
| `lib/Screens/item_screen.dart` | Inventário, detalhe, edição e imagem do item |
| `lib/Screens/week_screen.dart` | Mostra imagens dos itens planeados |
| `lib/Screens/home_screen.dart` | Mostra itens recentes, hoje e sugestões IA |
---
## Fluxo de UX ao adicionar item
## Fallback visual
```
1. Utilizador tira foto
2. App mostra loading ("A analisar item...")
3. Google Vision responde com labels
4. App mostra:
- Nome sugerido (editável)
- Categoria sugerida (editável)
- Tags sugeridas (checkboxes, editáveis)
5. Utilizador confirma ou ajusta
6. Item guardado
```
Quando um item não tem imagem ou a imagem falha ao carregar:
> O utilizador tem **sempre** controlo final. A IA apenas sugere.
- A UI mostra um fundo com a cor da categoria.
- A UI mostra o ícone da categoria.
Isto evita cards vazios e mantém consistência visual.
---
## Tratamento de erros
## Reconhecimento automático por IA
| Erro | Comportamento |
|------|---------------|
| API indisponível | Mostrar formulário manual, sem sugestões |
| Foto sem objeto reconhecível | Mostrar sugestão "Outro" e pedir nome manual |
| Score muito baixo (< 0.5) | Ignorar label, não sugerir |
| Timeout | Retry 1x, depois formulário manual |
Reconhecimento automático por imagem **não está implementado**.
Pode ser uma funcionalidade futura para sugerir:
- Nome do item.
- Categoria.
- Tags.
- Cor dominante.
- Tipo de peça ou objeto.
Possíveis serviços futuros:
- Google Vision API.
- Modelos multimodais via API externa.
- Modelo local/servidor próprio.
---
## Evolução futura desta camada
## Regras para futuras implementações
- Fase 2: Treinar modelo custom com os dados dos utilizadores
- Fase 3: Identificar marcas e modelos específicos
- Fase 3: Reconhecimento de cor dominante para filtros de outfit
---
## Notas para o agente IA
- A chave da API (`GOOGLE_VISION_API_KEY`) nunca deve aparecer em código client-side em produção — usar Firebase Cloud Functions como proxy
- No MVP, a chamada pode ser feita diretamente do cliente para simplicidade
- Nunca guardar `rawLabels` como `name` sem confirmação do utilizador
- O mapeamento de labels é iterativo — quando encontrar labels não mapeadas, adicionar ao ficheiro `categoryMapping.js`
- Não guardar API keys privadas no cliente Flutter.
- O utilizador deve poder confirmar/editar qualquer sugestão automática.
- Nunca substituir nome, categoria ou tags sem confirmação.
- Manter fallback manual caso a análise de imagem falhe.
- Atualizar este documento antes de implementar visão computacional.