MVP
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user