wings_length / test.py
pjsr's picture
Update test.py
8e124f9 verified
import gradio as gr
from ultralytics import YOLO
import torch
import cv2
import numpy as np
import os
import math
import pandas as pd
from datetime import datetime
# Caminho para o modelo treinado
modelo_path = "runs/segment/meu_yolo_seg20/weights/best.pt"
model_cache = {}
def carregar_modelo(device):
if device not in model_cache:
model = YOLO(modelo_path)
model.to(device)
model_cache[device] = model
return model_cache[device]
def contar_pixels_conexos(mask, minimo):
bin_mask = (mask > 0).astype(np.uint8)
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(bin_mask)
return sum(stat[cv2.CC_STAT_AREA] for stat in stats[1:] if stat[cv2.CC_STAT_AREA] >= minimo)
def calcular_distancia(p1, p2):
return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
def detectar_escala(img_bgr):
h, w = img_bgr.shape[:2]
roi = img_bgr[h-100:h-10, int(w*0.6):w]
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi/180, threshold=30,
minLineLength=30, maxLineGap=5)
longest = 0
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
if abs(y2 - y1) < 5:
length = abs(x2 - x1)
if length > longest:
longest = length
return 1.0 / longest if longest > 0 else None
def processar_uma_imagem(imagem_path, dispositivo, conf_min, pixels_minimos):
device = "cuda" if dispositivo == "GPU" and torch.cuda.is_available() else "cpu"
model = carregar_modelo(device)
results = model.predict(source=imagem_path, device=device, conf=conf_min, verbose=False)
r = results[0]
img_bgr = cv2.imread(imagem_path)
if img_bgr is None:
return None, None, "Erro ao carregar imagem."
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
h, w = img_rgb.shape[:2]
mm_por_pixel = detectar_escala(img_bgr)
mascaras_binarias = []
if r.masks is not None and r.masks.data is not None:
for mask in r.masks.data:
m = mask.cpu().numpy().astype(np.uint8) * 255
m = cv2.resize(m, (w, h), interpolation=cv2.INTER_NEAREST)
if contar_pixels_conexos(m, pixels_minimos) > 0:
mascaras_binarias.append((m > 0).astype(np.uint8))
mascaras_fundidas = []
for m in mascaras_binarias:
fundida = False
for i in range(len(mascaras_fundidas)):
if np.count_nonzero(cv2.bitwise_and(m, mascaras_fundidas[i])) > 0:
mascaras_fundidas[i] = cv2.bitwise_or(mascaras_fundidas[i], m)
fundida = True
break
if not fundida:
mascaras_fundidas.append(m)
centroides = []
for m in mascaras_fundidas:
M = cv2.moments(m)
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
centroides.append((cx, cy))
cv2.circle(img_rgb, (cx, cy), 12, (0, 255, 0), 2)
distancia_px = None
distancia_mm = None
if len(centroides) == 2:
cv2.line(img_rgb, centroides[0], centroides[1], (0, 0, 255), 2)
for c in centroides:
cv2.circle(img_rgb, c, 6, (0, 0, 255), -1)
distancia_px = calcular_distancia(centroides[0], centroides[1])
if mm_por_pixel:
distancia_mm = distancia_px * mm_por_pixel
nome = os.path.basename(imagem_path)
output_path = os.path.splitext(nome)[0] + "_out.png"
cv2.imwrite(output_path, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
return output_path, nome, distancia_px, distancia_mm
def processar_varias(imagens, dispositivo, conf_min, pixels_minimos):
imagens_resultado = []
nomes, pxs, mms = [], [], []
for imagem in imagens:
out_path, nome, dist_px, dist_mm = processar_uma_imagem(imagem, dispositivo, conf_min, pixels_minimos)
imagens_resultado.append(out_path)
nomes.append(nome)
pxs.append(f"{dist_px:.1f}" if dist_px else "-")
mms.append(f"{dist_mm:.3f}" if dist_mm else "-")
valores_mm = [float(x) for x in mms if x != "-"]
media = np.mean(valores_mm) if valores_mm else 0
desvio = np.std(valores_mm) if valores_mm else 0
tabela = pd.DataFrame({"Imagem": nomes, "Distância (px)": pxs, "Distância (mm)": mms})
resumo_df = pd.DataFrame({"Métrica": ["Média", "Desvio padrão"], "Valor (mm)": [f"{media:.3f}", f"{desvio:.3f}"]})
agora = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
resultados_xlsx_path = f"resultados_medidas_{agora}.xlsx"
with pd.ExcelWriter(resultados_xlsx_path) as writer:
tabela.to_excel(writer, sheet_name="Resultados", index=False)
resumo_df.to_excel(writer, sheet_name="Resumo", index=False)
# Criar pasta para imagens
pasta_saida = f"imagens_processadas_{agora}"
os.makedirs(pasta_saida, exist_ok=True)
for img_path in imagens_resultado:
novo_caminho = os.path.join(pasta_saida, os.path.basename(img_path))
os.replace(img_path, novo_caminho)
imagens_resultado = [os.path.join(pasta_saida, os.path.basename(p)) for p in imagens_resultado]
legendas = [f"{n} - {m} mm" for n, m in zip(nomes, mms)]
return list(zip(imagens_resultado, legendas)), tabela, resumo_df, resultados_xlsx_path
# Interface Gradio
with gr.Blocks(title="Medição de asas múltiplas") as demo:
gr.Markdown("## Medição automática de asas de abelha com normalização à escala impressa")
imagens_input = gr.File(label="Subir imagens", file_types=[".png", ".jpg", ".jpeg"], file_count="multiple")
dispositivo = gr.Radio(["CPU", "GPU"], label="Dispositivo", value="CPU", visible=False)
conf_slider = gr.Slider(0.01, 1.0, value=0.1, step=0.01, label="Confiança mínima", visible=False)
pixel_slider = gr.Slider(1, 1000, value=10, step=1, label="Pixels conectados mínimos por máscara", visible=False)
imagens_saida = gr.Gallery(label="Resultados das imagens segmentadas", columns=4, preview=True, show_label=True)
tabela_resultados = gr.Dataframe(label="Resultados", interactive=False, headers=["Imagem", "Distância (px)", "Distância (mm)"])
tabela_resumo = gr.Dataframe(label="Resumo estatístico", interactive=False, headers=["Métrica", "Valor (mm)"])
download_btn = gr.File(label="Download XLSX")
imagens_input.change(
fn=lambda imagens: processar_varias(imagens, "CPU", 0.1, 10),
inputs=imagens_input,
outputs=[imagens_saida, tabela_resultados, tabela_resumo, download_btn]
)
demo.launch(share=False)