scikit-image는 이미지처리에 특화된 Python 이미지 라이브러리이며 Numpy배열로 이미지 객체를 네이티브하게 다룬다.즉 Numpy배열로 동작한다. 여기에서는 scikit-image가 다양한 이미지 처리 작업에 어떻게 이용되는가, 그리고 Numpy 혹은 Scipy 등의 다른 Python의 기술 모듈과 어떻게 연계되어 있는지에 대해 다룰 것이다.
<참고>
기본적인 이미지 조작, 예를 들면 이미지를 자른다던가 단순한 필터링 등의 조작은 Numpy나 SciPy로도 적용 가능하다. 그에 대한 내용은 아래의 링크에서 확인할 수 있다.
http://www.turbare.net/transl/scipy-lecture-notes/advanced/image_processing/index.html#basic-image
<목차> 1. 도입과 컨셉 1) scikit-image와 SciPy의 에코 시스템 2) scikit-image로 할 수 있는 것들 2. 입출력, 데이터형 또는 색 공간 1) 데이터형 2) 색 공간( ; 컬러 스페이스) 3. 이미지 전처리 / 보간 1) 국소 필터 2) 비국소 필터 3) 수리형태학 4. 이미지의 분할 1) 2차분할 : 전면 + 후면 2) 마커를 사용한 방법 5. 영역의 특정을 규정하는 것 6. 데이터의 가시화와 대화 조작 7. 컴퓨터 비전을 위한 특징 추출 |
1. 도입과 컨셉
이미지는 Numpy 배열 np.ndarray
image | np.ndarray |
pixels | a[2,3] |
channels | 배열의 차원 |
image encoding | dtype(np.unit8, np.unit16, np.float) |
filters | 함수(numpy, skimage, scipy) |
import numpy as np
check = np.zeros((9, 9))
check[::2, 1::2] = 1
check[1::2, ::2] = 1
import matplotlib.pyplot as plt
plt.imshow(check, cmap='gray', interpolation='nearest')
1) scikit-image와 SciPy 에코 시스템
scikit-image의 최신 버전은 Anaconda이나 Enthought Canopy와 같이 Python 과학기술 디스트리뷰션을 거의 포함하고 있다. Ubuntu/Debian용 패키지도 있다.
import skimage
from skimage import data # most functions are in subpackages
거의 모든 scikit-image함수는 Numpy의 ndarrays를 인수로 한다.
camera = data.camera()
camera.dtype
>>> dtype('uint8')
camera.shape
>>> (512, 512)
from skimage import restoration
filtered_camera = restoration.denoise_bilateral(camera)
type(filtered_camera)
>>> <type 'numpy.ndarray'>
다른 Python 패키지도 이미지 처리에 이용하는 것이 가능하며, Numpy 배열과 조합되어 작동한다.
- scipy.ndimage ; nd-arrays와 조합되어 기본적인 필터링이나 수리형태학 조작, 영역의 특성을 취득한다.
- Mahotas
<참고>
마찬가지로 Python과 연결성을 갖는 강력한 이미지 처리 라이브러리
(1) OpenCV(compter vision)
(2) ITK (3D 이미지와 위치 )
(3) 기타 등등
(그러나, 라이브러리들 중 일부는 Python답지 않거나 Numpy와 합쳐 이용하기 어렵거나 등 제 각각이다.)
2) scikit-image로 가능한 것들
공식 scikit-image 사이트 ; http://scikit-image.org/
scikit-image 예제 사이트 ; https://scikit-image.org/docs/stable/auto_examples/
다양한 종류의 함수, 자주 볼 수 있는 유틸리티부터 고수준의 최근 알고리즘까지 가능하다.
- 필터 ; 이미지를 다른 이미지를 변환시키는 함수
1) Numpy의 구조
2) 일반적인 필터링 알고리즘
- 데이터 집약 함수 ; 이미지 히토그램의 계산, 극대치의 위치, 코너의 위치 등
- 그외 조작; 입출력, 가시화 등
2.입출력, 데이터형 또는 색 공간(컬러 스페이스)
입출력 : skimage.io
from skimage import io
파일로부터 이미지 읽어 들이기 : skimage.io.imread()
import os
filename = os.path.join(skimage.data_dir, 'camera.png')
camera = io.imread(filename)
Python Image Library에 지원되는 데이터형식 전부 동작한다.
(혹은 imread의 plugin 키워드 인수를 부여함으로써 제공되는 임의의 입출력 플러그인)
URL로 전달된 이미지의 경로도 동작한다.
logo = io.imread('http://scikit-image.org/_static/img/logo.png')
파일의 저장 :
io.imsave('local_logo.png', logo)
(imsave은 PIL과 같이 외부 플러그인도 이용 가능하다.)
1) 데이터형
이미지의 ndarrays 정수(부호 있음, 부호 없음) 나 부동소수점의 어느 것이라도 표현 가능하다.
정수 데이터형은 오버로드에 주의하자.
camera = data.camera()
camera.dtype
>>> dtype('uint8')
camera_multiply = 3 * camera
다른 정수 사이즈를 사용할 수도 있다 : 8, 16 혹은 32바이트의 부호 있는 것과 없는 것
<주의>
skimage에서의 관습 : 부동소수점의 수는 [-1, 1]의 범위 내라고 가정된다.
(모든 부동소수점의 수로 표현된 이미지의 대비를 비교할 수 있도록)
from skimage import img_as_float
camera_float = img_as_float(camera)
camera.max(), camera_float.max()
>>> (255, 1.0)
몇몇개의 이미지처리 루틴은 작동엥 부동소수점 수의 배열이 필요하므로, 출력배열의 형이나 데이터 범위는 입력배열과 달라질 수 있다.
try:
... from skimage import filters
... except ImportError:
... from skimage import filter as filters
camera_sobel = filters.sobel(camera)
camera_sobel.max()
>>> 0.591502...
<주의>
위의 예에서 scikit-image의 filters 소벨을 이용했다. 그것은 0.10에서 0.11버전 업그레이될 때 filter에서 filters로 이름이 변경되었기 때문이다.이것은 Python의 내장 filter과의 충돌을 피하기 위한 것이다.유틸리티 함수가 skimage로 제공되고, dtype이나 데이터 범위의 모두를 변환한다.변환은 skimage의 관습에 따라 이루어진다.: util.img_as_float, util.img_as_ubyte 등자세한 것은 아래의 user guide를 참고하라.
https://scikit-image.org/docs/stable/user_guide/data_types.html
2) 색 공간(컬러 스페이스)
컬러 이미지는 (N,M,3) 혹은 (N,M,4) (alpha채널에서 투과도가 부호화되어 있는 경우)의 형태를 가진다.
face = scipy.misc.face()
face.shape
>>> (768, 1024, 3)
다른 색 공간(RGB, HSV, LAB)간의 변환은 skimage.color로 가능하다. : color.rgb2hsv, color.lab2rgb 등
docstring에 입력 이미지로서 기대되는 dtype(그리고 데이터 범위)를 확인해보자.
<참고>
skimage의 거의 모든 함수는 3차원 이미지를 입출력 인수로 할 수 있다.
docstring으로 3차원이미지(예를 들면 MRI나 CT 이미지)에 대해서 적용되는 것인지 확인해보자.
3. 이미지의 전처리/ 보간
목표: 노이즈 감소, 특징(엣지) 검출,...
1) 국소 필터
국소 필터는 대상 픽셀에 인접하는 픽셀의 값에 따라 값을 바꾼다. 함수에는 선형 함수와 비선형 함수가 있다.
인접 : 정방형(사이즈를 선택한), 원형 또는 더 복잡한 구조 요소.
예: 수평방향 Sobel 필터
text = data.text()
hsobel_text = filters.hsobel(text)
이하의 선형 커넬을 이용해서 수평방향의 경사를 계산하자:
1 2 1
0 0 0
-1 -2 -1
2) 비국소 필터
비국소 필터는 이미지의 넓은 영역(혹은 이미지 전체)를 이용하여, 하나의 픽셀을 변환한다.
from skimage import exposure
camera = data.camera()
camera_equalized = exposure.equalize_hist(camera)
넓은 범위에서 대비가 거의 동일한 영역을 강조한다.
3) 수리형태학
수리형태학에 관련한 내용은 위키피디아를 참고하라.
이미지의 중잉부부터 단순한 형태(구성요소)를 검출하고, 형태가 국소적으로 될지 안 될지에 따라 이미지를 변경한다.
기본 구성 요소 : 픽셀에 대한 4가지의 연결
from skimage import morphology
morphology.diamond(1)
>>> array([[0, 1, 0],
[1, 1, 1],
[0, 1, 0]], dtype=uint8)
Erosion = 최소 필터, 픽셀을 구성하는 요소를 픽셀 최소치로 바꾼다:
a = np.zeros((7,7), dtype=np.int)
a[1:6, 2:5] = 1
a
>>> array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0]])
morphology.binary_erosion(a, morphology.diamond(1)).astype(np.uint8)
>>> array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
#Erosion removes objects smaller than the structure
morphology.binary_erosion(a, morphology.diamond(2)).astype(np.uint8)
>>> array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
Dilation: 최대 필터;
a = np.zeros((5, 5))
a[2, 2] = 1
a
>>> array([[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
morphology.binary_dilation(a, morphology.diamond(1)).astype(np.uint8)
>>> array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]], dtype=uint8)
Opening = erosion + dilation;
a = np.zeros((5,5), dtype=np.int)
a[1:4, 1:4] = 1; a[4, 4] = 1
a
>>> array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 1]])
morphology.binary_opening(a, morphology.diamond(1)).astype(np.uint8)
>>> array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]], dtype=uint8)
Opening은 작은 객체를 제거하고 코너를 부드럽게 한다.
<참고>
흑백화(그레이스케일화)에 대한 수리형태학
수리형태학의 연산은 그레이 스케일 이미지(정수나 부동소수점)에 대해서도 사용가능하다.
erosion나 dilation는 최소(혹은 최대) 필터를 지원한다.
더욱 고도의 수리형태학 조작도 이용가능하다: tophat, skeletonization등
필터 비교의 예 : 이미지의 노이즈 제거
from skimage.morphology import disk
coins = data.coins()
coins_zoom = coins[10:80, 300:370]
median_coins = filters.rank.median(coins_zoom, disk(1))
from skimage import restoration
tv_coins = restoration.denoise_tv_chambolle(coins_zoom, weight=0.1)
gaussian_coins = filters.gaussian_filter(coins, sigma=2)
4. 이미지의 분할
이미지의 분할이란 이미지의 어떠한 영역에 라벨을 붙이는 것이다.
예를 들어 대상이 되는 복수의 픽셀을 추출할 때 사용된다.
1) 2값 분할 : 전(前)면 + 후(後)면
(1) 히스토그램에 의거한 방법 : 오츠의 이분화
오츠 메소드는 전면과 배경을 분리하는 수치를 알아내는 방법이다.
from skimage import data
from skimage import filters
camera = data.camera()
val = filters.threshold_otsu(camera)
mask = camera < val
(2) 흩어진 이미지와 연결된 요소를 라벨을 붙인다.
일단 전면에 있는 객체를 분할하면, 각각을 다른 객체로 나누는 것이 가능하다.
각각 다른 정수값 라벨을 대입하여 분할 할 수 있다.
인공 데이터:
n = 20
l = 256
im = np.zeros((l, l))
points = l * np.random.random((2, n ** 2))
im[(points[0]).astype(np.int), (points[1]).astype(np.int)] = 1
im = filters.gaussian_filter(im, sigma=l / (4. * n))
blobs = im > im.mean()
모든 연결되어 있는 요소에 라벨을 붙인다.
from skimage import measure
all_labels = measure.label(blobs)
전면에 붙어있는 요소만을 라벨화한다.
blobs_labels = measure.label(blobs, background=0)
<참고>
scipy.ndimage.find_objects()는 이미지내의 객체의 슬라이스를 리턴하므로 편리하다.
2) 마커를 사용한 방법
영역내 마커를 붙이면, 각각 이용 가능한 영역으로 분할된다.
(1) 분수령 분할
분수령 알고리즘(skimage.morphology.watershed())은 이미지의 "물통"을 가득채우도록 영역을 넓혀 가는 접근 방식이다.
from skimage.morphology import watershed
from skimage.feature import peak_local_max
# Generate an initial image with two overlapping circles
x, y = np.indices((80, 80))
x1, y1, x2, y2 = 28, 28, 44, 52
r1, r2 = 16, 20
mask_circle1 = (x - x1) ** 2 + (y - y1) ** 2 < r1 ** 2
mask_circle2 = (x - x2) ** 2 + (y - y2) ** 2 < r2 ** 2
image = np.logical_or(mask_circle1, mask_circle2)
# Now we want to separate the two objects in image
# Generate the markers as local maxima of the distance
# to the background
from scipy import ndimage
distance = ndimage.distance_transform_edt(image)
local_maxi = peak_local_max(distance, indices=False, footprint=np.ones((3, 3)), labels=image)
markers = morphology.label(local_maxi)
labels_ws = watershed(-distance, markers, mask=image)
(2) 랜덤 워크 분할
랜덤 워크 알고리즘(skimage.segmentation.random_walker())은 분수령 알고리즘과 비슷하지만, 보다 확률적인 접근 방식이다.
이미지의 라벨을 확산시킨다는 아이디어에 근거하고 있다.
from skimage import segmentation
# Transform markers image so that 0-valued pixels are to
# be labelled, and -1-valued pixels represent background
markers[~image] = -1
labels_rw = segmentation.random_walker(image, markers)
<참고>
; 라벨 이미지의 전처리
skimage의 몇 개의 유틸리티함수는 라벨 이미지(즉, 다른 영역으로 볼 수 있는 흩어진 값을 가지는 이미지)에 대해서 사용하는 것이 가능하다.
함수는 이름으로 역할을 알 수 있도록 명명되어 있다.
skimage.segmentation.clear_border()
skimage.segmentation.relabel_from_one()
skimage.morphology.remove_small_objects() 등
<연습문제>
- data서브 모델에서 coins 이미지를 읽어들인다.
- 몇 가지 분할 방법을 사용하여 배경에서 코인을 나눈다
(1) 오츠의 경계치 선정
(2) 적응 한계치 선정
(3) 분수령이나 랜덤 워크 분할
- 필요하다면 전처리함수를 사용하여 코인과 배경을 선별하자.
5. 영역의 특성을 추출
from skimage import measure
(예) 두 개로 분할되어 있는 영역의 사이즈와 범위의 길이를 계산:
properties = measure.regionprops(labels_rw)
[prop.area for prop in properties]
>>> [770.0, 1168.0]
[prop.perimeter for prop in properties]
>>> [100.91..., 126.81...]
<참고>
몇 가지의 특성값을 취득하기 위한 다른 API로는 scipy.ndimage.measurements에 있는 함수를 사용할 수 있다.
<연습문제 (전의 연습 문제와 계속해서)>
- 전의 연습문제의 코인과 배경의 2값 이미지를 이용하시오.
- 다른 코인에 라벨을 붙여보자.
- 코인 전부의 사이즈와 이심률을 계산해보자.
6. 데이터의 가시화와 대화조작
유효한 가시화는 파이프라인 처리를 테스트하는 데 편리하다.
몇 가지 이미지 처리 조작:
coins = data.coins()
mask = coins > filters.threshold_otsu(coins)
clean_border = segmentation.clear_border(mask)
2값의 결과를 가시화 하기:
plt.figure()
>>> <matplotlib.figure.Figure object at 0x...>
plt.imshow(clean_border, cmap='gray')
>>> <matplotlib.image.AxesImage object at 0x...>
등고선 가시화 하기:
plt.figure()
>>> <matplotlib.figure.Figure object at 0x...>
plt.imshow(coins, cmap='gray')
>>> <matplotlib.image.AxesImage object at 0x...>
plt.contour(clean_border, [0.5])
>>> <matplotlib.contour.QuadContourSet ...>
skimage의 특화한 유틸리티함수를 이용한다:
coins_edges = segmentation.mark_boundaries(coins, clean_border.astype(np.int))
(실험적인)scikit-image 뷰어
skimage.viewer = matplotlib 베이스 이미지 표시 캠퍼스 + 실험적인 Qt기반의 GUI 툴킷
from skimage import viewer
new_viewer = viewer.ImageViewer(coins)
new_viewer.show()
픽셀값의 표시에 편리하다.
보다 많은 조작을 위해, 뷰어에 플러그인을 추가할 수 있다:
new_viewer = viewer.ImageViewer(coins)
from skimage.viewer.plugins import lineprofile
new_viewer += lineprofile.LineProfile()
new_viewer.show()
7. 컴퓨터비전을 위한 특징 추출
기하적인 특징이나 표면의 질감 등 특징을 이미지로 부터 추출한다. 그 이유는 아래와 같다.
1) 이미지의 일부를 분류한다 (예를 들면 하늘과 빌딩)
2) 서로 다른 이미지의 일부를 매치시킨다 (예를 들면 물체 인식)
3) 더 많은 Computer Vision이 있다.
from skimage import feature
(예)Harris 검출기를 이용한 코너 검출 :
from skimage.feature import corner_harris, corner_subpix, corner_peaks
from skimage.transform import warp, AffineTransform
tform = AffineTransform(scale=(1.3, 1.1), rotation=1, shear=0.7,
translation=(210, 50))
image = warp(data.checkerboard(), tform.inverse, output_shape=(350, 350))
coords = corner_peaks(corner_harris(image), min_distance=5)
coords_subpix = corner_subpix(image, coords, window_size=13)
(이 예는 scikit-image의 plot_corner예로부터 채용되었다)
흥미롭게도, 이러한 코너는 scikit-image의 예 plot_matching에 있듯이 서로 다른 이미지에 있는 물체의 매치에 사용될 수 있다.
참고자료
http://www.turbare.net/transl/scipy-lecture-notes/packages/scikit-image/index.html
'IT > 언어' 카테고리의 다른 글
[python] ArgumentParser(argpaser)의 사용법 간단 정리 (0) | 2020.05.20 |
---|---|
[python] 리스트의 요소 추가와 다른 리스트와의 결합 (0) | 2020.05.19 |
[python] Iterator와 Generator (4) | 2020.05.13 |
[python] Pandas란 (0) | 2020.04.20 |
[python] scikit-learn이란 (0) | 2020.04.20 |