Skip to content

Breast Masking

왜 마스킹이 먼저인가

대부분의 mammography 전처리는 "유방 영역에만 적용"이 전제다. 배경(공기·라벨·검은 직사각형) 픽셀을 함께 처리하면 다음과 같은 일이 벌어진다.

  • 히스토그램 균등화·매칭이 배경의 큰 검은 영역에 끌려가 유방 콘트라스트가 사라진다
  • CLAHE 타일 중 일부가 거의 균일한 배경만 보고 가짜 노이즈를 증폭한다
  • 평가 지표(SSIM/PSNR)가 배경 일치에 의해 부풀려진다

따라서 다른 모든 전처리에 앞서 유방 영역 마스크(binary mask) 를 만들어야 한다. 마스크의 정확도는 곧 파이프라인 전체 품질의 상한선이다.

1단계 — Otsu 이진화

가장 단순한 방법은 Otsu의 임계값 자동 결정이다. 히스토그램이 "배경(어두움) vs 유방(밝음)" 두 봉우리로 나뉜다는 가정 아래, 클래스 간 분산을 최대화하는 임계값을 찾는다.

otsu_mask.py
import cv2
import numpy as np

def otsu_mask(img_u8: np.ndarray) -> np.ndarray:
    _, binary = cv2.threshold(
        img_u8, 0, 255,
        cv2.THRESH_BINARY + cv2.THRESH_OTSU
    )
    # 형태학적 닫힘으로 작은 구멍 메우기
    k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (20, 20))
    cleaned = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, k)
    cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_OPEN,  k)
    # 가장 큰 연결 컴포넌트 = 유방
    n, lab, st, _ = cv2.connectedComponentsWithStats(cleaned)
    if n <= 1:
        return np.zeros_like(cleaned)
    largest = 1 + np.argmax(st[1:, cv2.CC_STAT_AREA])
    return (lab == largest).astype(np.uint8)

한계

  • RAW에서 배경이 밝고 유방이 어두운 경우 부등호를 뒤집어야 한다 (THRESH_BINARY_INV).
  • 히스토그램이 단봉(unimodal) 인 일부 디바이스/검사에서는 임계값이 엉뚱한 위치에 잡힌다.

2단계 — Multi-Otsu (3-class)

유방 내부에도 지방 조직과 선조직이 섞여 있다. 이를 한 클래스로 묶는 2-class Otsu는 치밀유방에서 마스크 경계가 흔들린다.

skimage.filters.threshold_multiotsu는 두 개의 임계값으로 이미지를 배경 / 지방조직 / 선조직+병변 3-class로 분리한다.

multi_otsu_mask.py
1
2
3
4
5
6
7
8
from skimage.filters import threshold_multiotsu
import numpy as np

def multi_otsu_mask(img: np.ndarray):
    t = threshold_multiotsu(img, classes=3)
    breast_mask = img > t[0]      # 배경 제외 전체 유방
    dense_mask  = img > t[1]      # 선조직 + 병변 영역
    return breast_mask.astype(np.uint8), dense_mask.astype(np.uint8)
  • breast_mask: 일반 전처리·평가 마스크
  • dense_mask: 밀도 분류·병변 후보 영역 좁히기에 활용

3단계 — 적응형 마스킹 (백분위 + 그래디언트 + Convex Hull)

Otsu가 흔드릴 때를 대비한 폴백. 단일 임계값에 매달리지 않고 세 가지 단서를 결합한다.

adaptive_mask.py
import cv2
import numpy as np

def adaptive_breast_mask(img: np.ndarray) -> np.ndarray:
    # 1) 백분위 기반 임계값 (RAW 분포 적응)
    p_low, p_high = np.percentile(img, [5, 95])
    thresh = p_low + (p_high - p_low) * 0.3

    binary = (img < thresh).astype(np.uint8)

    # 2) 그래디언트로 에지 단서 확보 (참고만, 결합은 선택적)
    grad = cv2.Sobel(img.astype(np.float32), cv2.CV_32F, 1, 0)

    # 3) 형태학으로 안정화
    k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (30, 30))
    binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, k)

    # 4) 가장 큰 컨투어의 볼록 껍질 → 매끄러운 윤곽
    cnts, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL,
                               cv2.CHAIN_APPROX_SIMPLE)
    if not cnts:
        return binary
    hull = cv2.convexHull(max(cnts, key=cv2.contourArea))
    mask = np.zeros_like(binary)
    cv2.fillPoly(mask, [hull], 1)
    return mask

볼록 껍질은 유방 윤곽의 작은 들쭉날쭉함을 흡수해 후속 처리(특히 패치 추출)의 경계를 안정적으로 만든다. 실측에서 Otsu 단독 9.7~98% 변동이 안정적인 85% 이상으로 좁혀졌다.

RAW 직접 분할 — 히스토그램 밸리 임계값

RAW→DCM 복원처럼 윈도잉 전 16비트 RAW를 직접 다룰 때는, RAW 히스토그램이 "유방(낮은 강도) vs 배경(높은 강도)"으로 갈리는 밸리(valley) 를 임계값으로 쓰면 단순하고 안정적이다. 배경이 밝은 RAW에서는 임계값보다 작은 값이 유방이다.

histogram_valley.py
1
2
3
4
5
6
7
8
def find_breast_threshold(raw_img, search_low=10000, search_high=30000, n_bins=1000):
    hist, bins = np.histogram(raw_img.ravel(), bins=n_bins)
    centers = (bins[:-1] + bins[1:]) / 2
    rng = (centers > search_low) & (centers < search_high)   # 두 봉우리 사이만 탐색
    valley = np.argmin(hist[rng])                            # 골짜기 = 임계값
    return centers[rng][valley]

mask = raw_img < find_breast_threshold(raw_img)              # 배경이 밝은 경우

이후 형태학적 정리 + 최대 연결요소 + 구멍 메우기는 Otsu 경로와 동일하게 적용한다. 탐색 구간(search_low/high)은 디바이스 강도 분포에 맞춰 조정한다.

평가 — 마스크 겹침 (IoU)

마스크 자체의 품질은 보통 IoU (Intersection over Union) 로 평가한다.

\[ \mathrm{IoU}(A, B) = \frac{|A \cap B|}{|A \cup B|} \]
iou.py
1
2
3
4
5
def iou(a: np.ndarray, b: np.ndarray) -> float:
    a, b = a.astype(bool), b.astype(bool)
    inter = np.logical_and(a, b).sum()
    union = np.logical_or(a, b).sum()
    return float(inter / union) if union else 0.0

DCM 참조 마스크가 있는 경우(보통 임상 DICOM의 (0028,1300) BreastImplantPresent와 별도로 자체 생성한 ground truth 마스크 사용) IoU > 0.9를 기준선으로 본다.