Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- .dockerignore +31 -0
- Dockerfile +27 -0
- api.py +26 -0
- inteligencia.py +69 -0
- main.py +129 -0
- requirements.txt +0 -0
- yolov8m.pt +3 -0
.dockerignore
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 1. Segurança (O MAIS IMPORTANTE)
|
| 2 |
+
.env
|
| 3 |
+
.git
|
| 4 |
+
.gitignore
|
| 5 |
+
|
| 6 |
+
# 2. Lixo do Python (Compilados locais que quebram no Linux)
|
| 7 |
+
__pycache__
|
| 8 |
+
*.pyc
|
| 9 |
+
*.pyo
|
| 10 |
+
*.pyd
|
| 11 |
+
|
| 12 |
+
# 3. Ambiente Virtual (O erro mais comum)
|
| 13 |
+
# Nunca envie sua pasta venv, o Docker cria o ambiente dele sozinho!
|
| 14 |
+
venv/
|
| 15 |
+
env/
|
| 16 |
+
.venv/
|
| 17 |
+
|
| 18 |
+
# 4. Configurações de IDE (VS Code, Pycharm)
|
| 19 |
+
.vscode/
|
| 20 |
+
.idea/
|
| 21 |
+
|
| 22 |
+
# 5. Arquivos temporários gerados pelo seu código
|
| 23 |
+
# Não precisamos das fotos de teste ou recortes no servidor
|
| 24 |
+
teste/
|
| 25 |
+
recortes/
|
| 26 |
+
temp_envio.jpg
|
| 27 |
+
*.jpg
|
| 28 |
+
*.png
|
| 29 |
+
|
| 30 |
+
# 6. Logs e pastas do YOLO (Ultralytics cria a pasta 'runs')
|
| 31 |
+
runs/
|
Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Usa uma imagem Python leve
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Define o diretório de trabalho
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Instala dependências do sistema (necessário para OpenCV/EasyOCR)
|
| 8 |
+
RUN apt-get update && apt-get install -y \
|
| 9 |
+
libgl1-mesa-glx \
|
| 10 |
+
libglib2.0-0 \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
# Copia os requisitos e instala
|
| 14 |
+
COPY requirements.txt .
|
| 15 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 16 |
+
|
| 17 |
+
# Copia o resto do código
|
| 18 |
+
COPY . .
|
| 19 |
+
|
| 20 |
+
# Cria a pasta de usuário (Hugging Face exige permissão especial)
|
| 21 |
+
RUN useradd -m -u 1000 user
|
| 22 |
+
USER user
|
| 23 |
+
ENV HOME=/home/user \
|
| 24 |
+
PATH=/home/user/.local/bin:$PATH
|
| 25 |
+
|
| 26 |
+
# Comando para rodar na porta 7860 (Padrão do Hugging Face)
|
| 27 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
api.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, UploadFile, File
|
| 2 |
+
import shutil
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
# AQUI ESTÁ O SEGREDO: Importamos sua função pronta!
|
| 6 |
+
from inteligencia import analisar_imagem_agora
|
| 7 |
+
|
| 8 |
+
app = FastAPI()
|
| 9 |
+
|
| 10 |
+
@app.post("/analisar")
|
| 11 |
+
async def api_analisar(arquivo: UploadFile = File(...)):
|
| 12 |
+
# 1. Salva o arquivo que chegou via internet num temp
|
| 13 |
+
nome_temp = f"temp_{arquivo.filename}"
|
| 14 |
+
|
| 15 |
+
with open(nome_temp, "wb") as buffer:
|
| 16 |
+
shutil.copyfileobj(arquivo.file, buffer)
|
| 17 |
+
|
| 18 |
+
# 2. Chama a SUA inteligência que já estava pronta
|
| 19 |
+
# Ela vai fazer o OCR, chamar o Gemini e devolver o resultado
|
| 20 |
+
resultado = analisar_imagem_agora(nome_temp)
|
| 21 |
+
|
| 22 |
+
# 3. Limpa a sujeira (apaga a imagem temp)
|
| 23 |
+
os.remove(nome_temp)
|
| 24 |
+
|
| 25 |
+
# 4. Devolve o resultado para o cliente (main.py ou app)
|
| 26 |
+
return resultado
|
inteligencia.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import easyocr
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import os
|
| 5 |
+
import cv2 # ADICIONE ESTA LINHA
|
| 6 |
+
|
| 7 |
+
# --- CONFIGURAÇÕES (Carregam apenas uma vez) ---
|
| 8 |
+
load_dotenv()
|
| 9 |
+
chave_api = os.getenv("GOOGLE_API_KEY") # <--- RECOLOQUE SUA CHAVE AQUI
|
| 10 |
+
genai.configure(api_key=chave_api)
|
| 11 |
+
model = genai.GenerativeModel('gemini-2.5-flash')
|
| 12 |
+
|
| 13 |
+
print("Inicializando OCR e IA... (Isso acontece só uma vez)")
|
| 14 |
+
# Mantenha gpu=False se não tiver CUDA configurado
|
| 15 |
+
reader = easyocr.Reader(['pt', 'en'], gpu=False)
|
| 16 |
+
|
| 17 |
+
def analisar_imagem_agora(caminho_imagem):
|
| 18 |
+
print(f"\n--- 🧠 INICIANDO ANÁLISE: {caminho_imagem} ---")
|
| 19 |
+
|
| 20 |
+
# 1. OCR (LEITURA) - FORMA MAIS SEGURA
|
| 21 |
+
try:
|
| 22 |
+
print("Executando OCR...")
|
| 23 |
+
# CORREÇÃO: Carregar e converter para escala de cinza
|
| 24 |
+
img = cv2.imread(caminho_imagem)
|
| 25 |
+
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 26 |
+
|
| 27 |
+
resultados_brutos = reader.readtext(img_gray)
|
| 28 |
+
|
| 29 |
+
# Vamos extrair só o texto manualmente para não dar erro de desempacotamento
|
| 30 |
+
lista_textos = []
|
| 31 |
+
for item in resultados_brutos:
|
| 32 |
+
# O formato do item é: ( [caixa], "texto lido", confiança )
|
| 33 |
+
if len(item) >= 2:
|
| 34 |
+
texto = item[1]
|
| 35 |
+
lista_textos.append(texto)
|
| 36 |
+
texto_detectado = " ".join(lista_textos)
|
| 37 |
+
print(f"📖 Texto Bruto: {texto_detectado}")
|
| 38 |
+
|
| 39 |
+
if len(texto_detectado) < 2:
|
| 40 |
+
print("⚠️ Pouco texto. A identificação pode falhar.")
|
| 41 |
+
return
|
| 42 |
+
|
| 43 |
+
except Exception as e:
|
| 44 |
+
print(f"Erro no OCR: {e}")
|
| 45 |
+
return
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# 2. IA (INTERPRETAÇÃO)
|
| 50 |
+
prompt = f"""
|
| 51 |
+
Analise este texto de rótulo de produto: "{texto_detectado}"
|
| 52 |
+
Identifique: Categoria | Marca | Detalhes.
|
| 53 |
+
Responda apenas nesse formato e não escreva textos longos. Se não souber, diga "Não identificado".
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
response = model.generate_content(prompt)
|
| 58 |
+
print(f"🤖 RESPOSTA IA: {response.text}") # Isso aparece no servidor (OK)
|
| 59 |
+
|
| 60 |
+
# --- O SEGREDO ESTÁ AQUI: TEM QUE TER O RETURN ---
|
| 61 |
+
return {
|
| 62 |
+
"texto_lido": texto_detectado,
|
| 63 |
+
"analise_ia": response.text
|
| 64 |
+
}
|
| 65 |
+
# -------------------------------------------------
|
| 66 |
+
|
| 67 |
+
except Exception as e:
|
| 68 |
+
print(f"Erro na IA: {e}")
|
| 69 |
+
return {"texto_lido": "Erro", "analise_ia": "Erro na IA"}
|
main.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
from ultralytics import YOLO
|
| 3 |
+
import os
|
| 4 |
+
import time
|
| 5 |
+
import requests # <--- ADICIONADO: Necessário para falar com a API
|
| 6 |
+
|
| 7 |
+
# --- REMOVIDO: from inteligencia import ...
|
| 8 |
+
# Não importamos mais a inteligência aqui, pois ela roda no servidor (api.py)
|
| 9 |
+
|
| 10 |
+
# Cria pastas para organizar
|
| 11 |
+
os.makedirs('resultados/recortes', exist_ok=True)
|
| 12 |
+
|
| 13 |
+
print("Carregando modelo YOLO (Visão)... aguarde.")
|
| 14 |
+
model = YOLO('yolov8m.pt')
|
| 15 |
+
print('Modelo carregado com sucesso!')
|
| 16 |
+
|
| 17 |
+
def read_image():
|
| 18 |
+
cap = cv2.VideoCapture(0)
|
| 19 |
+
cap.set(3, 1280)
|
| 20 |
+
cap.set(4, 720)
|
| 21 |
+
|
| 22 |
+
print('\n--- SISTEMA CLIENTE INICIADO ---')
|
| 23 |
+
print('📷 Aponte para o produto.')
|
| 24 |
+
print('📡 O processamento será feito pela API.')
|
| 25 |
+
print('🔘 Pressione "ESPAÇO" para enviar.')
|
| 26 |
+
print('❌ Pressione "q" para SAIR.\n')
|
| 27 |
+
|
| 28 |
+
while True:
|
| 29 |
+
success, frame = cap.read()
|
| 30 |
+
if not success:
|
| 31 |
+
print('Falha ao capturar imagem.')
|
| 32 |
+
break
|
| 33 |
+
|
| 34 |
+
# Faz a detecção
|
| 35 |
+
results = model.predict(frame, conf=0.5, verbose=False)
|
| 36 |
+
|
| 37 |
+
frame_anotado = frame.copy()
|
| 38 |
+
recorte_atual = None
|
| 39 |
+
|
| 40 |
+
# Loop por cada detecção
|
| 41 |
+
for box in results[0].boxes:
|
| 42 |
+
classe_id = int(box.cls[0])
|
| 43 |
+
name = model.names[classe_id]
|
| 44 |
+
|
| 45 |
+
# Filtro de pessoas
|
| 46 |
+
if name == 'person':
|
| 47 |
+
continue
|
| 48 |
+
|
| 49 |
+
confianca = float(box.conf[0])
|
| 50 |
+
|
| 51 |
+
if confianca > 0.6:
|
| 52 |
+
x1, y1, x2, y2 = map(int, box.xyxy[0])
|
| 53 |
+
|
| 54 |
+
# Desenha o retângulo
|
| 55 |
+
cv2.rectangle(frame_anotado, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
| 56 |
+
|
| 57 |
+
texto = f"{name.upper()} {confianca:.2f}"
|
| 58 |
+
cv2.putText(frame_anotado, texto, (x1, y1 - 10),
|
| 59 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
|
| 60 |
+
|
| 61 |
+
# Prepara o recorte com limites seguros
|
| 62 |
+
h, w, _ = frame.shape
|
| 63 |
+
x1, y1 = max(0, x1), max(0, y1)
|
| 64 |
+
x2, y2 = min(w, x2), min(h, y2)
|
| 65 |
+
|
| 66 |
+
recorte_atual = frame[y1:y2, x1:x2]
|
| 67 |
+
|
| 68 |
+
cv2.putText(frame_anotado, "PRODUTO DETECTADO! (Espaco para Enviar)", (50, 50),
|
| 69 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
|
| 70 |
+
|
| 71 |
+
cv2.imshow("Camera Cliente", frame_anotado)
|
| 72 |
+
|
| 73 |
+
key = cv2.waitKey(1)
|
| 74 |
+
if key == ord('q'):
|
| 75 |
+
break
|
| 76 |
+
elif key == 32: # ESPAÇO
|
| 77 |
+
if recorte_atual is not None:
|
| 78 |
+
# Salva temporariamente para envio
|
| 79 |
+
nome_arquivo = "temp_envio.jpg"
|
| 80 |
+
cv2.imwrite(nome_arquivo, recorte_atual)
|
| 81 |
+
|
| 82 |
+
print("📡 Enviando imagem para a API...")
|
| 83 |
+
|
| 84 |
+
try:
|
| 85 |
+
url = "http://127.0.0.1:8000/analisar"
|
| 86 |
+
|
| 87 |
+
# CORREÇÃO DO ARQUIVO ABERTO:
|
| 88 |
+
# Usamos 'with open' para garantir que o arquivo feche após o envio
|
| 89 |
+
with open(nome_arquivo, 'rb') as f:
|
| 90 |
+
arquivos = {'arquivo': f}
|
| 91 |
+
resposta = requests.post(url, files=arquivos)
|
| 92 |
+
|
| 93 |
+
# Agora o arquivo já está fechado, o código pode continuar
|
| 94 |
+
|
| 95 |
+
if resposta.status_code == 200:
|
| 96 |
+
dados = resposta.json()
|
| 97 |
+
|
| 98 |
+
# Verifica se dados não veio vazio
|
| 99 |
+
if dados:
|
| 100 |
+
print("\n" + "="*40)
|
| 101 |
+
print(f"📖 Texto Lido: {dados.get('texto_lido')}")
|
| 102 |
+
print(f"🤖 Análise IA: {dados.get('analise_ia')}")
|
| 103 |
+
print("="*40 + "\n")
|
| 104 |
+
else:
|
| 105 |
+
print("⚠️ A API retornou dados vazios.")
|
| 106 |
+
else:
|
| 107 |
+
print(f"❌ Erro na API: {resposta.status_code}")
|
| 108 |
+
|
| 109 |
+
except Exception as e:
|
| 110 |
+
print(f"❌ Erro: {e}")
|
| 111 |
+
|
| 112 |
+
# Agora sim pode deletar, pois o 'with open' já fechou o arquivo
|
| 113 |
+
if os.path.exists(nome_arquivo):
|
| 114 |
+
try:
|
| 115 |
+
os.remove(nome_arquivo)
|
| 116 |
+
except:
|
| 117 |
+
pass # Se não der pra deletar, tudo bem, ele sobrescreve na próxima
|
| 118 |
+
else:
|
| 119 |
+
print("⚠️ Nada detectado para enviar.")
|
| 120 |
+
|
| 121 |
+
cap.release()
|
| 122 |
+
cv2.destroyAllWindows()
|
| 123 |
+
# Limpa o arquivo temporário ao sair, se existir
|
| 124 |
+
if os.path.exists("temp_envio.jpg"):
|
| 125 |
+
os.remove("temp_envio.jpg")
|
| 126 |
+
print("Programa encerrado.")
|
| 127 |
+
|
| 128 |
+
if __name__ == "__main__":
|
| 129 |
+
read_image()
|
requirements.txt
ADDED
|
Binary file (258 Bytes). View file
|
|
|
yolov8m.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5d4a90cdc7a21786cc59cd19778e9eafff836df9e2da32524737c7ee6efe4fe5
|
| 3 |
+
size 52136884
|