요즘 들어서 삶이 꽤나 고단하다는 생각이 들고 있다. 그래도 뭐 매일매일 노력해야 하지 않나 싶다.

01. Morphological operations(형태학 연산)

  • Erosion(침식) : 커널(구조화 요소)로 스캔하며 픽셀 주변이 커널 모양을 “충족”할 때만 중심 픽셀을 유지 → 얇아짐/노이즈 제거
  • Dilation(팽창): 반대로 주변 중 하나라도 충족하면 중심 픽셀을 1로 설정 → 두꺼워짐/틈 메움
  • Opening = Erosion → Dilation: 작은 잡음 제거, 글자 안쪽 구멍 유지
  • Closing = Dilation → Erosion: 끊긴 획 연결, 작은 구멍 메움
  • 커널 선택: 직사각형/타원/십자형, 크기는 글자 획 두께와 간격을 고려(예: 3×3, 5×5)
import cv2 as cv
import numpy as np

img = cv.imread("input.png", cv.IMREAD_GRAYSCALE)
_, bin_ = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)

k = cv.getStructuringElement(cv.MORPH_RECT, (3,3))
eroded  = cv.erode(bin_, k, iterations=1)
dilated = cv.dilate(bin_, k, iterations=1)
opened  = cv.morphologyEx(bin_, cv.MORPH_OPEN, k, iterations=1)
closed  = cv.morphologyEx(bin_, cv.MORPH_CLOSE, k, iterations=1)

02. Otsu Thresholding (전역 임계값)

  • 히스토그램을 두 집단(배경/전경)으로 나눌 때 집단 간 분산 σb2(t)\sigma_b^2(t)가 최대가 되는 임계값 tt를 선택
    σb2(t)=ω0(t)ω1(t)[μ0(t)μ1(t)]2\sigma_b^2(t)=\omega_0(t)\,\omega_1(t)\,[\mu_0(t)-\mu_1(t)]^2

wiw_{i} : 집단 ii의 확률(빈도 누적)
uiu_{i} : 집단 평균

  • 예시 코드
import cv2 as cv
import numpy as np

img = cv.imread("input.png")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
blur = cv.GaussianBlur(gray, (5,5), 0)
_, th = cv.threshold(blur, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)

# 글자 연결을 위한 클로징
k = cv.getStructuringElement(cv.MORPH_RECT, (3,3))
m = cv.morphologyEx(th, cv.MORPH_CLOSE, k, iterations=2)

# 컨투어 탐색 및 간단한 필터링
cnts, _ = cv.findContours(m, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
boxes = []
for c in cnts:
    x,y,w,h = cv.boundingRect(c)
    area = w*h
    aspect = w / (h+1e-6)
    if area > 200 and 1.2 < aspect < 15:   # 예시 기준
        boxes.append((x,y,w,h))
        cv.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)

cv.imwrite("contour_result.png", img)

03. EAST: Efficient and Accurate Scene Text Detector 요약

03-01) 핵심 아이디어

  • 논문링크
  • 단 2단계 파이프라인: (1) FCN이 score map(텍스트 확률, 1채널)geometry map(텍스트 박스 기하, 다채널)을 직접 예측 → (2) NMS로 최종 박스 합치기. 중간 후보/연결/분할 단계 제거
  • 출력 형태
    • RBOX(5채널): AABB 4거리 + 각도 θ\theta
    • QUAD(8채널): 4꼭지점 (Δxi,Δyi)(\Delta x_{i},\Delta y_{i} )
      (score 1채널 + geometry N채널)
    • 성능: ICDAR2015에서 F-score 0.7820, 720p에서 13.2 FPS(Titan X).

03-02) 네트워크 구조

  • U-Net 유사 병합 가지: 깊은/얕은 피처를 단계적으로 업샘플-컨캣하며 합성. 수식(1)(2)로 병합 정의
  • 출력층은 1×1 컨브로 32채널 피처 → score 1채널 + geometry 다채널로 사상
    gi={unpool(hi),i3conv3×3(hi),i=4hi={fi,i=1conv3×3 ⁣(conv1×1 ⁣([gi1;fi])),otherwiseg_i=\begin{cases} \mathrm{unpool}(h_i), & i\le 3\\ \mathrm{conv}_{3\times3}(h_i), & i=4 \end{cases} \qquad h_i=\begin{cases} f_i, & i=1\\ \mathrm{conv}_{3\times3}\!\left(\mathrm{conv}_{1\times1}\!\big([g_{i-1};f_i]\big)\right), & \text{otherwise} \end{cases}

03-03) 라벨/손실(핵심 수식)

  • Score map: class-balanced cross entropy

    Ls=βY\*logY^(1β)(1Y\*)log(1Y^),β=1Y\*Y\*L_s=-\beta Y^\* \log \hat Y-(1-\beta)(1-Y^\*)\log(1-\hat Y),\quad \beta=1-\frac{\sum Y^\*}{|Y^\*|}
  • RBOX(AABB) 회귀: IoU loss + 각도 손실

    LAABB=logR^R\*R^R\*,Lθ=1cos(θ^θ\*),Lg=LAABB+λθLθL_{\mathrm{AABB}}=-\log\frac{| \hat R\cap R^\*|}{| \hat R\cup R^\*|},\quad L_\theta=1-\cos(\hat\theta-\theta^\*),\quad L_g=L_{\mathrm{AABB}}+\lambda_\theta L_\theta
  • QUAD 회귀: 스무스 L1에 길이 정규화 추가

    Lg=minQ~P(Q\*)18NQ\*ismoothL1(cic~i),NQ\*=miniD(pi,pi+1)L_g=\min_{\tilde Q\in \mathcal P(Q^\*)}\frac{1}{8\,N_{Q^\*}} \sum_i \mathrm{smoothL1}(c_i-\tilde c_i),\quad N_{Q^\*}=\min_i D(p_i,p_{i+1})

03-04) Locality-Aware NMS

  • 조밀한 픽셀별 예측으로 후보가 수만 개인 문제를 완화하기 위해, 행 우선(row-first)으로 순차 병합
    • 최선 O(n) 성능. 가중 평균으로 투표 효과를 내 안정성 향상

