밤하늘을 카메라로 찍어본 적이 있는가? 도심은 빛 공해가 너무 심해서 별을 보기 어렵지만, 조용한 시골로 여행을 떠나면 밤하늘을 수놓은 별들을 관찰할 수 있다.
그런데, 이 사진속에 별이 몇개인지 어떻게 알 수 있을까? 손으로 일일히 세기에는 너무 많은데, 컴퓨터로 셀 수 있지 않을까?
위와 같은 검은색 배경에서 아주 작은 점과 같은 별의 개수를 세는 것을 영상처리에서는 "Blob을 찾는다" 혹은 "Edge를 검출한다"고 한다. 사진속 무늬가 별이라는 사실을 잠깐 잊으면, 검은색 배경에 얼룩이 묻어있는 것 처럼 보이기도 한다. Blob을 찾는 알고리즘중에 가장 유명한 방법은 바로 Laplacian of Gaussian (LoG)이다.
LoG 알고리즘은 망막신경절(Retinal ganglion cells, RGCs) 세포를 모델링한 것이다. 망막신경절 세포는 눈에서 받은 정보를 뇌로 전달하는 임무를 가지고 있다. 그런데 이 망막신경절 세포는 눈에서 받아들인 정보를 그대로 전달하지 않고, 독특한 방식으로 시각적인 데이터를 압축한다. 이들은 다양한 정보들 중 변화가 있는 것 또는 Edge에 대한 부분에만 반응한다. 조금 더 구체적으로, 망막신경절 세포의 종류에 따라 빛이 정확하게 세포 중앙에 오거나, 혹은 아예 주변부에만 비칠 경우에 평소보다 크게 흥분하게 된다. 이를 이용해서 Edge를 검출해낸다. (관련 내용에 대해서는 bskyvision.com/132 블로그에 설명이 상세하게 잘 되어있다. 일독을 권한다.)
아무튼, 이를 이용한 LoG 알고리즘은 다음과 같은 단계를 가지고 있다.
1. 이미지를 Grayscale로 import한다. - Noise를 줄이기 위함이다.
2. Gaussian Function을 이용해 blur처리 해준다. - 마찬가지로 Noise reduction을 해주기 위함이다. Gaussian Function $G(x,y) = exp(-\frac{x^2+y^2}{2\sigma^2})$ 이다. $x,y$는 각 이미지 픽셀의 좌표이고, $\sigma$는 표준편차를 의미한다. 즉, 표준편차가 커질수록 블러가 강해진다.
3. 이 Gaussian에 Laplacian을 걸어서 local minima를 찾는다. 이 local minima가 바로 blob이다.
4. Convolution을 통해서 전체 이미지 픽셀에 대해 이 작업을 수행한다.
이 작업을 Python을 이용해서 해볼텐데, skimage라는 module에 있는 blog_log 함수를 사용할 것이다. 그럼 결과로 $(y,x,\sigma)$ 의 ndarray형태의 blob의 위치와 크기에 관한 output을 받을 수 있다. 여기서 $\sigma$ 에 $\sqrt{2}$를 곱해주면 blob의 radius크기가 된다. 이를 이용해서 우리가 찾은 별에 동그라미를 쳐 볼 생각이다. 작업환경은 이번에도 역시 Google Colab. 이다.
먼저 전체 코드는 다음과 같다.
#Counting Stars
from skimage.feature import blob_log
from math import sqrt
import glob
from skimage import io
import matplotlib.pyplot as plt
#image import
our_file=glob.glob('/content/drive/MyDrive/test.jpg')[0]
#showing original image
io.imshow(our_file)
plt.show()
im = io.imread(our_file, as_gray=True)
#Laplacian of Gaussian, blob_log returns (y,x,sigma)
blobs_log = blob_log(im, max_sigma=30, num_sigma =25, threshold=0.05)
#Counted blobs = len of blobs_log
numrows = len(blobs_log)
print("Number of stars detected : ",numrows)
#Compute Radius of blobs, We have 2D image
blobs_log[:, 2] = blobs_log[:, 2] * sqrt(2)
#Draw circles around blobs
io.imshow(our_file) #base image
plt.title("location where stars detected") #title
for blob in blobs_log:
y, x, r = blob
c=plt.Circle((x, y), r, linewidth=0.5, fill=False, color='yellow')
plt.gca().add_patch(c)
plt.tight_layout()
plt.savefig('the_stars')
plt.show()
그 결과 이렇게 출력 된다.
Number of stars detected : 617
사실 skimage 모듈에 있는 함수를 적용하는거라서 크게 어렵지는 않다. 주석에 적혀있는대로, 그리고 글 서두에서 설명했던 대로, 이미지를 as_gray=True로 불러오고, blob_log 함수를 적용해서 blob의 위치와 크기정보를 파악한다. 이걸 가지고 다시 radius를 계산한 뒤, 각각의 위치에 동그라미를 쳐 주면 끝이다.
어떤 논문에서는 이를 미세플라스틱의 계수에 시도한 사례도 있다. 엣지를 찾는 것을 많은 곳에 활용할 수 있다는 점이 흥미롭다. 아, 수식적인 부분에서도 글 초반부에 언급한 블로그가 유익할 것이다. 더 깊게 알고싶은 사람이 참고하기 좋은 사이트라고 생각한다. (bskyvision.com/133)
>>이번 게시글의 Colab 노트북 주소 : colab.research.google.com/drive/1zzF1luChekTBdY1JCmbb8oH3hhx7sd9P?usp=sharing