๐ ๋ณธ ์์ ๋ Window10์ VSCode, Python3.11.0๋ก ์์ฑ๋์์ต๋๋ค.
Trapped-ball Segmentation์ ์ฃผ๋ก ๋งํ ํ๋ ์๊ณผ ๊ฐ์ ์ด๋ฏธ์ง์์ ์๋ฏธ ์๋ ์์ญ์ ์ถ์ถํ๋๋ฐ ํจ๊ณผ์ ์ธ ๋น์ง๋ ์ด๋ฏธ์ง ๋ถํ ๊ธฐ๋ฒ์ด๋ค.
์ด ๋ฐฉ๋ฒ์ ์ด๋ฏธ์ง์ ๊ฒฝ๊ณ์ ์ ์ํด ์ ์๋ ๋ซํ ์ง์ญ์ ์๋ณํ๊ณ ์ด๋ฅผ ํตํด ์ด๋ฏธ์ง ๋ด์์ ๋ถํ ์ ์ํํ๋ค.
์ด ์๊ณ ๋ฆฌ์ฆ์ ํํํ์ ์ฐ์ฐ์ ์ฌ์ฉํ์ฌ ํฐ ํ์๋ ์ง์ญ์ ์๋ณํ๊ณ , ๊ฐ ์ง์ญ์ ๋ํด ๊ณ ์ ํ ๊ฐ์ ์ฑ์ฐ๋ ๋ฐฉ์์ผ๋ก ์๋ํ๋ค.
๋์ ์์
- ํ๋ฐฑ ๋๋ ์์์ด ์๋ ์ค์ผ์น ์ด๋ฏธ์ง๋ฅผ ํ๊น์ผ๋ก ์ค๋นํ๋ค.
- ํํํ์ ์ฐ์ฐ(Morphological Operations)์ ์ํ(์นจ์(Erosion), ํฝ์ฐฝ(Dilation)ํ์ฌ ๊ฐ ์์ญ์ ๊ฒฝ๊ณ๋ฅผ ์ ๋๋ฌ๋ด๋๋ก ํ๋ค.
- ๋น๊ต์ ๋์ ์์ญ์ ์ฐพ๊ณ , ํน์ ํฌ๊ธฐ ๋๋ ๋ชจ์์ ๊ธฐ๋ฐํ์ฌ ์์ญ์ ํํฐ๋งํ๋ค.
- ์ฐพ์๋ธ ์์ญ์ ๊ณ ์ ํ ๊ฐ์ผ๋ก ์ฑ์์ง๊ณ , ์ด ๊ณผ์ ์ ํตํด ํฐ ์ง์ญ๋ค์ด ๋ช ํํ๊ฒ ์๊ฐํ ๋๋ค.
- ํฐ ์์ญ๋ค์ด ์ ๋ถ ์ฑ์์ง๊ณ , ๋ ์์ ์์ญ์ผ๋ก ์ธ๋ถํํ๋ฉด์ ์ฑ์ฐ๊ธฐ๋ฅผ ๋ฐ๋ณตํ๋ค. (์ด ์์ ์ ์ด๊ธฐ ๋จ๊ณ์ ์ค์ ํ ๊ธฐ์ค์ ๋ฐ๋ผ ๋ฐ๋ณตํ๊ณ , ๋ชจ๋ ์์ญ์ด ๋ค ์ฑ์์ง ๋๊น์ง ๋ฐ๋ณตํ๋ค.)
- ์ต์ข ์ ์ผ๋ก ์ธ๋ถํ๋ ์ด๋ฏธ์ง๋ฅผ ์ถ๋ ฅํ๋ค.
๋์์ ํ๋ํ๋ ๋ฐ๋ผ๊ฐ๋ณด์.
๋จผ์ ํ๊น ์ด๋ฏธ์ง๋ฅผ Threshold๋ก ํ๋ฐฑ ์ด๋ฏธ์ง๋ก ๋ณ๊ฒฝํ๋ค.
img = cv2.imread("./test.jpg",cv2.IMREAD_GRAYSCALE)
ret, binary = cv2.threshold(image, 220, 255, cv2.THRESH_BINARY)
unfilled = binary
ํํํ์ ์ฐ์ฐ์ผ๋ก ๊ฒฝ๊ณ๋ฅผ ์ ๋๋ฌ๋๊ฒ ์ฒ๋ฆฌํ๋ค.
def get_unfilled_point(image: np.ndarray)->np.ndarray:
"""๊ฐ์ด 255์ธ ์์ญ(๋น ์์ญ)์ ํด๋นํ๋ ํฌ์ธํธ๋ฅผ ๊ฐ์ ธ์จ๋ค.
# ์ธ์
image: ์ฒ๋ฆฌํ ์ด๋ฏธ์ง.
# ๋ฐํ ๊ฐ
๋น ์์ญ(์ ํ๊ฐ ์๋ ์์ญ)์ ๋ํ ํฌ์ธํธ ๋ฐฐ์ด.
"""
y, x = np.where(image == 255) # ์ด๋ฏธ์ง์์ ๊ฐ์ด 255์ธ ํฝ์
์ x,y ์ขํ๋ฅผ ์ฐพ๋๋ค.
return np.stack((x.astype(int), y.astype(int)), axis=-1) # x,y ์ขํ๋ฅผ ๊ฒฐํฉํ์ฌ ํฌ์ธํธ ๋ฐฐ์ด์ ์์ฑํ๋ค.
def exclude_area(image:np.ndarray, radius)->np.ndarray:
"""์ด๋ฏธ์ง์์ ๊ฒฝ๊ณ์ ๊ฐ๊น์ด ํฌ์ธํธ๋ฅผ ์ ์ธํ๊ธฐ ์ํด ์นจ์์ ์ํํ๋ค.
ํฝ์ฐฝ ํ ์๋ ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ์ฌ ์์ญ์ ์ฑ์ฐ๊ณ ์ ํ๋ค.
์๋ ํฌ์ธํธ๊ฐ ๊ฒฝ๊ณ ๊ทผ์ฒ์ ์์ ๊ฒฝ์ฐ, ์ฑ์ฐ๊ธฐ ๊ฒฐ๊ณผ์ ํฌํจ๋์ง ์์ ์ ์์ผ๋ฉฐ,
๋ค์ ์ฑ์ฐ๊ธฐ ์์
์ ์ํ ์ ํจํ ํฌ์ธํธ๊ฐ ๋์ง ์์ ์ ์๋ค. ๋ฐ๋ผ์,
์ด๋ฌํ ํฌ์ธํธ๋ ์นจ์์ ํตํด ๋ฌด์ํ๋ค.
# Arguments
image: ์ด๋ฏธ์ง
radius: ๊ตฌํ ๊ตฌ์กฐ ์์์ ๋ฐ์ง๋ฆ
# Returns
์นจ์๋ ์ด๋ฏธ์ง
"""
return cv2.morphologyEx(image, cv2.MORPH_ERODE, get_ball_structuring_element(radius), anchor=(-1, -1), iterations=1)
๊ทธ๋ฆฌ๊ณ ํฐ์์ญ์ ์ฐพ๊ธฐ ์ํด ๊ตฌํ ๊ตฌ์กฐ ์์๋ฅผ ๋ง๋ค๊ณ ์์ญ์ ํ์ํ๋ค.
def get_ball_structuring_element(radius:int)->np.ndarray:
"""ํน์ ๋ฐ์ง๋ฆ์ ๊ฐ์ง ๊ตฌํ ๊ตฌ์กฐ ์์๋ฅผ ๊ฐ์ ธ์ค๋ ํจ์.
์ด ๊ตฌ์กฐ ์์๋ ํํํ์ ์์
(morphology operation)์์ ์ฌ์ฉ๋๋ค.
๊ตฌ์ ๋ฐ์ง๋ฆ์ ์ผ๋ฐ์ ์ผ๋ก (leaking_gap_size / 2)์ ๊ฐ๋ค.
# Arguments
radius: ๊ตฌํ ๊ตฌ์กฐ ์์์ ๋ฐ์ง๋ฆ
# Returns
๊ตฌํ ๊ตฌ์กฐ ์์์ ๋ฐฐ์ด
"""
return cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * radius + 1, 2 * radius + 1))
def trapped_ball_fill_single(image:np.ndarray, seed_point:tuple[int,int], radius:int)->np.ndarray:
"""๋จ์ผ Trapped-ball ์ฑ์ฐ๊ธฐ ์์
์ ์ํํ๋ ํจ์
Flood Fill : ๋ค์ฐจ์ ๋ฐฐ์ด์ ์ด๋ค ์นธ๊ณผ ์ฐ๊ฒฐ๋ ์์ญ์ ์ฐพ๋ ์๊ณ ๋ฆฌ์ฆ.
# Arguments
image: ํฐ์(๋น ์์ญ) ๋ฐฐ๊ฒฝ์ ๊ฒ์(์ฑ์์ง) ์ ํ๋ฅผ ๊ฐ์ง ์ด๋ฏธ์ง
seed_point: ๊ฐํ ๊ณต ์ฑ์ฐ๊ธฐ์ฉ ์๋ ํฌ์ธํธ, ํํ ํ์(int,int)
radius: ๊ตฌํ ๊ตฌ์กฐ ์์์ ๋ฐ์ง๋ฆ
# Returns
์ฑ์ด ์ด๋ฏธ์ง
"""
ball = get_ball_structuring_element(radius) # radius๋ก ๊ตฌํ ๊ตฌ์กฐ ์์ ์์ฑ
pass1 = np.full(image.shape, 255, np.uint8) # ์ ์ฒด๊ฐ ํฐ์์ธ ๋ฐฐ์ด
pass2 = np.full(image.shape, 255, np.uint8) # ์ ์ฒด๊ฐ ํฐ์์ธ ๋ฐฐ์ด
im_inv = cv2.bitwise_not(image) # ์ด๋ฏธ์ง ์์ ๋ฐ์
# Flood fill ์ํ
mask1 = cv2.copyMakeBorder(im_inv, 1, 1, 1, 1, cv2.BORDER_CONSTANT, 0) # ๊ฒฝ๊ณ ์ถ๊ฐ
_, pass1, _, _ = cv2.floodFill(pass1, mask1, seed_point, 0, 0, 0, 4) # flood fill
# ์ด๋ฏธ์ง ํฝ์ฐฝ(dilation) ์ํ, ๊ฐญ ์ฌ์ด์ ์์ญ์ด ๋ถ๋ฆฌ๋จ.
pass1 = cv2.morphologyEx(pass1, cv2.MORPH_DILATE, ball, anchor=(-1, -1), iterations=1)
mask2 = cv2.copyMakeBorder(pass1, 1, 1, 1, 1, cv2.BORDER_CONSTANT, 0) # ๊ฒฝ๊ณ ์ถ๊ฐ
# ๋ค์ ์๋ ํฌ์ธํธ๋ก Flood fill ์ํํ์ฌ ํ๋์ ์ฑ์ ์์ญ ์ ํ
_, pass2, _, rect = cv2.floodFill(pass2, mask2, seed_point, 0, 0, 0, 4)
# ์ฑ์ ๊ฒฐ๊ณผ์ ๋ํ ์นจ์(erosion) ์ํํ์ฌ ๋์ ๋ฐฉ์ง ์ฑ์
pass2 = cv2.morphologyEx(pass2, cv2.MORPH_ERODE, ball, anchor=(-1, -1), iterations=1)
return pass2
def trapped_ball_fill_multi(image:np.ndarray, radius:int, method:str='mean', max_iter:int=1000)->np.ndarray:
"""์ฌ๋ฌ๋ฒ์ ๊ฐํ ๊ณต ์ฑ์ฐ๊ธฐ(Trapped-Ball Fill) ์์
์ ์ํํ์ฌ ๋ชจ๋ ์ ํจํ ์์ญ์ ์ฑ์ด๋ค.
# Arguments
image: ํฐ์(๋น ์์ญ) ๋ฐฐ๊ฒฝ์ ๊ฒ์(์ฑ์์ง) ์ ํ๋ฅผ ๊ฐ์ง ์ด๋ฏธ์ง
radius: ๊ตฌํ ๊ตฌ์กฐ ์์์ ๋ฐ์ง๋ฆ
method: ์ฑ์ฐ๊ธฐ ๊ฒฐ๊ณผ ํํฐ๋ง ๋ฐฉ๋ฒ
'max' ๋ ์ผ๋ฐ์ ์ผ๋ก ํฐ ๋ฐ์ง๋ฆ์ ์ฌ์ฉํ์ฌ ๊ฐ์ ํฐ ์์ญ์ ์ ํ
max_iter: ์ต๋ ๋ฐ๋ณต ํ์
# Returns
์ฑ์ด ํฌ์ธํธ ๋ฐฐ์ด
"""
unfill_area = image # ์ฑ์ฐ์ง ์์ ์์ญ์ ์ฐ๋๋ณธ ์ด๋ฏธ์ง๋ก ์ด๊ธฐํ
filled_area, filled_area_size, result = [], [], []
for _ in range(max_iter):
# ๊ฒฝ๊ณ์ ๊ฐ๊น์ด ํฌ์ธํธ๋ฅผ ์ ์ธํ ํ ๋น ์์ญ์ ํฌ์ธํธ๋ฅผ ๊ฐ์ก์ด
points = get_unfilled_point(exclude_area(unfill_area, radius))
# ๋น ํฌ์ธํธ๊ฐ ์์ผ๋ฉด ์ข
๋ฃ
if not len(points) > 0:
break
# ์ฒซ ๋ฒ์งธ ํฌ์ธํธ๋ฅผ ๊ธฐ์ค์ผ๋ก Trapped-Ball Fill ์ํ
fill = trapped_ball_fill_single(unfill_area, (points[0][0], points[0][1]), radius)
# ์ฑ์ด ์์ญ๊ณผ ์๋ณธ ์ด๋ฏธ์ง์ ๋นํธ AND ์ฐ์ฐ ์ํ
unfill_area = cv2.bitwise_and(unfill_area, fill)
# ์ฑ์ด ์์ญ์ ํฌ์ธํธ๋ฅผ ์ ์ฅ
filled_area.append(np.where(fill == 0))
filled_area_size.append(len(np.where(fill == 0)[0]))
filled_area_size = np.asarray(filled_area_size) # ์ฑ์ด ์์ญ์ ํฌ๊ธฐ๋ฅผ ๋ฐฐ์ด๋ก ๋ฐ์
# ํํฐ๋ง
"""
## max
- ์ฑ์ด ์์ญ์ ํฌ๊ธฐ ์ค ์ต๋๊ฐ์ ๊ธฐ์ค์ผ๋ก ํํฐ๋ง, ๊ฐ์ฅ ํฐ์์ญ์ ์ ํํ์ฌ ๊ฒฐ๊ณผ์ ํฌํจ, ๊ทธ๋ณด๋ค ์์ ์์ญ์ ์ ์ธ
- ์ผ๋ฐ์ ์ผ๋ก ํฐ ๋ฐ์ง๋ฆ์ ์ฌ์ฉํ ๋ ์ ์ฉํ๋ฉฐ, ๋ฐฐ๊ฒฝ ๊ฐ์ ํฐ ์์ญ์ ์ ํํ ๋ ์ ํฉ
- ์ด ๋ฐฉ๋ฒ์ ๋
ธ์ด์ฆ๊ฐ ์์ ์ฑ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์ํ๊ณ , ์ฃผ์ ์ฑ์ด ์์ญ๋ง์ ์ถ์ถํ๊ณ ์ ํ ๋ ์ฌ์ฉ
## median
- ์ฑ์ด ์์ญ์ ํฌ๊ธฐ ์ค ์ค์๊ฐ์ ๊ธฐ์ค์ผ๋ก ํํฐ๋ง, ์ค์๊ฐ์ ๋ฐฐ์ด์ ์ ๋ ฌํ์ ๋, ์ค๊ฐ๊ฐ์ ์์นํ ๊ฐ
- ๊ทน๋จ์ ์ธ ๊ฐ์ ๋ ์ํฅ์ ๋ฐ๊ธฐ ๋๋ฌธ์, ์ด์์น๊ฐ ์๋ ๊ฒฝ์ฐ์๋ ์์ ์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณต
- ๋ค์ํ ํฌ๊ธฐ์ ์ฑ์ด ์์ญ์ด ์กด์ฌํ ๋, ์ค๊ฐ ์ ๋์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง ์์ญ์ ์ ํํ๊ณ ์ ํ ๋ ์ฌ์ฉ
- ํฌ๊ธฐ๊ฐ ๋งค์ฐ ์์ ์์ญ๊ณผ ๋งค์ฐ ํฐ ์์ญ์ด ํผ์ฌํ ๋, ์ค๊ฐ ๊ท๋ชจ์ ์ฑ์ด ์์ญ์ ์ ํํ๋ ๋ฐ ์ ์ฉ
## mean
- ์ฑ์ด ์์ญ์ ํฌ๊ธฐ ํ๊ท ๊ฐ์ ๊ธฐ์ค์ผ๋ก ํํฐ๋ง
- ๋ชจ๋ ์ฑ์ด์์ญ์ ํฌ๊ธฐ๋ฅผ ๋ํ ํ, ์ฑ์ด ์์ญ์ ๊ฐ์๋ก ๋๋ ๊ฐ์ ์ฌ์ฉํ์ฌ ํํฐ๋ง ๊ธฐ์ค์ ์ค์
- ํ๊ท ์ ์ธ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง ์ฑ์ด ์์ญ์ ์ ํํ๊ณ ์ ํ ๋ ์ฌ์ฉ
- ์ ์ฒด์ ์ธ ์ฑ์ด ๊ฒฐ๊ณผ์ ํฌ๊ธฐ๊ฐ ๋น์ทํ ๋ ์ ํฉํ๋ฉฐ, ํฌ๊ธฐ๊ฐ ํฌ๊ฒ ์ฐจ์ด๋์ง ์๋ ๊ฒฝ์ฐ์ ์ ์ฉ
"""
if method == 'max':
area_size_filter = np.max(filled_area_size)
elif method == 'median':
area_size_filter = np.median(filled_area_size)
elif method == 'mean':
area_size_filter = np.mean(filled_area_size)
else:
area_size_filter = 0
# ํํฐ๋ง๋ ์์ญ์ ์ธ๋ฑ์ค๋ฅผ ์ฐพ๋๋ค.
result_idx = np.where(filled_area_size >= area_size_filter)[0]
# ํํฐ๋ง ๊ฒฐ๊ณผ ๋ฆฌ์คํธ์ ์ถ๊ฐ
for i in result_idx:
result.append(filled_area[i])
return result # ์ฑ์ด ํฌ์ธํธ์ ๋ฐฐ์ด ๋ฐํ
๋ ์์ ์์ญ์ ์ธ๋ถํ ํ๋ฉด์ ์์ ํ๋ค.
import cv2
import numpy as np
img = cv2.imread("./test.jpg",cv2.IMREAD_GRAYSCALE)
ret, binary = cv2.threshold(image, 220, 255, cv2.THRESH_BINARY)
unfilled = binary
radius = radius
fills = []
for idx, method in enumerate(methods):
fill = trapped_ball_fill_multi(image=unfilled,radius=3,method=method)
fills+=fill
unfilled = mark_fill(unfilled,fills)
fill = flood_fill_multi(unfilled)
fills += fill
fillmap = build_fill_map(unfilled, fills)
fillmap = merge_fill(fillmap)
cv2.imshow(str(idx),unfilled)
์ด๋ ๊ฒ ์ฑ์ด ์์ญ ๋งต์ id๋ฅผ ์ค์ ๋ถํ ํ๋ค.
์ด๋ ๊ฒ ํ๋ฉด ์์ญ๋ณ๋ก ๊ตฌ๋ถ์ด ๋๋ค.
def build_fill_map(image: np.ndarray, fills: list) -> np.ndarray:
"""๊ฐ ํฝ์
์ ์ฑ์ด ์์ญ์ ID๋ก ํ์ํ๋ ๋ฐฐ์ด์ ์์ฑํ๋ ํจ์.
# Arguments
image: ์ฒ๋ฆฌํ ์ด๋ฏธ์ง.
fills: ์ฑ์ด ์์ญ์ ํฌ์ธํธ ๋ฐฐ์ด. ๊ฐ ํฌ์ธํธ๋ (y, x) ํํ์ ์ขํ๋ฅผ ํฌํจํด์ผ ํจ.
# Returns
๊ฐ ํฝ์
์ ์ฑ์ด ์์ญ์ ID๊ฐ ํ์๋ ๋ฐฐ์ด.
"""
result = np.zeros(image.shape[:2], np.int_) # ๊ฒฐ๊ณผ ๋ฐฐ์ด ์ด๊ธฐํ, ๋ชจ๋ ๊ฐ์ 0
for index, fill in enumerate(fills):
result[fill] = index + 1 # ์ฑ์ด ์์ญ์ ID๋ก ํ์
return result # ๊ฒฐ๊ณผ ๋ฐฐ์ด ๋ฐํ
๊ทธ๋ฆฌ๊ณ ์ด ์ฑ์ ๋งต์ ์์ญ ๋ณํฉ์ ํ๋ฉด์ ์์ญ ์ธ๋ถํ๋ฅผ ๋ฐ๋ณตํ๋ค.
def get_bounding_rect(points:np.ndarray)->tuple:
"""์ฃผ์ด์ง ํฌ์ธํธ ์งํฉ์ ์ต์ ๋ฐ ์ต๋ x,y ์ขํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ฒฝ๊ณ ์ฌ๊ฐํ์ ๊ณ์ฐ
# Arguments
points: (y,x) ํํ์ ๋ฐฐ์ด
# Returns
์ฌ๊ฐํ ํํ์ ๊ฒฝ๊ณ ์ฌ๊ฐํ ์ขํ
"""
x1, y1, x2, y2 = np.min(points[1]), np.min(points[0]), np.max(points[1]), np.max(points[0])
return (x1, y1, x2, y2)
def get_border_bounding_rect(h:int, w:int, p1:tuple, p2:tuple, r:int)->tuple:
""" ์ฃผ์ด์ง ๊ฒฝ๊ณ ์ฌ๊ฐํ์ ํน์ ํฌ๊ธฐ์ ์ฌ๋ฐฑ์ ์ถ๊ฐํ์ฌ ์ ํจํ ๊ฒฝ๊ณ ์ฌ๊ฐํ์ ๊ณ์ฐ
๊ฒฝ๊ณ๊ฐ ์ด๋ฏธ์ง์ ํฌ๊ธฐ๋ฅผ ์ด๊ณผํ์ง ์๋๋ก ์กฐ์
# Arguments
h: ์ด๋ฏธ์ง์ ์ต๋ ๋์ด
w: ์ด๋ฏธ์ง์ ์ต๋ ๋๋น
p1: ๊ฒฝ๊ณ ์ฌ๊ฐํ์ ์์ ์
p2: ๊ฒฝ๊ณ ์ฌ๊ฐํ์ ๋ ์
r: ์ฌ๋ฐฑ์ ๋ฐ๊ฒฝ
# Returns
์ฌ๋ฐฑ์ด ์ถ๊ฐ๋ ๊ฒฝ๊ณ ์ฌ๊ฐํ์ ์ขํ๋ฅผ ๋ฐํ
"""
x1, y1, x2, y2 = p1[0], p1[1], p2[0], p2[1]
x1 = x1 - r if 0 < x1 - r else 0
y1 = y1 - r if 0 < y1 - r else 0
x2 = x2 + r + 1 if x2 + r + 1 < w else w
y2 = y2 + r + 1 if y2 + r + 1 < h else h
return (x1, y1, x2, y2)
def get_border_point(points:list, rect:tuple, max_height:int, max_width:int)->tuple:
"""์ฃผ์ด์ง ํฌ์ธํธ์ ๊ฒฝ๊ณ ํฌ์ธํธ์ ๊ทธ ํํ๋ฅผ ๊ณ์ฐ
๊ฒฝ๊ณ ์ฌ๊ฐํ์ ์ฌ๋ฐฑ์ ์ถ๊ฐํ๊ณ , ํด๋น ์์ญ์์ ๊ฒฝ๊ณ ํฝ์
์ ์ถ์ถ
# Arguments
points: ์ฑ์ด ์์ญ์ ํฌ์ธํธ
rect: ์ฑ์ด ์์ญ์ ๊ฒฝ๊ณ ์ฌ๊ฐํ
max_height: ์ด๋ฏธ์ง ์ต๋ ๋์ด
max_width: ์ด๋ฏธ์ง ์ต๋ ๋๋น
# Returns
๊ฒฝ๊ณ ํฝ์
์ขํ์ ํฌ์ธํธ์ ๋ค๊ฐํ ํํ๋ฅผ ๋ฐํ
"""
# Get a local bounding rect.
border_rect = get_border_bounding_rect(max_height, max_width, rect[:2], rect[2:], 2)
# Get fill in rect.
fill = np.zeros((border_rect[3] - border_rect[1], border_rect[2] - border_rect[0]), np.uint8)
# Move points to the rect.
fill[(points[0] - border_rect[1], points[1] - border_rect[0])] = 255
# Get shape.
contours, _ = cv2.findContours(fill, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
approx_shape = cv2.approxPolyDP(contours[0], 0.02 * cv2.arcLength(contours[0], True), True)
# Get border pixel.
cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
border_pixel_mask = cv2.morphologyEx(fill, cv2.MORPH_DILATE, cross, anchor=(-1, -1), iterations=1) - fill
border_pixel_points = np.where(border_pixel_mask == 255)
# Transform points back to fillmap.
border_pixel_points = (border_pixel_points[0] + border_rect[1], border_pixel_points[1] + border_rect[0])
return border_pixel_points, approx_shape
def merge_fill(fillmap: np.ndarray, max_iter: int = 10) -> np.ndarray:
"""์ฑ์ด ์์ญ ๋ณํฉ ํจ์.
# Arguments
fillmap: ์ฑ์ด ์์ญ ID๊ฐ ํ์๋ ์ด๋ฏธ์ง.
max_iter: ์ต๋ ๋ฐ๋ณต ํ์.
# Returns
๋ณํฉ๋ ์ฑ์ด ์์ญ ์ด๋ฏธ์ง.
"""
max_height, max_width = fillmap.shape[:2]
result = fillmap.copy() # ๊ฒฐ๊ณผ ๋ฐฐ์ด ์ด๊ธฐํ
for i in range(max_iter):
result[np.where(fillmap == 0)] = 0 # ๋น ์์ญ์ 0์ผ๋ก ์ค์
fill_id = np.unique(result.flatten()) # ํ์ฌ ์ฑ์ด ์์ญ ID ์ถ์ถ
fills = []
for j in fill_id:
point = np.where(result == j)
fills.append({
'id': j,
'point': point,
'area': len(point[0]),
'rect': get_bounding_rect(point)
})
for j, f in enumerate(fills):
# ๋ผ์ธ ID๋ ๋ฌด์
if f['id'] == 0:
continue
border_points, approx_shape = get_border_point(f['point'], f['rect'], max_height, max_width)
border_pixels = result[border_points]
pixel_ids, counts = np.unique(border_pixels, return_counts=True)
ids = pixel_ids[np.nonzero(pixel_ids)]
new_id = f['id']
if len(ids) == 0:
# ์ฃผ๋ณ์ ์์์ด ๋ณ๊ฒฝ๋ ๋ผ์ธ์ผ๋ก ๋๋ฌ์ธ์ธ ๊ฒฝ์ฐ
if f['area'] < 5:
new_id = 0
else:
# ๊ฐ์ฅ ์ ์ด์ด ๋ง์ ์์ญ์ ID๋ก ์ค์
new_id = ids[0]
# ์กฐ๊ฑด์ ๋ฐ๋ผ ID ์
๋ฐ์ดํธ
if len(approx_shape) == 1 or f['area'] == 1:
result[f['point']] = new_id
if len(approx_shape) in [2, 3, 4, 5] and f['area'] < 500:
result[f['point']] = new_id
if f['area'] < 250 and len(ids) == 1:
result[f['point']] = new_id
if f['area'] < 50:
result[f['point']] = new_id
# ๋ณํ๊ฐ ์์ผ๋ฉด ์ข
๋ฃ
if len(fill_id) == len(np.unique(result.flatten())):
break
return result # ๋ณํฉ๋ ๊ฒฐ๊ณผ ๋ฐํ
์ต์ข ์ ์ผ๋ก ๊ธฐ์กด ์ ํ ์ด๋ฏธ์ง์์ ์ ํ๋ฅผ ์ง์ฐ๊ณ ์์ญ๋ณ๋ก ๋ค๋ฅธ ์์ผ๋ก ์น ํ์ฌ ๋ฐ์์ ๋ฐํ ํ๋ค.
def thinning(fillmap: np.ndarray, max_iter: int = 100) -> np.ndarray:
"""์ ์์ญ์ ์ฃผ๋ณ ์ฑ์ด ์์์ผ๋ก ์ฑ์ฐ๋ ํจ์.
# Arguments
fillmap: ์ฑ์ด ์์ญ ID๊ฐ ํ์๋ ์ด๋ฏธ์ง.
max_iter: ์ต๋ ๋ฐ๋ณต ํ์.
# Returns
์ฑ์ด ํ์ ์ด๋ฏธ์ง.
"""
line_id = 0 # ๊ธฐ๋ณธ ์ ID
h, w = fillmap.shape[:2] # ์ด๋ฏธ์ง ๋์ด์ ๋๋น
result = fillmap.copy() # ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ๋ฐฐ์ด ์ด๊ธฐํ
for iterNum in range(max_iter):
# ์ ์ ํฌ์ธํธ๋ฅผ ์ฐพ๊ธฐ. ํฌ์ธํธ๊ฐ ์์ผ๋ฉด ์ข
๋ฃ.
line_points = np.where(result == line_id)
if not len(line_points[0]) > 0:
break
# ์ ๊ณผ ์ฑ์ด ์์ญ ์ฌ์ด์ ํฌ์ธํธ๋ฅผ ์ฐพ๊ธฐ.
line_mask = np.full((h, w), 255, np.uint8) # ์ ๋ง์คํฌ ์ด๊ธฐํ
line_mask[line_points] = 0 # ์ ์ ํฌ์ธํธ๋ฅผ 0์ผ๋ก ์ค์
line_border_mask = cv2.morphologyEx(line_mask, cv2.MORPH_DILATE,
cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3)), anchor=(-1, -1),
iterations=1) - line_mask # ์ ์ ๊ฒฝ๊ณ ํฌ์ธํธ ์ฐพ๊ธฐ
line_border_points = np.where(line_border_mask == 255) # ๊ฒฝ๊ณ ํฌ์ธํธ ์ฐพ๊ธฐ
result_tmp = result.copy() # ์์ ๊ฒฐ๊ณผ ๋ฐฐ์ด ์์ฑ
# ๊ฐ ๊ฒฝ๊ณ ํฌ์ธํธ๋ฅผ ์ฃผ๋ณ ์ฑ์ด ์์ญ์ ID๋ก ์ฑ์
for i, _ in enumerate(line_border_points[0]):
x, y = line_border_points[1][i], line_border_points[0][i]
# ์ฃผ๋ณ ํฝ์
์ ํ์ธํ์ฌ ์ฑ์
if x - 1 > 0 and result[y][x - 1] != line_id:
result_tmp[y][x] = result[y][x - 1]
continue
if x - 1 > 0 and y - 1 > 0 and result[y - 1][x - 1] != line_id:
result_tmp[y][x] = result[y - 1][x - 1]
continue
if y - 1 > 0 and result[y - 1][x] != line_id:
result_tmp[y][x] = result[y - 1][x]
continue
if y - 1 > 0 and x + 1 < w and result[y - 1][x + 1] != line_id:
result_tmp[y][x] = result[y - 1][x + 1]
continue
if x + 1 < w and result[y][x + 1] != line_id:
result_tmp[y][x] = result[y][x + 1]
continue
if x + 1 < w and y + 1 < h and result[y + 1][x + 1] != line_id:
result_tmp[y][x] = result[y + 1][x + 1]
continue
if y + 1 < h and result[y + 1][x] != line_id:
result_tmp[y][x] = result[y + 1][x]
continue
if y + 1 < h and x - 1 > 0 and result[y + 1][x - 1] != line_id:
result_tmp[y][x] = result[y + 1][x - 1]
continue
result = result_tmp.copy() # ์ต์ข
๊ฒฐ๊ณผ ์
๋ฐ์ดํธ
return result # ์ฑ์ด ํ์ ์ด๋ฏธ์ง ๋ฐํ
def show_fill_map(fillmap: np.ndarray) -> np.ndarray:
"""์ฑ์ด ์์ญ์ ์์์ผ๋ก ํ์ํ์ฌ ์๊ฐํํ๋ ํจ์.
# Arguments
fillmap: ์ฑ์ด ์์ญ ID๊ฐ ํฌํจ๋ ์ด๋ฏธ์ง ๋ฐฐ์ด.
# Returns
์์์ด ์ ์ฉ๋ ์ด๋ฏธ์ง ๋ฐฐ์ด.
"""
# ๊ฐ ์ฑ์ด ์์ญ์ ๋ํด ๋๋ค ์์ ์์ฑ
colors = np.random.randint(0, 255, (np.max(fillmap) + 1, 3))
# ์ ์ ID๋ 0์ด๋ฉฐ, ๊ทธ ์์์ ๊ฒ์ ์์ผ๋ก ์ค์
colors[0] = [0, 0, 0]
return colors[fillmap] # ์์์ด ์ ์ฉ๋ ์ด๋ฏธ์ง ๋ฐํ
fill = show_fill_map(thinning(fillmap))
cv2.imshow("fill",cv2.convertScaleAbs(fill))
์ฌ๊ธฐ์ ๋ชจ๋ ์ฝ๋๋ฅผ ํ์ธํ ์ ์๋ค.
'AI > Computer Vision' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Computer Vision] Segmentation (0) | 2024.09.19 |
---|---|
[Computer Vision] Depth (0) | 2024.09.15 |
[Computer Vision] IoU(Intersection over Union) (0) | 2024.09.12 |
[Computer Vision] PIL(Python Image Library) (0) | 2024.09.08 |
[Computer Vision] ์ง์ญ ํน์ง์ ๊ฒ์ถ๊ณผ ๋งค์นญ (0) | 2024.09.07 |