JavaScript

Javascript Image Filter - convolution

La.place 2017. 5. 2. 18:06




 저번 포스팅에서 Javascript를 이용해서 간단한 이미지 필터를 만들어 보았다. 이번에는 컴퓨터 비전 분야에서 주로 사용하는 Mask를 이용해 좀 더 고차원 필터를 만들어 볼 것이다. 소스 코드는 저번에 만들었던 이미지 필터에서 발전시켜 나갈 예정이므로 코드가 없는 사람은 아래 github repository에서 다운받길 바란다. 아울러 앞에 내용에 대해 모르는 사람은 다음 아티클은 참고하도록 하자.

Mask ?

 코드를 짜기에 앞서 Mask란 무엇인지 부터 알아보도록 하자. Mask는 이렇게 생겼다.

[그림] Mask



 위 그림과 같이 보통 3x3, 5x5, 7x7 정방형 행렬로 이루어져 있으며 위 그림에서 w는 weight, 즉 가중치를 의미한다. 이전 포스팅에서 만들었던 이미지 필터는 하나의 픽셀당 하나의 처리 과정을 거쳤다. 하지만 이 Mask란걸 이용하면 한 픽셀을 가공할 때 주위 픽셀 값에 따라 특별한 처리를 해줄 수 있다. 다시 말해, 3x3마스크에서 정가운데 위치에 있는 픽셀 값을 정할 때, 주위 픽셀의 가중치를 반영하여 처리한다. 마스크를 이루는 가중치의 합은 반드시 0이 되어야한다. 한 가지 예를 들어 보자.

 


[그림] edge detection mask(sobel)


  위 마스크는 윤곽선을 검출 할때 사용하는 Mask중 하나이다. 윤곽선이란 경계선을 뜻하며, 경계선을 찾는 원리는 이렇다. 경계는 상대적으로 다른 색을 가진 두 영역 사이에 존재하기 때문에, 두 영역간 픽셀 차이가 두드러지게 나타나는 부분이 경계선이 될 것이다. 자 이제 위의 Mask를 적용해보자. 이제 Mask 안의 픽셀에 가중치를 곱한 후 모두 더하는 방식으로 변화값을 계산한다. 픽셀 차이가 클수록 변화량이 커지며 최종적으로 변화량이 큰 부분이 경계선이라는 것을 알 수 있다. 이해를 돕기 위해 설명이 잘 되어있는 아티클 하나를 참고하자. '소벨 검출이란?'  체스 보드를 예를 들어 Mask가 어떻게 적용되는지 이해하기 쉽게 설명이 되어있다. 이러한 Mask를 이용해서 모든 영역의 픽셀 값을 계산하면 윤곽선만 찾아낼 수가 있다. 이런 기법을 convolution(회선) 연산 이라고 하며, 공간 필터링(Spatial Filtering) 이라고도 한다. Mask종류에 따라 이미지에 독특한 처리를 해줄 수 있으며 이를 응용하면 좀 더 다양한 필터를 만들어 낼 수가 있다.


Convolution

위 이론을 바탕으로 convolution을 연산해주는 함수를 만들어 주도록 하자. filter.js 파일을 열고 아래에다가 다음과 같이 작성한다.

filter.js

function convolution(pixels, weights, opaque) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');

var side = Math.round(Math.sqrt(weights.length)); // 이미지 필터 가중치
var halfSide = Math.floor(side/2); // 가중치 절반 값 저장
var src = pixels.data; // 원본 데이터
var sw = pixels.width; // 원본 데이터 넓이
var sh = pixels.height; // 원본 데이터 높이
var w = sw;
var h = sh;
var output = ctx.createImageData(w, h);
var dst = output.data;
var alphaFac = opaque ? 1 : 0;
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy*sw+scx)*4;
var wt = weights[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3] * wt;
}
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = a + alphaFac*(255-a);
}
}
return output;
}

 위 함수는 각 가중치값을 가져와서 계산한 후에 각 픽셀의 rgb값에다가 결과를 반영한다. 위 코드는 (html5Rocks.com의 image filter 예제에서 가져왔다. 해당 문서는 하단의 Reference에 첨부되어 있으니 관심있으면 참조하도록 하자.) 이제 이 함수를 이용해서 앞에서 언급한 Sobel Mask를 한번 만들어 보자. 위의 Weight Mask를 그대로 사용하면 된다. convolution 함수의 첫번째 파라미터에는 canvas에서 가져온 pixel data를, 두번째 파라미터에는 정방형 mask 행렬을 넘겨주도록 하자. 위 그림에서 sobel mask의 Sx가중치를 넘겨 보았다.


