Spaces:
Sleeping
Sleeping
File size: 6,087 Bytes
3f5e07e 0aabd3f f3cde7d 8602d84 0aabd3f 8602d84 3f5e07e f3cde7d 3f5e07e 7b69340 f3cde7d 3f5e07e f3cde7d 3f5e07e f3cde7d 3f5e07e f3cde7d 3f5e07e f3cde7d 3f5e07e f3cde7d 3f5e07e f3cde7d 3f5e07e 7b69340 3f5e07e 0aabd3f 3f5e07e f3cde7d 3f5e07e 7b69340 3f5e07e 7b69340 3f5e07e 0aabd3f 8602d84 3f5e07e f3cde7d 3f5e07e 4f75d88 3f5e07e f3cde7d 0aabd3f f3cde7d 3f5e07e f3cde7d 0aabd3f 3f5e07e 0aabd3f 3f5e07e 7b69340 f3cde7d 3f5e07e 0aabd3f f3cde7d 0aabd3f f3cde7d 3f5e07e 0aabd3f 3f5e07e f3cde7d 3f5e07e 8602d84 0aabd3f f3cde7d 3f5e07e f3cde7d 3f5e07e f3cde7d 3f5e07e f3cde7d 3f5e07e 0aabd3f f3cde7d 3f5e07e 0aabd3f 8602d84 0aabd3f 7b69340 3f5e07e 0aabd3f 3f5e07e 0aabd3f f3cde7d 0aabd3f 8602d84 0aabd3f 8602d84 f3cde7d 3f5e07e 8602d84 3f5e07e 0aabd3f 8602d84 0aabd3f 7b69340 3f5e07e 7b69340 0aabd3f 3f5e07e 0aabd3f 8602d84 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# Contador de larvas – versión alineada a 2047x1148
# Autor: Cesarria & ChatGPT
import gradio as gr
import cv2
import numpy as np
import statistics
# --- CONFIG ---
IMG_W = 2047
IMG_H = 1148
BORDER = 6 # recorte de marco
global_count = 0
median_single_area = None
# =============== helpers ===============
def ellipse_ratio(cnt):
"""Relación eje menor / eje mayor de la elipse (0..1)."""
if len(cnt) < 5:
return None
(_, _), (MA, ma), _ = cv2.fitEllipse(cnt)
return min(MA, ma) / max(MA, ma)
def contour_solidity(cnt):
"""Solidez = área / área del hull. Sirve para descartar harina 'desflecada'."""
area = cv2.contourArea(cnt)
if area <= 0:
return 0.0
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
if hull_area == 0:
return 0.0
return float(area) / float(hull_area)
def preprocess(image_bgr):
"""
- redimensiona a 2047x1148
- recorta bordes
- pasa a gris
- CLAHE
- resta de fondo
- normaliza
"""
img = cv2.resize(image_bgr, (IMG_W, IMG_H), interpolation=cv2.INTER_LINEAR)
# recorte
roi = img[BORDER:IMG_H - BORDER, BORDER:IMG_W - BORDER]
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
# realzar los puntitos blancos
clahe = cv2.createCLAHE(clipLimit=2.5, tileGridSize=(8, 8))
gray = clahe.apply(gray)
# quitar gradiente
bg = cv2.GaussianBlur(gray, (25, 25), 0)
sub = cv2.subtract(gray, bg)
# normalizar
sub = cv2.normalize(sub, None, 0, 255, cv2.NORM_MINMAX)
# pequeño blur para bajar granitos de harina
sub = cv2.medianBlur(sub, 3)
return sub, img # devolvemos también la imagen redimensionada completa
def detect_larvas(image_bgr,
thresh_value=10,
min_area=6,
max_area_single=40,
shape_min=0.55,
shape_max=0.95,
min_solidity=0.7):
"""
Detecta y cuenta larvas.
- trabaja SIEMPRE en 2047x1148
- filtra por área, forma y solidez
"""
global median_single_area
gray_proc, base_img = preprocess(image_bgr) # base_img ya es 2047x1148
h_proc, w_proc = gray_proc.shape[:1][0], gray_proc.shape[:1][0]
# umbral
if thresh_value == 0:
_, th = cv2.threshold(gray_proc, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
else:
_, th = cv2.threshold(gray_proc, int(thresh_value), 255, cv2.THRESH_BINARY)
# limpiar un poco
kernel = np.ones((3, 3), np.uint8)
th = cv2.morphologyEx(th, cv2.MORPH_OPEN, kernel, iterations=1)
# contornos en la imagen recortada
contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
good = []
areas_all = []
areas_single = []
for c in contours:
area = cv2.contourArea(c)
if area < min_area or area > 5000:
continue
# forma (descartar harina despareja)
ratio = ellipse_ratio(c)
if ratio is None or not (shape_min <= ratio <= shape_max):
continue
# solidez (descarta cosas con bordes poco definidos)
sol = contour_solidity(c)
if sol < min_solidity:
continue
good.append(c)
areas_all.append(area)
if area <= max_area_single:
areas_single.append(area)
# estimar área típica
if areas_single:
median_single_area = statistics.median_low(areas_single)
elif areas_all:
median_single_area = statistics.median_low(areas_all)
else:
# no detectamos nada
out = base_img.copy()
cv2.putText(out, "LARVAS: 0", (40, 80),
cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 3)
return out, 0
total = 0
for c in good:
a = cv2.contourArea(c)
if a <= max_area_single:
total += 1
else:
est = int(round(a / median_single_area))
total += max(1, est)
# dibujar sobre la imagen COMPLETA (ya 2047x1148)
out = base_img.copy()
for c in good:
# como trabajamos sobre la imagen recortada, hay que desplazar
c_shifted = c + np.array([[BORDER, BORDER]])
cv2.drawContours(out, [c_shifted], -1, (0, 255, 0), 1)
cv2.putText(out, f"LARVAS: {total}", (40, 80),
cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 3)
return out, total
# =============== gradio wrappers ===============
def process(image, thresh, min_a, max_a):
global global_count
if image is None:
return None, "No subiste imagen", f"Conteo total: {global_count}"
img_bgr = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
out_img_bgr, n = detect_larvas(
img_bgr,
thresh_value=int(thresh),
min_area=int(min_a),
max_area_single=int(max_a)
)
global_count += n
out_img_rgb = cv2.cvtColor(out_img_bgr, cv2.COLOR_BGR2RGB)
return out_img_rgb, f"Larvas en la imagen: {n}", f"Conteo total: {global_count}"
def reset():
global global_count
global_count = 0
return f"Conteo total: {global_count}"
# =============== interfaz ===============
with gr.Blocks() as demo:
gr.Markdown("## Contador de larvas – v6 (rangos amplios y alineado)")
with gr.Row():
with gr.Column(scale=1):
inp = gr.Image(label="Subí la foto")
thresh = gr.Slider(0, 300, 10, 1, label="Umbral (0=Otsu auto)")
min_area = gr.Slider(0, 300, 6, 1, label="Min área px²")
max_area_single = gr.Slider(0, 300, 40, 1, label="Máx área 1 larva px²")
btn = gr.Button("Procesar")
btn_reset = gr.Button("Reset contador")
with gr.Column(scale=1):
out_img = gr.Image(label="Resultado")
out_txt = gr.Textbox(label="Resultado individual")
out_total = gr.Textbox(label="Resultado acumulado")
btn.click(
process,
inputs=[inp, thresh, min_area, max_area_single],
outputs=[out_img, out_txt, out_total]
)
btn_reset.click(reset, [], [out_total])
demo.launch(debug=True)
|