beefresearch-bima / src /segmentation.py
abrarwahid's picture
Initial clean version for pipeline
78aefa8
Raw
History Blame Contribute Delete
3.14 kB
"""
Segmentasi ROI (Region of Interest) permukaan daging.
Pendekatan heuristik berbasis Strict HSV Thresholding & Margin Annihilation.
"""
import cv2
import numpy as np
import config
def segment_meat_roi(image_bgr, min_area_ratio=0.015):
"""
Menghasilkan mask biner (uint8, nilai 0/255) area permukaan daging (foreground) murni.
"""
h, w = image_bgr.shape[:2]
total_area = float(h * w)
# 1. Konversi ke HSV dengan reduksi blur untuk menjaga ketegasan batas tepi
blurred = cv2.GaussianBlur(image_bgr, (7, 7), 0)
hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)
# 2. Strict Spectrum Isolation
# S > 50 dan V > 40 membuang warna netral (hitam/abu-abu latar & putih stiker)
# H: 0-20 & 160-180 murni mengunci pigmen mioglobin (merah/pink/cokelat)
mask1 = cv2.inRange(hsv, np.array([0, 30, 25]), np.array([30, 255, 255]))
mask2 = cv2.inRange(hsv, np.array([160, 30, 25]), np.array([180, 255, 255]))
color_mask = cv2.bitwise_or(mask1, mask2)
# 3. Margin Annihilation (Menghapus 12% margin untuk mematikan sisa label)
margin_y, margin_x = int(h * 0.12), int(w * 0.12)
color_mask[0:margin_y, :] = 0
color_mask[h-margin_y:h, :] = 0
color_mask[:, 0:margin_x] = 0
color_mask[:, w-margin_x:w] = 0
# 4. Conservative Morphology (Menutup porositas tanpa mendistorsi geometri tepi)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11, 11))
morph_mask = cv2.morphologyEx(color_mask, cv2.MORPH_CLOSE, kernel, iterations=2)
morph_mask = cv2.morphologyEx(morph_mask, cv2.MORPH_OPEN, kernel, iterations=1)
# 5. Ekstraksi Kontur Agnostik
cnts = cv2.findContours(morph_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = cnts[0] if len(cnts) == 2 else cnts[1]
final_mask = np.zeros((h, w), dtype=np.uint8)
fallback_used = False
if not contours:
final_mask.fill(255)
fallback_used = True
else:
center_x, center_y = w // 2, h // 2
best_contour = None
best_score = -1
for c in contours:
area = cv2.contourArea(c)
# Filter noise absolut: Minimal 1.5% dari dimensi kanvas
if area > (total_area * min_area_ratio):
M = cv2.moments(c)
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
else:
cx, cy = center_x, center_y
dist = np.sqrt((cx - center_x)**2 + (cy - center_y)**2)
score = area / (dist + 1)
if score > best_score:
best_score = score
best_contour = c
if best_contour is not None:
# Render poligon murni tanpa Gaussian Blur yang berlebihan di tahap akhir
cv2.drawContours(final_mask, [best_contour], -1, 255, thickness=cv2.FILLED)
else:
final_mask.fill(255)
fallback_used = True
meta = {"foreground_ratio": float(np.count_nonzero(final_mask)) / total_area, "fallback_used": fallback_used}
return final_mask, meta