filter.js

function sobel (pixels) {
return convolution(pixels,
[ -1, 0, 1,
-2, 0, 2,
-1, 0, 1], 1);
}

 다음으로 filterButton을 눌렀을 때, 이벤트 리스너를 수정해주자. image processing 주석 부분만 수정해 주면 된다. convolution함수의 return값이 pixel data이므로 그대로 putImageData를 사용해서 Canvas에 그려주자.

$('#filterButton').on('click', function () {
// imageData를 가져온다.
var pixels = ctx.getImageData(0,0, canvas.width, canvas.height);

// image processing
var filteredData = sobel(pixels);

// Canvas에 다시 그린다.
ctx.putImageData(filteredData, 0 , 0);
});


자 이제 결과를 확인해보자. 로컬에 html 파일을 연 후 이미지를 불러와서 필터 버튼을 누르면 다음과 같이 윤곽선부분이 강조되는 것을 볼 수 있다. 


[그림] Sobel Filter

 

 이번엔 Sharpen 효과를 줘 보도록 하자. Sharpen mask는 다음과 같이 생겼다. 가중치의 합은 0이되도록 만들었지만 가중치 값을 살짝 더주면 같은 필터라도 다양한 효과를 줄 수 있다. 가운데 가중치를 8이아니라 7, 9를 줘보고 직접 눈으로 어떻게 변하는지 확인해 보도록 하자.


filter.js

function sharpen(pixels){
return convolution(pixels,
[ -1, -1, -1,
-1, 8, -1,
-1, -1, -1 ], 1);
}

// image processing
var filteredData = sharpen(pixels);


[그림] Sharpen Filter



 마지막으로 뽀샤시 효과를 내주는 Blur Filter를 만들어 보도록 하자. Blur 필터의 경우엔 픽셀값을 나눠 흐릿 하게 하는 효과를 주면 된다. 이 필터도 역시 값을 바꿔가면서 테스트 해보도록 하자.


filter.js

function blur(pixels, value) {

var offset = 1/(value/10);

return convolution(pixels,
[offset, offset, offset,
offset, offset, offset,
offset, offset, offset ], 1);
}
// image processing
var filteredData = blur(pixels, 70);


[그림] Blur Filter



마치며

 이번엔 javascript보다는 영상처리에 관련된 부분에 대해 많이 알아본 것 같다. 사실 이런 분야는 수학적 이해가 많이 필요하다. mask 이론도 깊이 들어가면 미분과 적분에서 파생된 것이라 할 수 있다. 이해가 잘 가지 않아서 많이 찾아본 것 같다. 프로그래밍을 하면 할수록 수학의 필요성에 대해 많은 생각을 하게 된다. 아무튼 다양한 분야의 이론을 사용해서 웹 어플리케이션을 만드는 것도 색다른 재미가 있었다. 이번에는 컴퓨터 비전의 간단한 이론을 사용해 보았으니 다른 포스팅에서 컴퓨터 그래픽스쪽으로 한번 만들어 볼까 한다. 


Reference


'JavaScript' 카테고리의 다른 글

Functional Programming in Javascript  (8) 2017.07.22
TDD & Mocha - Javascript Test Framework  (0) 2017.05.14
Javascript ES6 Proxy  (1) 2017.04.16
Javascript Image Filter 만들기  (5) 2017.04.15
Webpack2 시작하기  (3) 2017.04.08