03-05) 학습 및 데이터

  • 학습: ADAM, 512×512 크롭, 배치 24, 초기 LR 1e−3에서 단계적 감소
  • 데이터셋 메모: ICDAR2015(사변형 주석), COCO-Text(AABB), MSRA-TD500(문장 단위, 한중 혼합).

04. 구현 예시

04-01) 컨투어 기반 파이프라인(전처리 + 후보 추출)

  • 위의 otsu Thresholding 코드 참조
  • 추가 팁: 작은 글자 연결에는 클로징, 큰 글자 내부 빈공간 제거에는 오프닝이 유리

04-02) OpenCV DNN으로 EAST 추론(사전학습 PB 사용)

  • 모델: frozen_east_text_detection.pb (입력 크기는 32의 배수 권장: 320×320, 512×512 등)
import cv2 as cv
import numpy as np

# 1) 모델 로드
net = cv.dnn.readNet("frozen_east_text_detection.pb")
layerNames = [
    "feature_fusion/Conv_7/Sigmoid",   # score map (1ch)
    "feature_fusion/concat_3"          # geometry map (5ch: RBOX)
]

# 2) 전처리(blobs)
img  = cv.imread("scene.jpg")
H, W = img.shape[:2]
inpW, inpH = 320, 320  # 32의 배수
blob = cv.dnn.blobFromImage(img, 1.0, (inpW, inpH),
                            (123.68,116.78,103.94), swapRB=True, crop=False)

# 3) 추론
net.setInput(blob)
(scores, geometry) = net.forward(layerNames)  # scores: (1,1,h,w), geometry: (1,5,h,w)

# 4) 디코딩(RBOX)
def decode(scores, geom, scoreThr=0.5):
    numRows, numCols = scores.shape[2:4]
    boxes, confidences = [], []
    for y in range(numRows):
        scoresData = scores[0,0,y]
        x0 = geom[0,0,y]; x1 = geom[0,1,y]; x2 = geom[0,2,y]; x3 = geom[0,3,y]
        angles = geom[0,4,y]
        for x in range(numCols):
            if scoresData[x] < scoreThr: 
                continue
            angle = angles[x]
            cos, sin = np.cos(angle), np.sin(angle)
            h = x0[x] + x2[x]; w = x1[x] + x3[x]
            # feature map stride = 4
            offsetX, offsetY = x*4.0, y*4.0
            endX = int(offsetX + (cos * x1[x]) + (sin * x2[x]))
            endY = int(offsetY - (sin * x1[x]) + (cos * x2[x]))
            startX = int(endX - w); startY = int(endY - h)
            boxes.append((startX, startY, endX, endY))
            confidences.append(float(scoresData[x]))
    return boxes, confidences

boxes, confs = decode(scores, geometry, 0.5)

# 5) NMS 및 원본 좌표 보정
indices = cv.dnn.NMSBoxes(
    [cv.Rect(b[0],b[1],b[2]-b[0],b[3]-b[1]) for b in boxes], 
    confs, 0.5, 0.4
)

rW, rH = W/float(inpW), H/float(inpH)
for i in indices:
    i = int(i)
    (sx, sy, ex, ey) = boxes[i]
    x1, y1, x2, y2 = int(sx*rW), int(sy*rH), int(ex*rW), int(ey*rH)
    cv.rectangle(img, (x1,y1), (x2,y2), (0,255,0), 2)

cv.imwrite("east_result.png", img)
  • 참고 : EAST 원 논문은 score 1채널 + geometry 다채널(RBOX 5, QUAD 8)을 예측하고 NMS로 합칩니다. OpenCV 제공 모델은 보통 RBOX(5ch)를 사용

05. 기법 선택 가이드

  • 컨투어 기반(형태학+Otsu)
    • 장점: 빠르고 의존 라이브러리 적음, 스캔/문서 이미지에 강함
    • 단점: 복잡한 배경, 다양한 각도/원근 왜곡에서 민감
  • EAST
    • 장점: 회전/사변형 텍스트에 강하고, 단순 2단계 파이프라인으로 정확도와 속도 동시 확보(ICDAR2015에서 13.2fps @720p, F1=0.782)
    • 단점: 모델 크기/추론 리소스가 필요, 매우 긴 라인텍스트나 수직텍스트에서 제약 존재(수용영역, 학습분포 영향)
profile
2025화이팅!

0개의 댓글