Spaces:
Sleeping
Sleeping
Update test.py
Browse files
test.py
CHANGED
|
@@ -5,6 +5,8 @@ import cv2
|
|
| 5 |
import numpy as np
|
| 6 |
import os
|
| 7 |
import math
|
|
|
|
|
|
|
| 8 |
|
| 9 |
# Caminho para o modelo treinado
|
| 10 |
modelo_path = "runs/segment/meu_yolo_seg20/weights/best.pt"
|
|
@@ -25,71 +27,41 @@ def contar_pixels_conexos(mask, minimo):
|
|
| 25 |
def calcular_distancia(p1, p2):
|
| 26 |
return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
|
| 27 |
|
| 28 |
-
#import pytesseract
|
| 29 |
-
import re
|
| 30 |
-
|
| 31 |
def detectar_escala(img_bgr):
|
| 32 |
-
"""
|
| 33 |
-
Detecta linha preta horizontal da régua no canto inferior direito
|
| 34 |
-
usando HoughLinesP, sem OCR.
|
| 35 |
-
"""
|
| 36 |
-
import cv2
|
| 37 |
-
import numpy as np
|
| 38 |
-
|
| 39 |
h, w = img_bgr.shape[:2]
|
| 40 |
-
roi = img_bgr[h-100:h-10, int(w*0.6):w]
|
| 41 |
-
|
| 42 |
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
|
| 43 |
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
|
| 44 |
-
|
| 45 |
lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi/180, threshold=30,
|
| 46 |
minLineLength=30, maxLineGap=5)
|
| 47 |
-
|
| 48 |
longest = 0
|
| 49 |
if lines is not None:
|
| 50 |
for line in lines:
|
| 51 |
x1, y1, x2, y2 = line[0]
|
| 52 |
-
if abs(y2 - y1) < 5:
|
| 53 |
length = abs(x2 - x1)
|
| 54 |
if length > longest:
|
| 55 |
longest = length
|
|
|
|
| 56 |
|
| 57 |
-
|
| 58 |
-
mm_per_pixel = 0.500 / longest
|
| 59 |
-
print(f"📏 Régua detetada sem OCR: {longest}px = 0.500 mm → {mm_per_pixel:.6f} mm/pixel")
|
| 60 |
-
return mm_per_pixel
|
| 61 |
-
else:
|
| 62 |
-
print("⚠️ Nenhuma linha horizontal de régua encontrada.")
|
| 63 |
-
return None
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
def segmentar(imagem_path, dispositivo, conf_min, pixels_minimos):
|
| 67 |
device = "cuda" if dispositivo == "GPU" and torch.cuda.is_available() else "cpu"
|
| 68 |
model = carregar_modelo(device)
|
| 69 |
-
|
| 70 |
results = model.predict(source=imagem_path, device=device, conf=conf_min, verbose=False)
|
| 71 |
r = results[0]
|
| 72 |
-
|
| 73 |
img_bgr = cv2.imread(imagem_path)
|
| 74 |
if img_bgr is None:
|
| 75 |
-
return
|
| 76 |
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 77 |
h, w = img_rgb.shape[:2]
|
| 78 |
-
|
| 79 |
mm_por_pixel = detectar_escala(img_bgr)
|
| 80 |
-
|
| 81 |
-
total_detectadas = 0
|
| 82 |
mascaras_binarias = []
|
| 83 |
-
|
| 84 |
if r.masks is not None and r.masks.data is not None:
|
| 85 |
-
total_detectadas = len(r.masks.data)
|
| 86 |
for mask in r.masks.data:
|
| 87 |
m = mask.cpu().numpy().astype(np.uint8) * 255
|
| 88 |
m = cv2.resize(m, (w, h), interpolation=cv2.INTER_NEAREST)
|
| 89 |
if contar_pixels_conexos(m, pixels_minimos) > 0:
|
| 90 |
mascaras_binarias.append((m > 0).astype(np.uint8))
|
| 91 |
-
|
| 92 |
-
# Fusão de máscaras sobrepostas
|
| 93 |
mascaras_fundidas = []
|
| 94 |
for m in mascaras_binarias:
|
| 95 |
fundida = False
|
|
@@ -100,10 +72,7 @@ def segmentar(imagem_path, dispositivo, conf_min, pixels_minimos):
|
|
| 100 |
break
|
| 101 |
if not fundida:
|
| 102 |
mascaras_fundidas.append(m)
|
| 103 |
-
|
| 104 |
-
total_usadas = len(mascaras_fundidas)
|
| 105 |
centroides = []
|
| 106 |
-
|
| 107 |
for m in mascaras_fundidas:
|
| 108 |
M = cv2.moments(m)
|
| 109 |
if M["m00"] != 0:
|
|
@@ -111,7 +80,6 @@ def segmentar(imagem_path, dispositivo, conf_min, pixels_minimos):
|
|
| 111 |
cy = int(M["m01"] / M["m00"])
|
| 112 |
centroides.append((cx, cy))
|
| 113 |
cv2.circle(img_rgb, (cx, cy), 12, (0, 255, 0), 2)
|
| 114 |
-
|
| 115 |
distancia_px = None
|
| 116 |
distancia_mm = None
|
| 117 |
if len(centroides) == 2:
|
|
@@ -121,53 +89,54 @@ def segmentar(imagem_path, dispositivo, conf_min, pixels_minimos):
|
|
| 121 |
distancia_px = calcular_distancia(centroides[0], centroides[1])
|
| 122 |
if mm_por_pixel:
|
| 123 |
distancia_mm = distancia_px * mm_por_pixel
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
output_path = "output_segmentada.png"
|
| 127 |
cv2.imwrite(output_path, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
with
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
)
|
| 165 |
-
|
| 166 |
-
# Limpa saídas quando nova imagem é carregada
|
| 167 |
-
imagem_input.change(
|
| 168 |
-
fn=lambda _: (None, None, ""),
|
| 169 |
-
inputs=imagem_input,
|
| 170 |
-
outputs=[img_output, download_output, resumo]
|
| 171 |
-
)
|
| 172 |
-
|
| 173 |
-
demo.launch(share = True)
|
|
|
|
| 5 |
import numpy as np
|
| 6 |
import os
|
| 7 |
import math
|
| 8 |
+
import pandas as pd
|
| 9 |
+
from datetime import datetime
|
| 10 |
|
| 11 |
# Caminho para o modelo treinado
|
| 12 |
modelo_path = "runs/segment/meu_yolo_seg20/weights/best.pt"
|
|
|
|
| 27 |
def calcular_distancia(p1, p2):
|
| 28 |
return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
|
| 29 |
|
|
|
|
|
|
|
|
|
|
| 30 |
def detectar_escala(img_bgr):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
h, w = img_bgr.shape[:2]
|
| 32 |
+
roi = img_bgr[h-100:h-10, int(w*0.6):w]
|
|
|
|
| 33 |
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
|
| 34 |
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
|
|
|
|
| 35 |
lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi/180, threshold=30,
|
| 36 |
minLineLength=30, maxLineGap=5)
|
|
|
|
| 37 |
longest = 0
|
| 38 |
if lines is not None:
|
| 39 |
for line in lines:
|
| 40 |
x1, y1, x2, y2 = line[0]
|
| 41 |
+
if abs(y2 - y1) < 5:
|
| 42 |
length = abs(x2 - x1)
|
| 43 |
if length > longest:
|
| 44 |
longest = length
|
| 45 |
+
return 0.500 / longest if longest > 0 else None
|
| 46 |
|
| 47 |
+
def processar_uma_imagem(imagem_path, dispositivo, conf_min, pixels_minimos):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
device = "cuda" if dispositivo == "GPU" and torch.cuda.is_available() else "cpu"
|
| 49 |
model = carregar_modelo(device)
|
|
|
|
| 50 |
results = model.predict(source=imagem_path, device=device, conf=conf_min, verbose=False)
|
| 51 |
r = results[0]
|
|
|
|
| 52 |
img_bgr = cv2.imread(imagem_path)
|
| 53 |
if img_bgr is None:
|
| 54 |
+
return None, None, "Erro ao carregar imagem."
|
| 55 |
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 56 |
h, w = img_rgb.shape[:2]
|
|
|
|
| 57 |
mm_por_pixel = detectar_escala(img_bgr)
|
|
|
|
|
|
|
| 58 |
mascaras_binarias = []
|
|
|
|
| 59 |
if r.masks is not None and r.masks.data is not None:
|
|
|
|
| 60 |
for mask in r.masks.data:
|
| 61 |
m = mask.cpu().numpy().astype(np.uint8) * 255
|
| 62 |
m = cv2.resize(m, (w, h), interpolation=cv2.INTER_NEAREST)
|
| 63 |
if contar_pixels_conexos(m, pixels_minimos) > 0:
|
| 64 |
mascaras_binarias.append((m > 0).astype(np.uint8))
|
|
|
|
|
|
|
| 65 |
mascaras_fundidas = []
|
| 66 |
for m in mascaras_binarias:
|
| 67 |
fundida = False
|
|
|
|
| 72 |
break
|
| 73 |
if not fundida:
|
| 74 |
mascaras_fundidas.append(m)
|
|
|
|
|
|
|
| 75 |
centroides = []
|
|
|
|
| 76 |
for m in mascaras_fundidas:
|
| 77 |
M = cv2.moments(m)
|
| 78 |
if M["m00"] != 0:
|
|
|
|
| 80 |
cy = int(M["m01"] / M["m00"])
|
| 81 |
centroides.append((cx, cy))
|
| 82 |
cv2.circle(img_rgb, (cx, cy), 12, (0, 255, 0), 2)
|
|
|
|
| 83 |
distancia_px = None
|
| 84 |
distancia_mm = None
|
| 85 |
if len(centroides) == 2:
|
|
|
|
| 89 |
distancia_px = calcular_distancia(centroides[0], centroides[1])
|
| 90 |
if mm_por_pixel:
|
| 91 |
distancia_mm = distancia_px * mm_por_pixel
|
| 92 |
+
nome = os.path.basename(imagem_path)
|
| 93 |
+
output_path = os.path.splitext(nome)[0] + "_out.png"
|
|
|
|
| 94 |
cv2.imwrite(output_path, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
|
| 95 |
+
return output_path, nome, distancia_px, distancia_mm
|
| 96 |
+
|
| 97 |
+
def processar_varias(imagens, dispositivo, conf_min, pixels_minimos):
|
| 98 |
+
imagens_resultado = []
|
| 99 |
+
nomes, pxs, mms = [], [], []
|
| 100 |
+
for imagem in imagens:
|
| 101 |
+
out_path, nome, dist_px, dist_mm = processar_uma_imagem(imagem, dispositivo, conf_min, pixels_minimos)
|
| 102 |
+
imagens_resultado.append(out_path)
|
| 103 |
+
nomes.append(nome)
|
| 104 |
+
pxs.append(f"{dist_px:.1f}" if dist_px else "-")
|
| 105 |
+
mms.append(f"{dist_mm:.3f}" if dist_mm else "-")
|
| 106 |
+
valores_mm = [float(x) for x in mms if x != "-"]
|
| 107 |
+
media = np.mean(valores_mm) if valores_mm else 0
|
| 108 |
+
desvio = np.std(valores_mm) if valores_mm else 0
|
| 109 |
+
tabela = pd.DataFrame({"Imagem": nomes, "Distância (px)": pxs, "Distância (mm)": mms})
|
| 110 |
+
resumo_df = pd.DataFrame({"Métrica": ["Média", "Desvio padrão"], "Valor (mm)": [f"{media:.3f}", f"{desvio:.3f}"]})
|
| 111 |
+
agora = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
| 112 |
+
resultados_xlsx_path = f"resultados_medidas_{agora}.xlsx"
|
| 113 |
+
with pd.ExcelWriter(resultados_xlsx_path) as writer:
|
| 114 |
+
tabela.to_excel(writer, sheet_name="Resultados", index=False)
|
| 115 |
+
resumo_df.to_excel(writer, sheet_name="Resumo", index=False)
|
| 116 |
+
# Criar pasta para imagens
|
| 117 |
+
pasta_saida = f"imagens_processadas_{agora}"
|
| 118 |
+
os.makedirs(pasta_saida, exist_ok=True)
|
| 119 |
+
for img_path in imagens_resultado:
|
| 120 |
+
novo_caminho = os.path.join(pasta_saida, os.path.basename(img_path))
|
| 121 |
+
os.replace(img_path, novo_caminho)
|
| 122 |
+
imagens_resultado = [os.path.join(pasta_saida, os.path.basename(p)) for p in imagens_resultado]
|
| 123 |
+
legendas = [f"{n} - {m} mm" for n, m in zip(nomes, mms)]
|
| 124 |
+
return list(zip(imagens_resultado, legendas)), tabela, resumo_df, resultados_xlsx_path
|
| 125 |
+
|
| 126 |
+
# Interface Gradio
|
| 127 |
+
with gr.Blocks(title="Medição de asas múltiplas") as demo:
|
| 128 |
+
gr.Markdown("## Medição automática de asas de abelha")
|
| 129 |
+
imagens_input = gr.File(label="Subir imagens", file_types=[".png", ".jpg", ".jpeg"], file_count="multiple")
|
| 130 |
+
dispositivo = gr.Radio(["CPU", "GPU"], label="Dispositivo", value="CPU", visible=False)
|
| 131 |
+
conf_slider = gr.Slider(0.01, 1.0, value=0.1, step=0.01, label="Confiança mínima", visible=False)
|
| 132 |
+
pixel_slider = gr.Slider(1, 1000, value=10, step=1, label="Pixels conectados mínimos por máscara", visible=False)
|
| 133 |
+
imagens_saida = gr.Gallery(label="Resultados das imagens segmentadas", columns=4, preview=True, show_label=True)
|
| 134 |
+
tabela_resultados = gr.Dataframe(label="Resultados", interactive=False, headers=["Imagem", "Distância (px)", "Distância (mm)"])
|
| 135 |
+
tabela_resumo = gr.Dataframe(label="Resumo estatístico", interactive=False, headers=["Métrica", "Valor (mm)"])
|
| 136 |
+
download_btn = gr.File(label="Download XLSX")
|
| 137 |
+
imagens_input.change(
|
| 138 |
+
fn=lambda imagens: processar_varias(imagens, "CPU", 0.1, 10),
|
| 139 |
+
inputs=imagens_input,
|
| 140 |
+
outputs=[imagens_saida, tabela_resultados, tabela_resumo, download_btn]
|
| 141 |
)
|
| 142 |
+
demo.launch(share=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|